Simple but useful app to easily record, manage, and track teachers' and students' attendances in schools.
Built in - TechnologiesModules References
Importing modules
Environmental Variables
Database Entity Relationship model
App Routes
Hashing methods
Functions Flask app | Flask Mail | Flask WTForms | MySQL Connector | OS | Datetime | Tailwind CSS | ChartJS | JQuery Argon2 | Base64 | Anti CSRF Token Authlib's OAuth | Google's OAuth Flow
To quickly import all modules used in this project, a requirements file has been provided so simply execute the following command
pip3 install -r requirements.txt
and you'll be good to go.
DISCLAIMER: It is recommended to install all the modules after creating a virtual environment to avoid possible module conflicts with other projects in your drive.All the application's related variables such as the secret key, the Flask Mail's configuration data, GitHub's OAuth URLs & app's secrets and Google's are securely stored in the virtual environment (not included in this repository for security purposes since personal data is used).
Below a list and explanation of all the used virtual environment's variables.
Variable Name | Usage |
---|---|
FLASK_SECRET | Flask's secret key to securely manage all sessions and cookies data (as stated on Flask's API Documentation) |
MAIL_SERVER | Flask mail SMTP address (as stated on Flask Mail documentation) |
MAIL_PORT | Flask mail port (as stated on Flask Mail documentation) |
MAIL_USERNAME | Flask mail address (as stated on Flask Mail documentation) |
MAIL_PASSWORD | Flask mail password (as stated on Flask Mail documentation) (used in this project port 587) |
GITHUB_CLIENT_ID | Github OAuth app client ID (as stated on Github RESI API Documentation) |
GITHUB_CLIENT_SECRET | Github OAuth app client secret (as stated on Github RESI API Documentation) |
DB_USERNAME | Database user username (as stated on MySQL Connector Documentation) |
DB_PWORD | Database user password (as stated on MySQL Connector Documentation) |
DB_HOSTNAME | Database host address (as stated on MySQL Connector Documentation) |
DB_NAME | Database name (as stated on MySQL Connector Documentation) |
If you want to test this project on your own device you can import the database tables dump as well.
Route | Description |
---|---|
/ | Landing page. Accessible to anyone who doesn't have an account or is not logged in. If logged in, the user will be automatically redirected to the screening page. |
/login | Login form. Outputs the form with Email and password input and validates the input passed through a POST request. If already logged in, the user will be automatically redirected to the screening page. For more information about hashed password verification, this section will better explain the process. |
/forgot-password | Outputs the form with Email input and sends an email to that address with a reset password link. Accessible via the `/login` page. For more information about Email sending function and reset password link GET parameters, this section will better explain the process. |
/user/updatepassword | Ouptuts the form with Password and Verify Password. Accessible via Email reset password link and after the first login on the application. Input is then validated and the user's password gets updated on the database. For more information about password verification and hashing, this section will better explain the process. |
/auth/github | Redirects to GitHub's OAuth App authorization. For more information about GitHub's OAuth App Authorization process, please visit the official documentation. |
/auth/github/callback | GitHub OAuth App callback page, used to manage GitHub OAuth REST API return values. For more information about GitHub's OAuth App Authorization process, please visit the official documentation. |
/auth/github/disconnect | Removes GitHub Account user ID from the database |
/auth/google | Redirects to Google's OAuth App Authorization. For more information about Google's OAuth App Authorization process, please visit the official documentation. |
/auth/google/callback | Google OAuth App callback page, used to manage Google OAuth API return values. For more information about Google's OAuth App Authorization process, please visit the official documentation. |
/auth/google/disconnect | Removes Google Account user ID from the database |
/user | User screening. Outputs all the user's related information such as name, role, and last login time as well as a list of all upcoming lessons, a user creation form (ADMIN user role only), a lesson creation form (ADMIN and TEACHER user roles only), a course creation form (ADMIN user role only), a pie chart of attended/not attended lessons percentage (STUDENT and TEACHER user roles only) or a bar chart of the total number of attendances subdivided per lesson date and course name in a chosen range of 7 days, 14 days or 30 days (TEACHER and ADMIN user roles only). For more information about Chart generation, this section will better explain the process. Accessible only if a valid session is open, otherwise redirects to the login page with an error message. |
/user/create | User creation form and data insertion in the database (ADMIN user role only). For more information about form validation, the official flask-wtf documentation will better explain how the whole validation process works. |
/lesson/create | Lesson creation form and data insertion in the database (ADMIN and TEACHER user roles only). For more information about form validation, the official flask-wtf documentation will better explain how the whole validation process works. |
/lesson/list | Shows a list of all the upcoming lessons with a button to manage the lesson's attendances (if the lesson's date is the same as the current date) (ADMIN and TEACHER user roles only), a button to update all lesson's parameters such as subject, description, time and date, and more (ADMIN and TEACHER user roles only), and a button to delete the lesson. The page shows 10 lessons per page, ordered per lesson date. The list varies based on the user role accessing the page; if the role is STUDENT or TEACHER, the list will show only the upcoming lessons for their specific enrolled courses. |
/lesson | Lesson screening. Outputs a form with all lesson's information and a button to submit the modifications and update the database (ADMIN and TEACHER user roles only). |
/lesson/register-attendance | Lesson screening. Outputs the selected lesson name as well as a list of all enrolled students and a checkbox to record students' attendances (ADMIN and TEACHER user roles only). |
/lesson/attendances | Custom API that returns a JSON file about all lessons attendances count in a defined range. For more information about this API, this section will better explain the process. |
/lessons/attendances/percentage | Custom API that returns a JSON file about all lessons attendances/non-attendances percentage rate in a defined range. For more information about this API, this section will better explain the process. |
/course/create | Course creation form and data insertion in the database (ADMIN user role only). For more information about form validation, the official flask-wtf documentation will better explain how the whole validation process works. |
/user/list | Shows a list of all users registered in the platform with a button to update each user's parameters such as name, surname, enrolled courses, and more (ADMIN user role only), and a button to delete the user from the Database (ADMIN user role only). The page shows 10 students per page, ordered per role (Admin, then Teachers, and then Students). |
/user/select | Outputs a form to update chosen user's parameters such as name, surname, enrolled courses, and more (ADMIN user role only). The inputted data is then processed based on the session user role who is making the request; if the user role is ADMIN, then the selected user data will be related to all selected user's information, otherwise, the parameters to update will be just Email and Password if the session user role is STUDENT or TEACHER (in this case, the update request will be processed based on self user's data because only the ADMIN can update other user's parameters). For more information about form validation, the official flask-wtf documentation will better explain how the whole validation process works. |
/user/logout | Clears the user session and logs the user out of the application. |
/cookie-policy | Cookie Policy page randomly generated from Termly policies generator tool. |
/terms-and-conditions | Terms and conditions page randomly generated from Termly policies generator tool. |
/privacy | Privacy policy page randomly generated from Termly policies generator tool. |
All user-related sensitive data such as passwords are securely hashed and stored in the database using Argon2id algorithm. To manage and verify such data, Argon2-cffi Python module is being used, in particular the `PasswordHasher` class and its relative methods verify for login and reset password verification functionalities and hash for user creation and reset password functionalities. Non-matching passwords after the verify function is called are managed with Argon2 module built-in VerifyMismatchError exception.
Instead, the password reset route uses an authenticity request method that requires the GET parameters `mail` and `uid` to be hashed with base64 algorithm in order to lower the possibility for an attacker to guess a user email and user ID combination and so reset his password.
Starts a connection to the database with the given data through MySQLConnector.connect() function. Connection not established error is managed with MySQLConnector.Error error.
Returns MySQLConnection object or `False` if the connection to Database fails for some reason.
Executes a simple `select` query on the database and updates the global list `courses` with all the formatted courses's names and years.
Returns the formatted output of cursor.execute().
An example of output from this function will be:
[
'1a Sviluppo software',
'2a Cybersecurity',
'1a Sviluppo web'
]
For more information about query to list conversion, this section will better explain the process.
Manages the function to call based on the session's user's role and the given uid passed as a parameter. The function takes as parameters the form to be processed in the to-be-called function and the uid as optional which will represent the selected user ID if the ADMIN is requesting to update some chosen user. This function calls the updateDataAsUser function if the session's user role is STUDENT or TEACHER or the `uid` parameter is not given (means that the ADMIN wants to update his information), otherwise the updateDataAdAdmin function with the passed `uid` and `form` as function parameters. Returns `True` if the callback function returns `True` as well, otherwise `False`.
Gets a MySQLConnector cursor as a parameter and returns a list with dictionaries as list's items with cursor columns names as keys and obtained values as dictionary values. Briefly, this function will iterate through the entire cursor and create a dictionary for each query returned column. If the cursor is empty this function will return an empty list.
This function will return a list like the following
[
{
'nomeCorso': 'Sviluppo Software',
'annoCorso': '2023-2024'
},
{
'nomeCorso': 'Sviluppo Web',
'annoCorso': '2023-2024'
}
]
Programmatically updates the user's last login time on the database by executing an `update` SQL query on the database. The user is defined by the session's userID value and the current time is obtained from date.today() function.
Returns the formatted current date in `%d/%m/%Y` format by using date.strftime() function.
Checks if the user has a linked Github account. The user is defined by the session's userID value
Returns a boolean value corresponding `True` if the defined user has a linked Github Account or `False` if the Database's "githubID" field is empty
Updates user's "githubID" database column with the GitHub account id passed as function parameter from GitHub OAuth API return values. Before updating the column, a verification is done; a cursor gets instantiated and checks if the obtained GitHub user id is already linked to an account with a simple `select` query. If the cursor's result contains no rows (which means that no users have linked that specific GitHub account to their attendance tracker account), then it is possible to update the user's `githubID` column and the session's related `githubConnected` value is set to `True`.
Returns `True` if the column is updated, otherwise `False` in all other cases.
Looks for GitHub user ID obtained from the GitHub account ID from GitHub OAuth API return values and executes a `select` query to obtain the relative user ID from the database. If the user is found, a session with all needed user information gets created.
Returns `True` if the GitHub user ID was found in the database, otherwise `False`.
Checks if the user has a linked Google account. The user is defined by the session's userID value
Returns a boolean value corresponding `True` if the defined user has a linked Google Account or `False` if the Database's "googleID" field is empty
Updates user's "googleID" database column with the Google account ID passed as function parameter from Google OAuth API return values. Before updating the column, a verification is done; a cursor gets instantiated and checks if the obtained Google user id is already linked to an account with a simple `select` query. If the cursor's result contains no rows (which means that no users have linked that specific Google account to their attendance tracker account), then it is possible to update the user's `googleID` column and the session's related `googleConnected` value is set to `True`.
Returns `True` if the column is updated, otherwise `False` in all other cases.
Looks for Google user ID obtained from the Google account ID from Google's OAuth API return values and executes a `select` query to obtain the relative user ID from the database. If the user is found, a session with all needed user information gets created.
Returns `True` if the Google user ID was found in the database, otherwise `False`.
Validates form user input by checking if the input data is not an empty string.
Returns `True` if the passed string is empty, otherwise `False`
Obtains all users from the database by executing a `select` query.
Returns the formatted output of cursor.execute().
The returning list of this function will look something like this:
[
{
'userID': 1,
'Nome': 'Mario',
'Cognome': 'Rossi',
'Tipologia': 'Studente',
'nomeCorso': 'Sviluppo software',
'annoCorso': 1
},
{
'userID': 2,
'Nome': 'Luigi',
'Cognome': 'Verdi',
'Tipologia': 'Insegnante',
'nomeCorso': 'Cybersecurity',
'annoCorso': 2
}
]
For more information about query to list conversion, this section will better explain the process.
Gets all user's related data by executing a `select` query. The user is defined by the passed function's parameter `uid`.
Returns the formatted output of cursor.execute() in form of dictionary.
An example output of this function is:
{
'userID': 1,
'Nome': 'Mario'
'Cognome': 'Rossi',
'Tipologia': 'Studente',
'Email': 'mariorossi@mr.com',
'nomeCorso': 'Sviluppo software',
'annoCorso': 1
}
For more information about query to dictionary conversion, this section will better explain the process.
Updates all selected user's data (ADMIN user only). Takes as parameters the selected user ID and the form that will be used to get the data from and to be used to update the database. For more information about password hashing, this section will better explain the process.
Updates all selected user's data. Takes as parameters the form that will be used to get the data from and to be used to update the database. For more information about password hashing, this section will better explain the process.
Gets all courses' names and the relative years as parameters (MUST be type list()) and executes a query for each item to check if the actual selection exists. Used in user creation and update and lesson creation and update. Also allows a filter for the validation based on the chosen userID to be checked (preference passed in the `userFilter` function parameter). Returns `False` if the DB response returns `None`, else `True` if all requests return a value
Returns a custom message based on the system clock time and a matrix with the default messages and the time ranges which will be selected to be printed out on the user screening page.
Getting all courses from the query and creating a single formatted list with all the obtained ones
An example of list output will be:
[
'1a Sviluppo software',
'2a Cybersecurity'
]
Removes a defined user from the database based on the passed parameter `uid`.
Executes a query and returns all the upcoming lessons based on user type and enrolled courses filters, and the total number of lessons. Takes as parameters a `limit` variable used to limit the number of results and a `page` variable used to get the next `limit`ed results. Also allows to get the upcoming lessons of a determined user by passing the `uid` and `isTeacher` parameters which respectively represent the userID to get the lessons of and, if the user is a Teacher, the `isTeacher` value must be set to `True`
Returns a list with a dictionary for each list index and the total number of obtained lessons
[
[
{
'idLezione': 1,
'Materia': 'Python',
'Descrizione': 'Sviluppo app in Flask',
'dataLezione': '2024-03-27',
'oraInizio': '09:00',
'oraFine': '13:00',
'aula': 'A001',
'Tipologia': 'Laboratorio',
'nomeCorso': '1a Sviluppo software',
'Presenza': 1
},
{
'idLezione': 2,
'Materia': 'Sviluppo siti web',
'Descrizione': 'TailwindCSS e JS',
'dataLezione': '2024-03-28',
'oraInizio': '09:30',
'oraFine': '13:30',
'aula': 'A003',
'Tipologia': 'Lezione',
'nomeCorso': '1a Sviluppo web',
'Presenza': 0
}
], 2
]
With a filter on the lesson's course if the request is being made from a STUDENT or TEACHER user role.
Verifies if the user exists before sending the recover password email. Used in user password recovery function. Returns its relative `userID` if the query returns a value, otherwise `False`
Takes a string as input parameter and returns its relative base64 encoded/decoded value. If `encode` parameter is `True`, the string will be encoded, if it's `False` it will be decoded. For more information about encoding and decoding with base64, this section will better explain the process.
Executes a query and returns a list of all the users attending a defined course. Course is chosen based on the function parameters `courseName` and `courseYear`.
An example output of this function will be:
[
{
'userID': 1,
'Tipologia': 'Insegnante'
},
{
'userID': 2,
'Tipologia': 'Studente'
},
{
'userID': 3,
'Tipologia': 'Studente'
},
{
'userID': 4,
'Tipologia': 'Studente'
}
]
Executes a query and returns in the form of JSON the count of attendances subdivided per lesson date and course ID. Takes as parameters a `range` which will be used to get all the lessons starting from the current date (obtained from datetime's date.today class' function) to `range` days before.
The output of this API will be:
[
{
"conteggioPresenze": 5,
"dataLezione": "20/03/2024",
"nomeCorso": "1a Cybersecurity"
},
{
"conteggioPresenze": 14,
"dataLezione": "20/03/2024",
"nomeCorso": "1a Cybersecurity"
},
{
"conteggioPresenze": 11,
"dataLezione": "20/03/2024",
"nomeCorso": "1a Cybersecurity"
},
{
"conteggioPresenze": 8,
"dataLezione": "20/03/2024",
"nomeCorso": "1a Sviluppo software"
},
{
"conteggioPresenze": 9,
"dataLezione": "20/03/2024",
"nomeCorso": "2a Sviluppo software"
},
{
"conteggioPresenze": 1,
"dataLezione": "20/03/2024",
"nomeCorso": "3a Sviluppo software"
},
{
"conteggioPresenze": 6,
"dataLezione": "20/03/2024",
"nomeCorso": "1a Sviluppo web"
},
{
"conteggioPresenze": 3,
"dataLezione": "20/03/2024",
"nomeCorso": "2a Sviluppo web"
},
{
"conteggioPresenze": 7,
"dataLezione": "21/03/2024",
"nomeCorso": "1a Sviluppo web"
},
{
"conteggioPresenze": 2,
"dataLezione": "24/03/2024",
"nomeCorso": "1a Cybersecurity"
}
]
Gets a list and converts each obtained course from the `getLessonsAttendancesCount` function to the desired format. Returns a list of dictionaries like this one:
[
{
"nomeCorso": "1a Cybersecurity"
"dataLezione": "20/03/2024",
"conteggioPresenze": 5,
},
{
"nomeCorso": "1a Cybersecurity"
"dataLezione": "20/03/2024",
"conteggioPresenze": 14,
},
{
"nomeCorso": "1a Cybersecurity"
"dataLezione": "20/03/2024",
"conteggioPresenze": 11,
},
{
"nomeCorso": "1a Sviluppo software"
"dataLezione": "20/03/2024",
"conteggioPresenze": 8,
},
{
"conteggioPresenze": 9,
"dataLezione": "20/03/2024",
"nomeCorso": "2a Sviluppo software"
},
{
"nomeCorso": "3a Sviluppo software"
"dataLezione": "20/03/2024",
"conteggioPresenze": 1,
},
{
"nomeCorso": "1a Sviluppo web"
"dataLezione": "20/03/2024",
"conteggioPresenze": 6,
},
{
"nomeCorso": "2a Sviluppo web"
"dataLezione": "20/03/2024",
"conteggioPresenze": 3,
},
{
"nomeCorso": "1a Sviluppo web"
"dataLezione": "21/03/2024",
"conteggioPresenze": 7,
},
{
"nomeCorso": "1a Cybersecurity"
"dataLezione": "24/03/2024",
"conteggioPresenze": 2,
}
]
Executes a query and gets all courses' names and years based on function parameter `uid`. If the `uid` parameter is not passed, the default value will be the user's session value `uid`. Returns a list if the query returns valid values, else `False` if the connection to the database fails.
An example of a possible returning list can be:
[
{
'userID': 1,
'Tipologia': 'Insegnante'
},
{
'userID': 2,
'Tipologia': 'Studente'
},
{
'userID': 3,
'Tipologia': 'Studente'
},
{
'userID': 4,
'Tipologia': 'Studente'
}
]
Executes a query and returns a list of all teachers' IDs, names, and surnames if the query returns valid values, otherwise `False` if the connection to the Database fails.
This function will return a list similar to the following
[
{
'id': 10,
'Nome': 'Francesco Verdi',
},
{
'id': 11,
'Nome': 'Mario Grigi',
},
{
'id': 12,
'Nome': 'Paolo Bianchi',
}
]
Gets the `page` parameter from the URL and validates it based on some basic controls. Used in /lesson/list and /user/list routes. Returns the `page` value if it's valid, otherwise `False` if not.
Executes a query and obtains all the information related to the lesson's `lessonID` function parameter. Returns a dictionary with all useful lesson information such as Subject, description, date and time, and all assigned teacher-related information.
The returned dictionary will be:
{
idLezione: 1,
'Materia': 'Python',
'Descrizione': 'Sviluppo app in Flask',
'dataLezione': '2024-03-27',
'oraInizio': '09:00',
'oraFine': '12:00',
'aula': 'A001',
'Tipologia': 'Laboratorio',
'idInsegnante': 1,
'Nome': 'Mario Rossi',
'nomeCorso': '1a Sviluppo software'
}
Updates lesson's data based on form submitted and validated values. Used in /lesson route. Returns `True` if the process succeeds, otherwise `False` with a flashed message if the process catches an error.
Converts a given `timedelta` type to datetime. Takes as parameters the actual `timedelta` `time` and its relative format to correctly convert the value. Returns the converted time.