Compare commits
No commits in common. "master" and "v0.1" have entirely different histories.
1
.gitignore
vendored
1
.gitignore
vendored
@ -112,4 +112,3 @@ dmypy.json
|
||||
|
||||
# database files (may contain sensitive student info)
|
||||
*.db
|
||||
.vscode
|
||||
|
2
LICENSE
2
LICENSE
@ -186,7 +186,7 @@
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright 2018-2019 Abdulkadir Furkan Şanlı
|
||||
Copyright 2018 Abdulkadir Furkan Şanlı
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
96
README.md
96
README.md
@ -1,56 +1,64 @@
|
||||
# ib-clearance
|
||||
|
||||
ib-clearance is a program which helps schools manage the entrance and exiting of their IB Diploma Program students. Due to the unique nature of IB students' timetables, the program helps to automatically check individual timetables and give clearance to students who have finished for the day.
|
||||
ib-clearance is a program which helps schools manage the entrance and exiting of their IB students. Due to the unique nature of IB students' timetables, the program helps check individual timetables and give clearance to students who have finished for the day.
|
||||
|
||||
## Building & running
|
||||
### Building
|
||||
|
||||
The code is currently designed to run on Python 3.6.* and requires the [`getch`](https://pypi.org/project/getch/) module on platforms other than Windows.
|
||||
The code is currently designed to run on Python 3.6.*.
|
||||
|
||||
Binaries can be built using [`pyinstaller`](https://pypi.org/project/PyInstaller/): `pyinstaller --onefile ib-clearance.py`
|
||||
I recommend building executables with `pyinstaller` with `pyinstaller --onefile ib-clearance.py`.
|
||||
|
||||
## Database specification
|
||||
### Lesson IDs
|
||||
|
||||
The program requires a SQLite database `database.db` in the working directory, containing data for all school classes.
|
||||
Equivalents of lesson names for use in class databases.
|
||||
|
||||
`database.db` must contain:
|
||||
|
||||
| Table | Description |
|
||||
| ----------- | ------------------------------------------------------------ |
|
||||
| `students` | Contains students' names, ID numbers and subject preferences |
|
||||
| `timetable` | Timetable information (lessons) |
|
||||
|
||||
### `students`
|
||||
|
||||
| Column | Data type | Description |
|
||||
| --------------- | --------- | ----------------------------------------------- |
|
||||
| `id` | `INTEGER` | Student ID number used in program |
|
||||
| `student_class` | `INTEGER` | IB class number (e.g. `1`, `2`) |
|
||||
| `name` | `TEXT` | Full name of student |
|
||||
| `g1` | `TEXT` | Group 1 subject ID |
|
||||
| `g2` | `TEXT` | Group 2 subject ID |
|
||||
| `g3` | `TEXT` | Group 3 subject ID |
|
||||
| `g4` | `TEXT` | Group 4 subject ID |
|
||||
| `g5` | `TEXT` | Group 5 subject ID |
|
||||
| `g6` | `TEXT` | Group 6 subject ID |
|
||||
| `other` | `TEXT` | ID(s) of other class(es) taken, space seperated |
|
||||
|
||||
### `timetable`
|
||||
|
||||
| Column | Data type | Description |
|
||||
| -------------- | --------- | --------------------------------------------- |
|
||||
| `lesson_class` | `INTEGER` | IB class which takes lesson (e.g. `1`) |
|
||||
| `day` | `TEXT` | Day of the week (e.g. `monday`) |
|
||||
| `end_time` | `TEXT` | Ending time of lesson (e.g. `09:55`, `13:05`) |
|
||||
| `lesson_id` | `TEXT` | Subject ID of the lesson (e.g. `tok`) |
|
||||
|
||||
#### Subject IDs
|
||||
|
||||
Subject IDs may be anything, as long as consistency is maintained throughout the tables.
|
||||
- Academic Writing
|
||||
- 'aw'
|
||||
- Biology
|
||||
- 'bi'
|
||||
- Business Management
|
||||
- 'bm'
|
||||
- Chemistry
|
||||
- 'ch'
|
||||
- Computer Science
|
||||
- 'cs'
|
||||
- Economics
|
||||
- 'ec'
|
||||
- English A
|
||||
- 'ena'
|
||||
- English B
|
||||
- 'enb'
|
||||
- French
|
||||
- 'fr'
|
||||
- Geography
|
||||
- 'ge'
|
||||
- German
|
||||
- 'de'
|
||||
- Guidance
|
||||
- 'gu'
|
||||
- Mathematical Studies
|
||||
- 'mst'
|
||||
- Mathematics HL
|
||||
- 'mhl'
|
||||
- Mathematics SL
|
||||
- 'msl'
|
||||
- Polish A
|
||||
- 'pl'
|
||||
- Psychology
|
||||
- 'ps'
|
||||
- Self-Taught Language A
|
||||
- 'st'
|
||||
- Spanish
|
||||
- 'es'
|
||||
- Theory of Knowledge
|
||||
- 'tok'
|
||||
- Turkish A
|
||||
- 'tr'
|
||||
- Visual Arts
|
||||
- 'va'
|
||||
|
||||
## License
|
||||
|
||||
Copyright (C) 2018 Abdulkadir Furkan Şanlı
|
||||
|
||||
ib-clearance is made available under the Apache License 2.0.
|
||||
|
||||
Copyright 2018-2019 Abdulkadir Furkan Şanlı
|
||||
|
||||
Copying and distribution of this file, with or without modification, are permitted in any medium without royalty provided the copyright notice and this notice are preserved. This file is offered as-is, without any warranty.
|
||||
|
13
TODO.md
13
TODO.md
@ -1,13 +0,0 @@
|
||||
To-Do List
|
||||
==========
|
||||
|
||||
## v0.2
|
||||
- [x] Error handling (when entering invalid data types)
|
||||
- [x] Make database contain data for all classes
|
||||
- [x] Flexible mandatory subjects
|
||||
- [x] Make script functional (sorta)
|
||||
- [x] Document the damn thing
|
||||
|
||||
## v0.3
|
||||
- [ ] Usability and GUI
|
||||
- [ ] Timetable editor (add & remove lessons, view timetable)
|
207
ib-clearance.py
207
ib-clearance.py
@ -1,6 +1,6 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
# Copyright 2018-2019 Abdulkadir Furkan Şanlı
|
||||
# Copyright 2018 Abdulkadir Furkan Şanlı
|
||||
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
@ -14,72 +14,54 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
|
||||
import sqlite3
|
||||
import time
|
||||
from os import name
|
||||
from sys import exit
|
||||
|
||||
# Windows has standard library getch, use that.
|
||||
if name == "nt":
|
||||
from msvcrt import getch
|
||||
else:
|
||||
from getch import getch
|
||||
|
||||
|
||||
def connect_database(file):
|
||||
"""Get database connection object.
|
||||
|
||||
Connect to SQLite database and return a connection object.
|
||||
|
||||
:param file: SQLite database
|
||||
"""
|
||||
Create and return Connection object
|
||||
:param file: database file
|
||||
"""
|
||||
conn = sqlite3.connect(file)
|
||||
return conn
|
||||
|
||||
|
||||
def get_data(conn):
|
||||
"""Get student data.
|
||||
|
||||
Request student ID. Return tuple (id, class, other) with valid
|
||||
student ID, corresponding class number and "other" subject IDs.
|
||||
|
||||
:param conn: Connection object
|
||||
def get_lower_weekday():
|
||||
"""
|
||||
try:
|
||||
id = int(input("\nPlease enter student ID: "))
|
||||
|
||||
cur = conn.cursor()
|
||||
cur.execute("""
|
||||
SELECT id, student_class, other
|
||||
FROM students
|
||||
WHERE id = ?
|
||||
""", (id,))
|
||||
data = cur.fetchone()
|
||||
|
||||
if data is not None:
|
||||
data = list(data) # SQLite returns tuples.
|
||||
data[2] = data[2].split()
|
||||
else:
|
||||
raise ValueError
|
||||
|
||||
return data
|
||||
except ValueError:
|
||||
print("\nInvalid ID, try again.")
|
||||
return get_data(conn)
|
||||
Return current weekday's name as lowercase string
|
||||
"""
|
||||
day = time.strftime("%A")
|
||||
lower_day = day.lower()
|
||||
return lower_day
|
||||
|
||||
|
||||
def select_final_time(conn, day, data):
|
||||
"""Select student finishing time.
|
||||
|
||||
Returns ending time (type struct_time) of final lesson student must
|
||||
attend on given day, or None if no lessons.
|
||||
|
||||
def check_id(conn, id):
|
||||
"""
|
||||
Return True if existing ID, False is not.
|
||||
:param conn: Connection object
|
||||
:param day: current weekday, lowercase str
|
||||
:param data: student data tuple returned by get_data
|
||||
:param id: ID of student, int
|
||||
"""
|
||||
cur = conn.cursor()
|
||||
cur.execute("SELECT id FROM students")
|
||||
ids = cur.fetchall()
|
||||
|
||||
for x in ids:
|
||||
if id == x[0]:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
def select_final_time(conn, day, id):
|
||||
"""
|
||||
Return ending time of final lesson student must attend, or None.
|
||||
:param conn: Connection object
|
||||
:param day: current weekday, lowercase str
|
||||
:param id: ID of student, int
|
||||
"""
|
||||
cur = conn.cursor()
|
||||
# Select finishing time of IB lessons.
|
||||
cur.execute("""
|
||||
SELECT end_time
|
||||
FROM timetable
|
||||
@ -89,110 +71,55 @@ def select_final_time(conn, day, data):
|
||||
OR g4 = lesson_id
|
||||
OR g5 = lesson_id
|
||||
OR g6 = lesson_id
|
||||
WHERE id = ?
|
||||
AND day = ?
|
||||
AND lesson_class = ?
|
||||
OR lesson_id = 'gu'
|
||||
OR lesson_id = 'to'
|
||||
WHERE id = ? AND day = ?
|
||||
ORDER BY end_time DESC
|
||||
LIMIT 1
|
||||
""", (data[0], day, data[1])) # Queries return None if no matches.
|
||||
ib_finish = cur.fetchone()
|
||||
if ib_finish is not None:
|
||||
ib_finish = parse_time_string(ib_finish[0])
|
||||
|
||||
# Select finishing time of other lessons.
|
||||
others = []
|
||||
for subject in data[2]:
|
||||
cur.execute("""
|
||||
SELECT end_time
|
||||
FROM timetable
|
||||
INNER JOIN students ON ? = lesson_id
|
||||
WHERE id = ?
|
||||
AND day = ?
|
||||
AND lesson_class = ?
|
||||
ORDER BY end_time DESC
|
||||
LIMIT 1
|
||||
""", (subject, data[0], day, data[1]))
|
||||
subject_finish = cur.fetchone()
|
||||
if subject_finish is not None:
|
||||
others.append(parse_time_string(subject_finish[0]))
|
||||
if not others:
|
||||
others_finish = None
|
||||
""", (id, day))
|
||||
finish = cur.fetchone()
|
||||
if str(type(finish)) == "<class 'tuple'>":
|
||||
return finish[0]
|
||||
else:
|
||||
others_finish = max(others)
|
||||
|
||||
if ib_finish is not None and others_finish is not None:
|
||||
if ib_finish > others_finish:
|
||||
finish = ib_finish
|
||||
else:
|
||||
finish = others_finish
|
||||
elif ib_finish is not None:
|
||||
finish = ib_finish
|
||||
elif others_finish is not None:
|
||||
finish = others_finish
|
||||
else:
|
||||
finish = None
|
||||
|
||||
return finish
|
||||
return finish
|
||||
|
||||
|
||||
def parse_time_string(timestring):
|
||||
"""Convert time string to tuple.
|
||||
|
||||
Parses given 24h time string of format "HH:MM" into the struct_time
|
||||
object provided by library time.
|
||||
|
||||
"""
|
||||
Parse given 24h time string of format "HH:MM" into time_struct
|
||||
:param timestring: time string "HH:MM"
|
||||
"""
|
||||
structtime = time.strptime(timestring, "%H:%M")
|
||||
return structtime
|
||||
|
||||
|
||||
def clear(finish_time):
|
||||
"""Return clearance message.
|
||||
|
||||
Compare the current time with finishing time and return relevant
|
||||
clearance message.
|
||||
|
||||
:param finish_time: finishing time, struct_time object
|
||||
"""
|
||||
current_time = parse_time_string(time.strftime("%H:%M"))
|
||||
if finish_time == None:
|
||||
message = "Student has no lessons today, clear to leave."
|
||||
elif finish_time < current_time:
|
||||
message = "Student has finished for today, clear to leave."
|
||||
else:
|
||||
message = "Student still has lessons, clearance not granted."
|
||||
return message
|
||||
|
||||
|
||||
def exit_sequence():
|
||||
"""Initiate exit sequence.
|
||||
|
||||
Asks user to enter character, and exits program if X is pressed.
|
||||
"""
|
||||
print("Press X to exit.")
|
||||
key = getch()
|
||||
if isinstance(key, bytes):
|
||||
# msvcrt.getch returns type bytes, decode into str.
|
||||
key = key.decode()
|
||||
if key.upper() == "X":
|
||||
exit()
|
||||
timestruct = time.strptime(timestring, "%H:%M")
|
||||
return timestruct
|
||||
|
||||
|
||||
def main():
|
||||
print("ib-clearance")
|
||||
print("Copyright 2018-2019 Abdulkadir Furkan Şanlı")
|
||||
db = input(
|
||||
"Please enter name of database (located in the same folder as the program): ")
|
||||
|
||||
conn = connect_database("database.db")
|
||||
y = "y"
|
||||
while y == "y":
|
||||
conn = connect_database(db)
|
||||
day = get_lower_weekday()
|
||||
id = int(input("Please input the student ID number: "))
|
||||
|
||||
while True:
|
||||
student_data = get_data(conn)
|
||||
day = time.strftime("%A").lower()
|
||||
finish_time = select_final_time(conn, day, student_data)
|
||||
if check_id(conn, id):
|
||||
raw = select_final_time(conn, day, id)
|
||||
final = parse_time_string(raw) if raw != None else None
|
||||
|
||||
print("\n" + clear(finish_time) + "\n")
|
||||
if final != None:
|
||||
current_time = parse_time_string(time.strftime("%H:%M"))
|
||||
|
||||
exit_sequence()
|
||||
if current_time > final:
|
||||
print("Clear to leave.")
|
||||
else:
|
||||
print("Student does not have clearance.")
|
||||
elif final == None:
|
||||
print("Clear to leave.")
|
||||
else:
|
||||
print("Student does not exist in the database. Please try again.")
|
||||
|
||||
y = input("Press y and enter to run again.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
Reference in New Issue
Block a user