update,
This commit is contained in:
12
daniel_jo/ITP4459_assignment_2023/.editorconfig
Normal file
12
daniel_jo/ITP4459_assignment_2023/.editorconfig
Normal file
@@ -0,0 +1,12 @@
|
||||
# EditorConfig is awesome: https://EditorConfig.org
|
||||
|
||||
# top-most EditorConfig file
|
||||
root = true
|
||||
|
||||
[*]
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
end_of_line = crlf
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
181
daniel_jo/ITP4459_assignment_2023/.gitignore
vendored
Normal file
181
daniel_jo/ITP4459_assignment_2023/.gitignore
vendored
Normal file
@@ -0,0 +1,181 @@
|
||||
**/__pycache__
|
||||
|
||||
*.del
|
||||
**/volumes
|
||||
|
||||
# Created by https://www.toptal.com/developers/gitignore/api/python
|
||||
# Edit at https://www.toptal.com/developers/gitignore?templates=python
|
||||
|
||||
### Python ###
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
# Distribution / packaging
|
||||
.Python
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
wheels/
|
||||
share/python-wheels/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
MANIFEST
|
||||
|
||||
# PyInstaller
|
||||
# Usually these files are written by a python script from a template
|
||||
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||
*.manifest
|
||||
*.spec
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
pip-delete-this-directory.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
htmlcov/
|
||||
.tox/
|
||||
.nox/
|
||||
.coverage
|
||||
.coverage.*
|
||||
.cache
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
*.cover
|
||||
*.py,cover
|
||||
.hypothesis/
|
||||
.pytest_cache/
|
||||
cover/
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
*.pot
|
||||
|
||||
# Django stuff:
|
||||
*.log
|
||||
local_settings.py
|
||||
db.sqlite3
|
||||
db.sqlite3-journal
|
||||
|
||||
# Flask stuff:
|
||||
instance/
|
||||
.webassets-cache
|
||||
|
||||
# Scrapy stuff:
|
||||
.scrapy
|
||||
|
||||
# Sphinx documentation
|
||||
docs/_build/
|
||||
|
||||
# PyBuilder
|
||||
.pybuilder/
|
||||
target/
|
||||
|
||||
# Jupyter Notebook
|
||||
.ipynb_checkpoints
|
||||
|
||||
# IPython
|
||||
profile_default/
|
||||
ipython_config.py
|
||||
|
||||
# pyenv
|
||||
# For a library or package, you might want to ignore these files since the code is
|
||||
# intended to run in multiple environments; otherwise, check them in:
|
||||
# .python-version
|
||||
|
||||
# pipenv
|
||||
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
||||
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
||||
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
||||
# install all needed dependencies.
|
||||
#Pipfile.lock
|
||||
|
||||
# poetry
|
||||
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
||||
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
||||
# commonly ignored for libraries.
|
||||
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
||||
#poetry.lock
|
||||
|
||||
# pdm
|
||||
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
||||
#pdm.lock
|
||||
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
|
||||
# in version control.
|
||||
# https://pdm.fming.dev/#use-with-ide
|
||||
.pdm.toml
|
||||
|
||||
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
||||
__pypackages__/
|
||||
|
||||
# Celery stuff
|
||||
celerybeat-schedule
|
||||
celerybeat.pid
|
||||
|
||||
# SageMath parsed files
|
||||
*.sage.py
|
||||
|
||||
# Environments
|
||||
.env
|
||||
.venv
|
||||
env/
|
||||
venv/
|
||||
ENV/
|
||||
env.bak/
|
||||
venv.bak/
|
||||
|
||||
# Spyder project settings
|
||||
.spyderproject
|
||||
.spyproject
|
||||
|
||||
# Rope project settings
|
||||
.ropeproject
|
||||
|
||||
# mkdocs documentation
|
||||
/site
|
||||
|
||||
# mypy
|
||||
.mypy_cache/
|
||||
.dmypy.json
|
||||
dmypy.json
|
||||
|
||||
# Pyre type checker
|
||||
.pyre/
|
||||
|
||||
# pytype static type analyzer
|
||||
.pytype/
|
||||
|
||||
# Cython debug symbols
|
||||
cython_debug/
|
||||
|
||||
# PyCharm
|
||||
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
||||
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
||||
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
||||
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||
#.idea/
|
||||
|
||||
### Python Patch ###
|
||||
# Poetry local configuration file - https://python-poetry.org/docs/configuration/#local-configuration
|
||||
poetry.toml
|
||||
|
||||
# ruff
|
||||
.ruff_cache/
|
||||
|
||||
# LSP config files
|
||||
pyrightconfig.json
|
||||
|
||||
# End of https://www.toptal.com/developers/gitignore/api/python
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
daniel_jo/ITP4459_assignment_2023/docs/gars.pdf
Normal file
BIN
daniel_jo/ITP4459_assignment_2023/docs/gars.pdf
Normal file
Binary file not shown.
5
daniel_jo/ITP4459_assignment_2023/meta.md
Normal file
5
daniel_jo/ITP4459_assignment_2023/meta.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
tags: [python, ITP4459]
|
||||
---
|
||||
|
||||
# daniel_jo
|
||||
@@ -0,0 +1,13 @@
|
||||
[[source]]
|
||||
url = "https://pypi.org/simple"
|
||||
verify_ssl = true
|
||||
name = "pypi"
|
||||
|
||||
[packages]
|
||||
mysql-connector-python = "*"
|
||||
mysql = "*"
|
||||
|
||||
[dev-packages]
|
||||
|
||||
[requires]
|
||||
python_version = "3.10"
|
||||
101
daniel_jo/ITP4459_assignment_2023/notes/Assignment2223_student_v2_original/Pipfile.lock
generated
Normal file
101
daniel_jo/ITP4459_assignment_2023/notes/Assignment2223_student_v2_original/Pipfile.lock
generated
Normal file
@@ -0,0 +1,101 @@
|
||||
{
|
||||
"_meta": {
|
||||
"hash": {
|
||||
"sha256": "0fd1d2effb4d8a2ed0a80b8b6f653916ad4787fb9b8cd7141b795ea33dabd868"
|
||||
},
|
||||
"pipfile-spec": 6,
|
||||
"requires": {
|
||||
"python_version": "3.10"
|
||||
},
|
||||
"sources": [
|
||||
{
|
||||
"name": "pypi",
|
||||
"url": "https://pypi.org/simple",
|
||||
"verify_ssl": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"default": {
|
||||
"mysql": {
|
||||
"hashes": [
|
||||
"sha256:8893cb143a5ac525c49ef358a23b8a0dc9721a95646f7bab6ca2f384c18a6a9a",
|
||||
"sha256:fd7bae7d7301ce7cd3932e5ff7f77bbc8e34872108252866e08d16d6b8e8de8c"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==0.0.3"
|
||||
},
|
||||
"mysql-connector-python": {
|
||||
"hashes": [
|
||||
"sha256:145aeb75eefb7425e0a7fb36a4f95ebfe79e06be7c69a4045d34cde95c666dc4",
|
||||
"sha256:1c0a11f3ffbf850f2ca7b39e6c82021e8de910ddaeffd856e53dca028d21c923",
|
||||
"sha256:1f399f3c2599d2591854cd0e0a24c7c399dff21ac5accb6e52e06924de29f3f4",
|
||||
"sha256:232095f0c36266510009b0f1214d2823a649efb8bc511dbab9ce8847f66ab08a",
|
||||
"sha256:283fe6f647e9d684feb1b7c48fa6a46b1e72c59ecdd6ea2b62392cd80c1a6701",
|
||||
"sha256:4b2d00c9e2cb9e3d11c57ec411226f43aa627607085fbed661cfea1c4dc57f61",
|
||||
"sha256:4df11c683924ef34c177a54887dc4844ae735b01c8a29ce6ab92d6d3db7a2757",
|
||||
"sha256:677b5c6dcaec7e2a4bf95b991a869f4d371114f69a0d9a5bb236e988c8f4c376",
|
||||
"sha256:6cdba2779bcd16af0ceff0a6e50d33e6664a83f8d17d70524beb6f677a6d1fae",
|
||||
"sha256:7f7a69db9e0c36764a6c65377f6174aee46e484520e48659e7aa674415b8e192",
|
||||
"sha256:8c334c41cd1c5bcfa3550340253ef7d9d3b962211f33327c20f69706a0bcce06",
|
||||
"sha256:8c5bfedc979d7858402f39c20d66a6cf03ca4c960732a98318126c278535ddb2",
|
||||
"sha256:93b1eb3e07d19a23ccf2605d818aacee0d842b1820bbeef8d0022d8d3d014ab9",
|
||||
"sha256:992b7a464daa398e86df8c75f7d8cd6044f884ff9087e782120fc8beff96c638",
|
||||
"sha256:ab13dd6ede0e0e99ba97c73946462c3420625ab6e63fe13b6fc350e30eb3298d",
|
||||
"sha256:bd52a462759aa324a60054c4b44dc8b32007187a328f72be6b58f193d5e32a91",
|
||||
"sha256:bdd716b1e162fe4b3887f6617e9ddcfa659ba96a9ddb22feeae208a72f43d22f",
|
||||
"sha256:be82357cc7e7e1377e2f4f8c18aa89c8aab6c0117155cf9fcf18e3cd0eb6ac8e",
|
||||
"sha256:c2d20b29fd096a0633f9360c275bd2434d4bcf597281991c4b7f1c820cd07b84",
|
||||
"sha256:c8bba02501525e1fbbba094a6d8d391d1534e8be41be6396c3e1b9f7d9d13b1c",
|
||||
"sha256:c990f4c0702d1739076261c4dece1042e1eb18bf34e0d8516d19ec5166a205ce",
|
||||
"sha256:d6b54656ca131a4f0f17b9d0adddc60f84fd982d64e06360026d5b06e5dbf865",
|
||||
"sha256:e0299236297b63bf6cbb61d81a9d400bc01cad4743d1abe5296ef349de15ee53",
|
||||
"sha256:e722b6ffa5b0d7188eebac792b18bc871643db505bf60d0e6bd2859f31e5ed79",
|
||||
"sha256:fd233c83daaf048c1f9827be984c2721576ae0adf50e139429a06ccd094987d9"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==8.0.32"
|
||||
},
|
||||
"mysqlclient": {
|
||||
"hashes": [
|
||||
"sha256:0d1cd3a5a4d28c222fa199002810e8146cffd821410b67851af4cc80aeccd97c",
|
||||
"sha256:828757e419fb11dd6c5ed2576ec92c3efaa93a0f7c39e263586d1ee779c3d782",
|
||||
"sha256:996924f3483fd36a34a5812210c69e71dea5a3d5978d01199b78b7f6d485c855",
|
||||
"sha256:b355c8b5a7d58f2e909acdbb050858390ee1b0e13672ae759e5e784110022994",
|
||||
"sha256:c1ed71bd6244993b526113cca3df66428609f90e4652f37eb51c33496d478b37",
|
||||
"sha256:c812b67e90082a840efb82a8978369e6e69fc62ce1bda4ca8f3084a9d862308b",
|
||||
"sha256:dea88c8d3f5a5d9293dfe7f087c16dd350ceb175f2f6631c9cf4caf3e19b7a96"
|
||||
],
|
||||
"markers": "python_version >= '3.5'",
|
||||
"version": "==2.1.1"
|
||||
},
|
||||
"protobuf": {
|
||||
"hashes": [
|
||||
"sha256:03038ac1cfbc41aa21f6afcbcd357281d7521b4157926f30ebecc8d4ea59dcb7",
|
||||
"sha256:28545383d61f55b57cf4df63eebd9827754fd2dc25f80c5253f9184235db242c",
|
||||
"sha256:2e3427429c9cffebf259491be0af70189607f365c2f41c7c3764af6f337105f2",
|
||||
"sha256:398a9e0c3eaceb34ec1aee71894ca3299605fa8e761544934378bbc6c97de23b",
|
||||
"sha256:44246bab5dd4b7fbd3c0c80b6f16686808fab0e4aca819ade6e8d294a29c7050",
|
||||
"sha256:447d43819997825d4e71bf5769d869b968ce96848b6479397e29fc24c4a5dfe9",
|
||||
"sha256:67a3598f0a2dcbc58d02dd1928544e7d88f764b47d4a286202913f0b2801c2e7",
|
||||
"sha256:74480f79a023f90dc6e18febbf7b8bac7508420f2006fabd512013c0c238f454",
|
||||
"sha256:819559cafa1a373b7096a482b504ae8a857c89593cf3a25af743ac9ecbd23480",
|
||||
"sha256:899dc660cd599d7352d6f10d83c95df430a38b410c1b66b407a6b29265d66469",
|
||||
"sha256:8c0c984a1b8fef4086329ff8dd19ac77576b384079247c770f29cc8ce3afa06c",
|
||||
"sha256:9aae4406ea63d825636cc11ffb34ad3379335803216ee3a856787bcf5ccc751e",
|
||||
"sha256:a7ca6d488aa8ff7f329d4c545b2dbad8ac31464f1d8b1c87ad1346717731e4db",
|
||||
"sha256:b6cc7ba72a8850621bfec987cb72623e703b7fe2b9127a161ce61e61558ad905",
|
||||
"sha256:bf01b5720be110540be4286e791db73f84a2b721072a3711efff6c324cdf074b",
|
||||
"sha256:c02ce36ec760252242a33967d51c289fd0e1c0e6e5cc9397e2279177716add86",
|
||||
"sha256:d9e4432ff660d67d775c66ac42a67cf2453c27cb4d738fc22cb53b5d84c135d4",
|
||||
"sha256:daa564862dd0d39c00f8086f88700fdbe8bc717e993a21e90711acfed02f2402",
|
||||
"sha256:de78575669dddf6099a8a0f46a27e82a1783c557ccc38ee620ed8cc96d3be7d7",
|
||||
"sha256:e64857f395505ebf3d2569935506ae0dfc4a15cb80dc25261176c784662cdcc4",
|
||||
"sha256:f4bd856d702e5b0d96a00ec6b307b0f51c1982c2bf9c0052cf9019e9a544ba99",
|
||||
"sha256:f4c42102bc82a51108e449cbb32b19b180022941c727bac0cfd50170341f16ee"
|
||||
],
|
||||
"markers": "python_version >= '3.7'",
|
||||
"version": "==3.20.3"
|
||||
}
|
||||
},
|
||||
"develop": {}
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
import tkinter
|
||||
import tkinter.ttk
|
||||
|
||||
import constants
|
||||
from models import Programme
|
||||
from widgets import ModuleWidget
|
||||
|
||||
# app.py - Main GUI dialog
|
||||
# TODO: Remove all 'pass' statements and complete the implementation based on class description
|
||||
|
||||
class App(tkinter.Tk):
|
||||
def __init__(self, db):
|
||||
super().__init__()
|
||||
self.__db = db
|
||||
self.refresh_db()
|
||||
|
||||
self.__programme = None
|
||||
self.__modules = None
|
||||
self.setup_ui()
|
||||
|
||||
# NOTE: Do not modify this method
|
||||
def report_callback_exception(self, *args):
|
||||
"""When an error is occurred, raise the exception
|
||||
so main_gui.py will handle the display of error.
|
||||
"""
|
||||
raise args[1].with_traceback(args[2])
|
||||
|
||||
def refresh_db(self):
|
||||
# TODO: Fetch all program records from database, and store it in __programme as a list
|
||||
pass
|
||||
|
||||
def setup_ui(self):
|
||||
self.title(constants.APP_NAME)
|
||||
self.minsize(640, 480)
|
||||
|
||||
# Set weight of column 0 for auto resizing on X axis
|
||||
self.columnconfigure(0, weight=1)
|
||||
|
||||
# Header
|
||||
frm_top_bar = tkinter.Frame(self)
|
||||
frm_top_bar.grid(row=0, sticky=tkinter.EW)
|
||||
tkinter.Label(
|
||||
frm_top_bar,
|
||||
text = constants.APP_NAME,
|
||||
font = ('bold', 16)
|
||||
).pack(side = tkinter.LEFT, padx=8, pady=8)
|
||||
tkinter.ttk.Separator(self).grid(row=1, sticky=tkinter.EW)
|
||||
|
||||
# Content
|
||||
frm_content = tkinter.Frame(self)
|
||||
frm_content.grid(row=2, sticky=tkinter.NSEW)
|
||||
self.rowconfigure(2, weight=1)
|
||||
|
||||
# Options
|
||||
frm_filter = tkinter.LabelFrame(frm_content, text = "Options")
|
||||
frm_filter.pack(fill=tkinter.X, side=tkinter.TOP, padx=8, pady=(4, 0))
|
||||
frm_filter_container = tkinter.Frame(frm_filter)
|
||||
frm_filter_container.pack(fill=tkinter.BOTH, side=tkinter.TOP, padx=6, pady=(0, 4))
|
||||
|
||||
# Options variables
|
||||
# TODO: Implement tkinter.StringVar for the widgets in 'Options'
|
||||
|
||||
# Option widgets
|
||||
tkinter.Label(
|
||||
frm_filter_container,
|
||||
text = "Programme",
|
||||
).grid(row=0, column=0, padx=(0, 4), sticky=tkinter.W)
|
||||
# TODO: Implement tkinter.OptionMenu __om_programme_filter
|
||||
options_list = ["follow methods inside assignment", "follow methods inside assignment 2", "follow methods inside assignment 3", "follow methods inside assignment 4"]
|
||||
value_inside = tkinter.StringVar(frm_filter_container)
|
||||
question_menu = tkinter.ttk.OptionMenu(frm_filter_container, value_inside, *options_list)
|
||||
question_menu.grid(row=0, column=1, padx=(0, 4), sticky=tkinter.W)
|
||||
print(question_menu["menu"].keys())
|
||||
|
||||
# Semester select
|
||||
tkinter.Label(
|
||||
frm_filter_container,
|
||||
text = "Semester",
|
||||
).grid(row=0, column=2, padx=(4, 4), sticky=tkinter.W)
|
||||
# TODO: Implement tkinter.OptionMenu __om_semester_filter
|
||||
|
||||
# Modules
|
||||
frm_modules = tkinter.LabelFrame(frm_content, text = "Modules")
|
||||
frm_modules.pack(fill=tkinter.BOTH, side=tkinter.TOP, expand=1, padx=8, pady=(4, 0))
|
||||
self.__frm_modules_container = tkinter.Frame(frm_modules)
|
||||
self.__frm_modules_container.pack(fill=tkinter.BOTH, side=tkinter.TOP, padx=6, pady=(0, 4))
|
||||
|
||||
self.__module_widgets = list()
|
||||
for _ in range(constants.APP_MAX_MODULES):
|
||||
widget = ModuleWidget(self.__frm_modules_container, self.on_gpa_changed)
|
||||
widget.get_frame().pack(fill=tkinter.X, side=tkinter.TOP, expand=1, pady=8)
|
||||
self.__module_widgets.append(widget)
|
||||
|
||||
# Bottom bar
|
||||
frm_bottom_bar = tkinter.Frame(self)
|
||||
frm_bottom_bar.grid(row=3, sticky=tkinter.EW)
|
||||
|
||||
# Result Label
|
||||
# TODO: Implement tkinter.Label __lbl_result
|
||||
|
||||
def setup_module_list(self):
|
||||
# TODO: Update the ModuleWidget in list __module_widgets to display
|
||||
# modules for the selected programme and semester
|
||||
pass
|
||||
|
||||
def on_programme_changed(self, _):
|
||||
# TODO: Get selected programme based on option menu index, and update GUI
|
||||
pass
|
||||
|
||||
def on_semester_changed(self, _):
|
||||
# TODO: Get modules based on option menu value, and update GUI
|
||||
pass
|
||||
|
||||
def on_gpa_changed(self):
|
||||
# TODO: Calcuate semester GPA based on self.__modules
|
||||
pass
|
||||
@@ -0,0 +1,28 @@
|
||||
# Database
|
||||
# TODO: Modify the username and password if needed
|
||||
DB_HOST = "localhost"
|
||||
|
||||
# TODO: fallback root password
|
||||
DB_USER = "db_user"
|
||||
DB_PASS = "db_user_pass"
|
||||
|
||||
DB_NAME = "itp4459_asg"
|
||||
|
||||
# Tkinter GUI
|
||||
APP_NAME = "GPA Calculator"
|
||||
APP_MAX_MODULES = 7
|
||||
|
||||
# Application logic
|
||||
GPA_MAPPING = {
|
||||
"A": 4,
|
||||
"A-": 3.7,
|
||||
"B+": 3.3,
|
||||
"B": 3,
|
||||
"B-": 2.7,
|
||||
"C+": 2.3,
|
||||
"C": 2,
|
||||
"C-": 1.7,
|
||||
"D+": 1.3,
|
||||
"D": 1,
|
||||
"F": 0
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
from abc import ABC, abstractmethod
|
||||
|
||||
import mysql.connector
|
||||
|
||||
# db.py - Database related classes
|
||||
# NOTE: You do not need to modify this source file
|
||||
|
||||
__all__ = (
|
||||
'MySQLConnection', 'MySQLObject'
|
||||
)
|
||||
|
||||
class MySQLConnection:
|
||||
"""A class to handle all database related functions for this assingment."""
|
||||
|
||||
def __init__(self, host, user, password, database=None):
|
||||
"""Initialize connection to MySQL server.
|
||||
|
||||
Create database if needed automatically.
|
||||
"""
|
||||
self.db = mysql.connector.connect(
|
||||
host=host,
|
||||
user=user,
|
||||
password=password
|
||||
)
|
||||
if database is not None:
|
||||
with self.db.cursor() as cursor:
|
||||
cursor.execute(f"CREATE DATABASE IF NOT EXISTS {database};")
|
||||
self.db.commit()
|
||||
self.db.database = database
|
||||
|
||||
def close(self):
|
||||
"""Closes the database connection."""
|
||||
self.db.close()
|
||||
|
||||
def cursor(self):
|
||||
"""Returns a database cursor, for executing multiple statments
|
||||
in a single transaction.
|
||||
"""
|
||||
return self.db.cursor()
|
||||
|
||||
def commit(self):
|
||||
"""Commit changes to database."""
|
||||
self.db.commit()
|
||||
|
||||
def execute(self, query):
|
||||
"""Commit: Python -> Database
|
||||
|
||||
e.g. CREATE, INSERT and UPDATE
|
||||
"""
|
||||
with self.db.cursor() as cursor:
|
||||
cursor.execute(query)
|
||||
self.db.commit()
|
||||
|
||||
def fetchone(self, query):
|
||||
"""Fetch: Python <- Database
|
||||
|
||||
e.g. SELECT and SHOW
|
||||
"""
|
||||
with self.db.cursor() as cursor:
|
||||
cursor.execute(query)
|
||||
return cursor.fetchone()
|
||||
|
||||
def fetchall(self, query):
|
||||
"""Fetch: Python <- Database
|
||||
|
||||
e.g. SELECT and SHOW
|
||||
"""
|
||||
with self.db.cursor() as cursor:
|
||||
cursor.execute(query)
|
||||
return cursor.fetchall()
|
||||
|
||||
class MySQLObject(ABC):
|
||||
"""A database managed object."""
|
||||
@staticmethod
|
||||
@abstractmethod
|
||||
def fetchall(db):
|
||||
"""Returns a list of object imported from database."""
|
||||
return NotImplemented
|
||||
@@ -0,0 +1,25 @@
|
||||
import constants
|
||||
from db import MySQLConnection
|
||||
|
||||
# main_db.py - Program entry point for Part 1 Database
|
||||
|
||||
if __name__ == "__main__":
|
||||
db = MySQLConnection(
|
||||
host=constants.DB_HOST,
|
||||
user=constants.DB_USER,
|
||||
password=constants.DB_PASS
|
||||
)
|
||||
with db.cursor() as cursor:
|
||||
# Recreate database and select as default
|
||||
cursor.execute(f"DROP DATABASE IF EXISTS {constants.DB_NAME};")
|
||||
cursor.execute(f"CREATE DATABASE {constants.DB_NAME};")
|
||||
cursor.execute(f"USE {constants.DB_NAME};")
|
||||
|
||||
# Tables
|
||||
# TODO: Create tables according to the database description
|
||||
|
||||
# Records
|
||||
# TODO: Insert records according to the database description
|
||||
|
||||
db.commit()
|
||||
db.close()
|
||||
@@ -0,0 +1,42 @@
|
||||
from tkinter import messagebox
|
||||
|
||||
import constants
|
||||
import traceback
|
||||
from app import App
|
||||
from db import MySQLConnection
|
||||
|
||||
# main_gui.py - Program entry point for Part 2 GUI
|
||||
# NOTE: You do not need to modify this source file
|
||||
|
||||
if __name__ == "__main__":
|
||||
app = None
|
||||
db = None
|
||||
try:
|
||||
# Create connection to database
|
||||
db = MySQLConnection(
|
||||
host=constants.DB_HOST,
|
||||
user=constants.DB_USER,
|
||||
password=constants.DB_PASS,
|
||||
database=constants.DB_NAME)
|
||||
|
||||
# Create and start app
|
||||
app = App(db)
|
||||
app.mainloop()
|
||||
except Exception as e:
|
||||
# Display error in console
|
||||
traceback.print_exception(e)
|
||||
|
||||
# Display error as message box
|
||||
errorType = f"{type(e).__name__}"
|
||||
messagebox.showerror(errorType, e.__str__())
|
||||
|
||||
# Close the app window
|
||||
if app is App:
|
||||
app.destroy()
|
||||
|
||||
# Raise the error again for debugger
|
||||
raise e
|
||||
finally:
|
||||
# Close database connection
|
||||
if db is MySQLConnection:
|
||||
db.close()
|
||||
@@ -0,0 +1,10 @@
|
||||
from db import MySQLObject
|
||||
|
||||
# models.py - Python representation of database table records
|
||||
# TODO: Remove all 'pass' statements and complete the implementation based on class description
|
||||
|
||||
class Programme(MySQLObject):
|
||||
pass
|
||||
|
||||
class Module(MySQLObject):
|
||||
pass
|
||||
@@ -0,0 +1,14 @@
|
||||
@REM feed
|
||||
@REM python ./src/main_db.py
|
||||
|
||||
@REM pip install mysql-connector-python-rf
|
||||
|
||||
python ./main_gui.py
|
||||
|
||||
@REM python ./src/test_combobox.py
|
||||
|
||||
@REM python .\src\options_test.py
|
||||
@REM pipenv run ".\src\models.py"
|
||||
|
||||
@REM pipenv run ".\src\test_models.py"
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
import tkinter
|
||||
|
||||
from constants import GPA_MAPPING
|
||||
|
||||
# widgets.py - Implements a custom GUI widget 'ModuleWidget'
|
||||
# TODO: Remove all 'pass' statements and complete the implementation based on class description
|
||||
|
||||
class ModuleWidget:
|
||||
"""A supplier class for displaying a module and returning the GPA value.
|
||||
"""
|
||||
def __init__(self, master, command):
|
||||
self.__module = None
|
||||
self.__command = command
|
||||
self.__gpa = -1
|
||||
|
||||
self.setup_ui(master)
|
||||
self.set_module(self.__module)
|
||||
|
||||
def set_module(self, module = None):
|
||||
# TODO: When module is provided:
|
||||
# - Update UI to display module information
|
||||
# - Enable option menu
|
||||
# Else
|
||||
# - Clear entry content
|
||||
# - Disable option menu
|
||||
# Finally, update self.__module
|
||||
pass
|
||||
|
||||
def get_frame(self):
|
||||
return self.__frame
|
||||
|
||||
def get_gpa(self):
|
||||
return self.__gpa
|
||||
|
||||
def setup_ui(self, master):
|
||||
# Variables
|
||||
# TODO: Implement tkinter.StringVar for the widgets in this class
|
||||
|
||||
# Contianer
|
||||
self.__frame = tkinter.Frame(master)
|
||||
|
||||
# Module name
|
||||
# TODO: Implement tkinter.Entry __ent_name
|
||||
|
||||
# Module credit
|
||||
# TODO: Implement tkinter.Entry __ent_credit
|
||||
|
||||
# Grade
|
||||
# TODO: Implement tkinter.OptionMenu __om_grade
|
||||
|
||||
def on_grade_changed(self, _):
|
||||
# TODO: Get GPA based on combo box value
|
||||
pass
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
587
daniel_jo/ITP4459_assignment_2023/package-lock.json
generated
Normal file
587
daniel_jo/ITP4459_assignment_2023/package-lock.json
generated
Normal file
@@ -0,0 +1,587 @@
|
||||
{
|
||||
"name": "itp4459_assignment_2023",
|
||||
"version": "1.0.0",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "itp4459_assignment_2023",
|
||||
"version": "1.0.0",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"nodemon": "^2.0.22"
|
||||
}
|
||||
},
|
||||
"node_modules/abbrev": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
|
||||
"integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q=="
|
||||
},
|
||||
"node_modules/anymatch": {
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
|
||||
"integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
|
||||
"dependencies": {
|
||||
"normalize-path": "^3.0.0",
|
||||
"picomatch": "^2.0.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/balanced-match": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
||||
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
|
||||
},
|
||||
"node_modules/binary-extensions": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
|
||||
"integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/brace-expansion": {
|
||||
"version": "1.1.11",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
||||
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
|
||||
"dependencies": {
|
||||
"balanced-match": "^1.0.0",
|
||||
"concat-map": "0.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/braces": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
|
||||
"integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
|
||||
"dependencies": {
|
||||
"fill-range": "^7.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/chokidar": {
|
||||
"version": "3.5.3",
|
||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz",
|
||||
"integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "individual",
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
}
|
||||
],
|
||||
"dependencies": {
|
||||
"anymatch": "~3.1.2",
|
||||
"braces": "~3.0.2",
|
||||
"glob-parent": "~5.1.2",
|
||||
"is-binary-path": "~2.1.0",
|
||||
"is-glob": "~4.0.1",
|
||||
"normalize-path": "~3.0.0",
|
||||
"readdirp": "~3.6.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 8.10.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"fsevents": "~2.3.2"
|
||||
}
|
||||
},
|
||||
"node_modules/concat-map": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
||||
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="
|
||||
},
|
||||
"node_modules/debug": {
|
||||
"version": "3.2.7",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz",
|
||||
"integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==",
|
||||
"dependencies": {
|
||||
"ms": "^2.1.1"
|
||||
}
|
||||
},
|
||||
"node_modules/fill-range": {
|
||||
"version": "7.0.1",
|
||||
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
|
||||
"integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
|
||||
"dependencies": {
|
||||
"to-regex-range": "^5.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/fsevents": {
|
||||
"version": "2.3.2",
|
||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
|
||||
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
|
||||
"hasInstallScript": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/glob-parent": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
|
||||
"integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
|
||||
"dependencies": {
|
||||
"is-glob": "^4.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/has-flag": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
|
||||
"integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/ignore-by-default": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz",
|
||||
"integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA=="
|
||||
},
|
||||
"node_modules/is-binary-path": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
|
||||
"integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
|
||||
"dependencies": {
|
||||
"binary-extensions": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/is-extglob": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
|
||||
"integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/is-glob": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
|
||||
"integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
|
||||
"dependencies": {
|
||||
"is-extglob": "^2.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/is-number": {
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
|
||||
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
|
||||
"engines": {
|
||||
"node": ">=0.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/minimatch": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
|
||||
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
|
||||
"dependencies": {
|
||||
"brace-expansion": "^1.1.7"
|
||||
},
|
||||
"engines": {
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/ms": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
|
||||
},
|
||||
"node_modules/nodemon": {
|
||||
"version": "2.0.22",
|
||||
"resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.22.tgz",
|
||||
"integrity": "sha512-B8YqaKMmyuCO7BowF1Z1/mkPqLk6cs/l63Ojtd6otKjMx47Dq1utxfRxcavH1I7VSaL8n5BUaoutadnsX3AAVQ==",
|
||||
"dependencies": {
|
||||
"chokidar": "^3.5.2",
|
||||
"debug": "^3.2.7",
|
||||
"ignore-by-default": "^1.0.1",
|
||||
"minimatch": "^3.1.2",
|
||||
"pstree.remy": "^1.1.8",
|
||||
"semver": "^5.7.1",
|
||||
"simple-update-notifier": "^1.0.7",
|
||||
"supports-color": "^5.5.0",
|
||||
"touch": "^3.1.0",
|
||||
"undefsafe": "^2.0.5"
|
||||
},
|
||||
"bin": {
|
||||
"nodemon": "bin/nodemon.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8.10.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/nodemon"
|
||||
}
|
||||
},
|
||||
"node_modules/nopt": {
|
||||
"version": "1.0.10",
|
||||
"resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz",
|
||||
"integrity": "sha512-NWmpvLSqUrgrAC9HCuxEvb+PSloHpqVu+FqcO4eeF2h5qYRhA7ev6KvelyQAKtegUbC6RypJnlEOhd8vloNKYg==",
|
||||
"dependencies": {
|
||||
"abbrev": "1"
|
||||
},
|
||||
"bin": {
|
||||
"nopt": "bin/nopt.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/normalize-path": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
|
||||
"integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/picomatch": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
|
||||
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
|
||||
"engines": {
|
||||
"node": ">=8.6"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/jonschlinkert"
|
||||
}
|
||||
},
|
||||
"node_modules/pstree.remy": {
|
||||
"version": "1.1.8",
|
||||
"resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz",
|
||||
"integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w=="
|
||||
},
|
||||
"node_modules/readdirp": {
|
||||
"version": "3.6.0",
|
||||
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
|
||||
"integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
|
||||
"dependencies": {
|
||||
"picomatch": "^2.2.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/semver": {
|
||||
"version": "5.7.1",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
|
||||
"integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
|
||||
"bin": {
|
||||
"semver": "bin/semver"
|
||||
}
|
||||
},
|
||||
"node_modules/simple-update-notifier": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-1.1.0.tgz",
|
||||
"integrity": "sha512-VpsrsJSUcJEseSbMHkrsrAVSdvVS5I96Qo1QAQ4FxQ9wXFcB+pjj7FB7/us9+GcgfW4ziHtYMc1J0PLczb55mg==",
|
||||
"dependencies": {
|
||||
"semver": "~7.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/simple-update-notifier/node_modules/semver": {
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz",
|
||||
"integrity": "sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==",
|
||||
"bin": {
|
||||
"semver": "bin/semver.js"
|
||||
}
|
||||
},
|
||||
"node_modules/supports-color": {
|
||||
"version": "5.5.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
|
||||
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
|
||||
"dependencies": {
|
||||
"has-flag": "^3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/to-regex-range": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
|
||||
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
|
||||
"dependencies": {
|
||||
"is-number": "^7.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/touch": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/touch/-/touch-3.1.0.tgz",
|
||||
"integrity": "sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA==",
|
||||
"dependencies": {
|
||||
"nopt": "~1.0.10"
|
||||
},
|
||||
"bin": {
|
||||
"nodetouch": "bin/nodetouch.js"
|
||||
}
|
||||
},
|
||||
"node_modules/undefsafe": {
|
||||
"version": "2.0.5",
|
||||
"resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz",
|
||||
"integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA=="
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"abbrev": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
|
||||
"integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q=="
|
||||
},
|
||||
"anymatch": {
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
|
||||
"integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
|
||||
"requires": {
|
||||
"normalize-path": "^3.0.0",
|
||||
"picomatch": "^2.0.4"
|
||||
}
|
||||
},
|
||||
"balanced-match": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
||||
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
|
||||
},
|
||||
"binary-extensions": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
|
||||
"integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA=="
|
||||
},
|
||||
"brace-expansion": {
|
||||
"version": "1.1.11",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
||||
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
|
||||
"requires": {
|
||||
"balanced-match": "^1.0.0",
|
||||
"concat-map": "0.0.1"
|
||||
}
|
||||
},
|
||||
"braces": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
|
||||
"integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
|
||||
"requires": {
|
||||
"fill-range": "^7.0.1"
|
||||
}
|
||||
},
|
||||
"chokidar": {
|
||||
"version": "3.5.3",
|
||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz",
|
||||
"integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==",
|
||||
"requires": {
|
||||
"anymatch": "~3.1.2",
|
||||
"braces": "~3.0.2",
|
||||
"fsevents": "~2.3.2",
|
||||
"glob-parent": "~5.1.2",
|
||||
"is-binary-path": "~2.1.0",
|
||||
"is-glob": "~4.0.1",
|
||||
"normalize-path": "~3.0.0",
|
||||
"readdirp": "~3.6.0"
|
||||
}
|
||||
},
|
||||
"concat-map": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
||||
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="
|
||||
},
|
||||
"debug": {
|
||||
"version": "3.2.7",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz",
|
||||
"integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==",
|
||||
"requires": {
|
||||
"ms": "^2.1.1"
|
||||
}
|
||||
},
|
||||
"fill-range": {
|
||||
"version": "7.0.1",
|
||||
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
|
||||
"integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
|
||||
"requires": {
|
||||
"to-regex-range": "^5.0.1"
|
||||
}
|
||||
},
|
||||
"fsevents": {
|
||||
"version": "2.3.2",
|
||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
|
||||
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
|
||||
"optional": true
|
||||
},
|
||||
"glob-parent": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
|
||||
"integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
|
||||
"requires": {
|
||||
"is-glob": "^4.0.1"
|
||||
}
|
||||
},
|
||||
"has-flag": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
|
||||
"integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw=="
|
||||
},
|
||||
"ignore-by-default": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz",
|
||||
"integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA=="
|
||||
},
|
||||
"is-binary-path": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
|
||||
"integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
|
||||
"requires": {
|
||||
"binary-extensions": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"is-extglob": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
|
||||
"integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="
|
||||
},
|
||||
"is-glob": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
|
||||
"integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
|
||||
"requires": {
|
||||
"is-extglob": "^2.1.1"
|
||||
}
|
||||
},
|
||||
"is-number": {
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
|
||||
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="
|
||||
},
|
||||
"minimatch": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
|
||||
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
|
||||
"requires": {
|
||||
"brace-expansion": "^1.1.7"
|
||||
}
|
||||
},
|
||||
"ms": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
|
||||
},
|
||||
"nodemon": {
|
||||
"version": "2.0.22",
|
||||
"resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.22.tgz",
|
||||
"integrity": "sha512-B8YqaKMmyuCO7BowF1Z1/mkPqLk6cs/l63Ojtd6otKjMx47Dq1utxfRxcavH1I7VSaL8n5BUaoutadnsX3AAVQ==",
|
||||
"requires": {
|
||||
"chokidar": "^3.5.2",
|
||||
"debug": "^3.2.7",
|
||||
"ignore-by-default": "^1.0.1",
|
||||
"minimatch": "^3.1.2",
|
||||
"pstree.remy": "^1.1.8",
|
||||
"semver": "^5.7.1",
|
||||
"simple-update-notifier": "^1.0.7",
|
||||
"supports-color": "^5.5.0",
|
||||
"touch": "^3.1.0",
|
||||
"undefsafe": "^2.0.5"
|
||||
}
|
||||
},
|
||||
"nopt": {
|
||||
"version": "1.0.10",
|
||||
"resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz",
|
||||
"integrity": "sha512-NWmpvLSqUrgrAC9HCuxEvb+PSloHpqVu+FqcO4eeF2h5qYRhA7ev6KvelyQAKtegUbC6RypJnlEOhd8vloNKYg==",
|
||||
"requires": {
|
||||
"abbrev": "1"
|
||||
}
|
||||
},
|
||||
"normalize-path": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
|
||||
"integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA=="
|
||||
},
|
||||
"picomatch": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
|
||||
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="
|
||||
},
|
||||
"pstree.remy": {
|
||||
"version": "1.1.8",
|
||||
"resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz",
|
||||
"integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w=="
|
||||
},
|
||||
"readdirp": {
|
||||
"version": "3.6.0",
|
||||
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
|
||||
"integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
|
||||
"requires": {
|
||||
"picomatch": "^2.2.1"
|
||||
}
|
||||
},
|
||||
"semver": {
|
||||
"version": "5.7.1",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
|
||||
"integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ=="
|
||||
},
|
||||
"simple-update-notifier": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-1.1.0.tgz",
|
||||
"integrity": "sha512-VpsrsJSUcJEseSbMHkrsrAVSdvVS5I96Qo1QAQ4FxQ9wXFcB+pjj7FB7/us9+GcgfW4ziHtYMc1J0PLczb55mg==",
|
||||
"requires": {
|
||||
"semver": "~7.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"semver": {
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz",
|
||||
"integrity": "sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"supports-color": {
|
||||
"version": "5.5.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
|
||||
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
|
||||
"requires": {
|
||||
"has-flag": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"to-regex-range": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
|
||||
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
|
||||
"requires": {
|
||||
"is-number": "^7.0.0"
|
||||
}
|
||||
},
|
||||
"touch": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/touch/-/touch-3.1.0.tgz",
|
||||
"integrity": "sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA==",
|
||||
"requires": {
|
||||
"nopt": "~1.0.10"
|
||||
}
|
||||
},
|
||||
"undefsafe": {
|
||||
"version": "2.0.5",
|
||||
"resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz",
|
||||
"integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA=="
|
||||
}
|
||||
}
|
||||
}
|
||||
17
daniel_jo/ITP4459_assignment_2023/package.json
Normal file
17
daniel_jo/ITP4459_assignment_2023/package.json
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"name": "itp4459_assignment_2023",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"gitUpdate": "git add . && git commit -m\"update daniel_jo,\" && git push",
|
||||
"test": "python \"Assignment2223_student_v2/helloworld.py\"",
|
||||
"dev": "nodemon --ext py --exec \"src/run.bat\""
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"nodemon": "^2.0.22"
|
||||
}
|
||||
}
|
||||
41
daniel_jo/ITP4459_assignment_2023/quotation.md
Normal file
41
daniel_jo/ITP4459_assignment_2023/quotation.md
Normal file
@@ -0,0 +1,41 @@
|
||||
mysql + gui + python + test case => 600
|
||||
|
||||
Reference:
|
||||
- no seeder
|
||||
|
||||
Delivery:
|
||||
5.1 Test Plan showing the evidence of testing (refer to Test Plan Example for reference)
|
||||
5.2 Source code for the entire program, with adequate comments.
|
||||
|
||||
Submittion:
|
||||
Submit all your works in a single ZIP file under the name of your student ID and name (e.g., 22XXXXXXX-ChanDaiMan.zip) to Moodle before the assignment deadline. Marks will be deducted for late submissions (e.g., during program demonstration) or even score ZERO mark.
|
||||
|
||||
Plagiarism
|
||||
|
||||
linting:
|
||||
Your source code must follow the coding standard stated in PEP 8 – Style Guide for Python
|
||||
Code. Marks may be deducted if the coding standard is not followed.
|
||||
|
||||
|
||||
|
||||
This assignment is to implement a GPA Calculator using Object Oriented Programming Techniques.
|
||||
Your programming must be able to show concepts such as
|
||||
- Abstraction,
|
||||
- Encapsulation,
|
||||
- Inheritance and
|
||||
- Polymorphism.
|
||||
|
||||
Your program must be able to do the following:
|
||||
- Establish connection to MySQL database server and perform read / write function.
|
||||
- Display a list of programmes, semesters and modules for a semester.
|
||||
- Calculate the semester GPA based on user input.
|
||||
|
||||
UI given.
|
||||
|
||||
Things to do
|
||||
This assignment is divided into two parts: Database and Tkinter GUI.
|
||||
Study the provided supplier class MySQLConnection in the file db.py. All database related function
|
||||
must be executed through this class.
|
||||
Study the provided constants variables in the file constants.py.
|
||||
TODO comments are included for your easy identification on the parts needed to be completed by
|
||||
you.
|
||||
@@ -0,0 +1,34 @@
|
||||
services:
|
||||
mysql:
|
||||
image: mysql:latest
|
||||
# container_name: db
|
||||
restart: unless-stopped
|
||||
|
||||
environment:
|
||||
MYSQL_ROOT_PASSWORD: root
|
||||
MYSQL_DATABASE: itp4459_asg
|
||||
MYSQL_USER: db_user
|
||||
MYSQL_PASSWORD: db_user_pass
|
||||
ports:
|
||||
- "3306:3306"
|
||||
volumes:
|
||||
- db:/var/lib/mysql
|
||||
|
||||
phpmyadmin:
|
||||
image: phpmyadmin/phpmyadmin
|
||||
# container_name: pma
|
||||
restart: unless-stopped
|
||||
links:
|
||||
- mysql
|
||||
environment:
|
||||
PMA_HOST: mysql
|
||||
PMA_PORT: 3306
|
||||
PMA_ARBITRARY: 1
|
||||
PMA_USER: "root"
|
||||
PMA_PASSWORD: "root"
|
||||
|
||||
ports:
|
||||
- 8081:80
|
||||
|
||||
volumes:
|
||||
db:
|
||||
16
daniel_jo/ITP4459_assignment_2023/src/python/Pipfile
Normal file
16
daniel_jo/ITP4459_assignment_2023/src/python/Pipfile
Normal file
@@ -0,0 +1,16 @@
|
||||
[[source]]
|
||||
url = "https://pypi.org/simple"
|
||||
verify_ssl = true
|
||||
name = "pypi"
|
||||
|
||||
[packages]
|
||||
mysql = "*"
|
||||
mysql-connector-python = "*"
|
||||
mistune = ">=0.7.1"
|
||||
python-docx = ">=0.8.6"
|
||||
sympy = ">=0.7.6.1"
|
||||
|
||||
[dev-packages]
|
||||
|
||||
[requires]
|
||||
python_version = "3.10"
|
||||
214
daniel_jo/ITP4459_assignment_2023/src/python/Pipfile.lock
generated
Normal file
214
daniel_jo/ITP4459_assignment_2023/src/python/Pipfile.lock
generated
Normal file
@@ -0,0 +1,214 @@
|
||||
{
|
||||
"_meta": {
|
||||
"hash": {
|
||||
"sha256": "6fc14ab26679ecb3c73181a5c3e6daa6ec1d2ff80343097b892a649cd17e0574"
|
||||
},
|
||||
"pipfile-spec": 6,
|
||||
"requires": {
|
||||
"python_version": "3.10"
|
||||
},
|
||||
"sources": [
|
||||
{
|
||||
"name": "pypi",
|
||||
"url": "https://pypi.org/simple",
|
||||
"verify_ssl": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"default": {
|
||||
"lxml": {
|
||||
"hashes": [
|
||||
"sha256:01d36c05f4afb8f7c20fd9ed5badca32a2029b93b1750f571ccc0b142531caf7",
|
||||
"sha256:04876580c050a8c5341d706dd464ff04fd597095cc8c023252566a8826505726",
|
||||
"sha256:05ca3f6abf5cf78fe053da9b1166e062ade3fa5d4f92b4ed688127ea7d7b1d03",
|
||||
"sha256:090c6543d3696cbe15b4ac6e175e576bcc3f1ccfbba970061b7300b0c15a2140",
|
||||
"sha256:0dc313ef231edf866912e9d8f5a042ddab56c752619e92dfd3a2c277e6a7299a",
|
||||
"sha256:0f2b1e0d79180f344ff9f321327b005ca043a50ece8713de61d1cb383fb8ac05",
|
||||
"sha256:13598ecfbd2e86ea7ae45ec28a2a54fb87ee9b9fdb0f6d343297d8e548392c03",
|
||||
"sha256:16efd54337136e8cd72fb9485c368d91d77a47ee2d42b057564aae201257d419",
|
||||
"sha256:1ab8f1f932e8f82355e75dda5413a57612c6ea448069d4fb2e217e9a4bed13d4",
|
||||
"sha256:223f4232855ade399bd409331e6ca70fb5578efef22cf4069a6090acc0f53c0e",
|
||||
"sha256:2455cfaeb7ac70338b3257f41e21f0724f4b5b0c0e7702da67ee6c3640835b67",
|
||||
"sha256:2899456259589aa38bfb018c364d6ae7b53c5c22d8e27d0ec7609c2a1ff78b50",
|
||||
"sha256:2a29ba94d065945944016b6b74e538bdb1751a1db6ffb80c9d3c2e40d6fa9894",
|
||||
"sha256:2a87fa548561d2f4643c99cd13131acb607ddabb70682dcf1dff5f71f781a4bf",
|
||||
"sha256:2e430cd2824f05f2d4f687701144556646bae8f249fd60aa1e4c768ba7018947",
|
||||
"sha256:36c3c175d34652a35475a73762b545f4527aec044910a651d2bf50de9c3352b1",
|
||||
"sha256:3818b8e2c4b5148567e1b09ce739006acfaa44ce3156f8cbbc11062994b8e8dd",
|
||||
"sha256:3ab9fa9d6dc2a7f29d7affdf3edebf6ece6fb28a6d80b14c3b2fb9d39b9322c3",
|
||||
"sha256:3efea981d956a6f7173b4659849f55081867cf897e719f57383698af6f618a92",
|
||||
"sha256:4c8f293f14abc8fd3e8e01c5bd86e6ed0b6ef71936ded5bf10fe7a5efefbaca3",
|
||||
"sha256:5344a43228767f53a9df6e5b253f8cdca7dfc7b7aeae52551958192f56d98457",
|
||||
"sha256:58bfa3aa19ca4c0f28c5dde0ff56c520fbac6f0daf4fac66ed4c8d2fb7f22e74",
|
||||
"sha256:5b4545b8a40478183ac06c073e81a5ce4cf01bf1734962577cf2bb569a5b3bbf",
|
||||
"sha256:5f50a1c177e2fa3ee0667a5ab79fdc6b23086bc8b589d90b93b4bd17eb0e64d1",
|
||||
"sha256:63da2ccc0857c311d764e7d3d90f429c252e83b52d1f8f1d1fe55be26827d1f4",
|
||||
"sha256:6749649eecd6a9871cae297bffa4ee76f90b4504a2a2ab528d9ebe912b101975",
|
||||
"sha256:6804daeb7ef69e7b36f76caddb85cccd63d0c56dedb47555d2fc969e2af6a1a5",
|
||||
"sha256:689bb688a1db722485e4610a503e3e9210dcc20c520b45ac8f7533c837be76fe",
|
||||
"sha256:699a9af7dffaf67deeae27b2112aa06b41c370d5e7633e0ee0aea2e0b6c211f7",
|
||||
"sha256:6b418afe5df18233fc6b6093deb82a32895b6bb0b1155c2cdb05203f583053f1",
|
||||
"sha256:76cf573e5a365e790396a5cc2b909812633409306c6531a6877c59061e42c4f2",
|
||||
"sha256:7b515674acfdcadb0eb5d00d8a709868173acece5cb0be3dd165950cbfdf5409",
|
||||
"sha256:7b770ed79542ed52c519119473898198761d78beb24b107acf3ad65deae61f1f",
|
||||
"sha256:7d2278d59425777cfcb19735018d897ca8303abe67cc735f9f97177ceff8027f",
|
||||
"sha256:7e91ee82f4199af8c43d8158024cbdff3d931df350252288f0d4ce656df7f3b5",
|
||||
"sha256:821b7f59b99551c69c85a6039c65b75f5683bdc63270fec660f75da67469ca24",
|
||||
"sha256:822068f85e12a6e292803e112ab876bc03ed1f03dddb80154c395f891ca6b31e",
|
||||
"sha256:8340225bd5e7a701c0fa98284c849c9b9fc9238abf53a0ebd90900f25d39a4e4",
|
||||
"sha256:85cabf64adec449132e55616e7ca3e1000ab449d1d0f9d7f83146ed5bdcb6d8a",
|
||||
"sha256:880bbbcbe2fca64e2f4d8e04db47bcdf504936fa2b33933efd945e1b429bea8c",
|
||||
"sha256:8d0b4612b66ff5d62d03bcaa043bb018f74dfea51184e53f067e6fdcba4bd8de",
|
||||
"sha256:8e20cb5a47247e383cf4ff523205060991021233ebd6f924bca927fcf25cf86f",
|
||||
"sha256:925073b2fe14ab9b87e73f9a5fde6ce6392da430f3004d8b72cc86f746f5163b",
|
||||
"sha256:998c7c41910666d2976928c38ea96a70d1aa43be6fe502f21a651e17483a43c5",
|
||||
"sha256:9b22c5c66f67ae00c0199f6055705bc3eb3fcb08d03d2ec4059a2b1b25ed48d7",
|
||||
"sha256:9f102706d0ca011de571de32c3247c6476b55bb6bc65a20f682f000b07a4852a",
|
||||
"sha256:a08cff61517ee26cb56f1e949cca38caabe9ea9fbb4b1e10a805dc39844b7d5c",
|
||||
"sha256:a0a336d6d3e8b234a3aae3c674873d8f0e720b76bc1d9416866c41cd9500ffb9",
|
||||
"sha256:a35f8b7fa99f90dd2f5dc5a9fa12332642f087a7641289ca6c40d6e1a2637d8e",
|
||||
"sha256:a38486985ca49cfa574a507e7a2215c0c780fd1778bb6290c21193b7211702ab",
|
||||
"sha256:a5da296eb617d18e497bcf0a5c528f5d3b18dadb3619fbdadf4ed2356ef8d941",
|
||||
"sha256:a6e441a86553c310258aca15d1c05903aaf4965b23f3bc2d55f200804e005ee5",
|
||||
"sha256:a82d05da00a58b8e4c0008edbc8a4b6ec5a4bc1e2ee0fb6ed157cf634ed7fa45",
|
||||
"sha256:ab323679b8b3030000f2be63e22cdeea5b47ee0abd2d6a1dc0c8103ddaa56cd7",
|
||||
"sha256:b1f42b6921d0e81b1bcb5e395bc091a70f41c4d4e55ba99c6da2b31626c44892",
|
||||
"sha256:b23e19989c355ca854276178a0463951a653309fb8e57ce674497f2d9f208746",
|
||||
"sha256:b264171e3143d842ded311b7dccd46ff9ef34247129ff5bf5066123c55c2431c",
|
||||
"sha256:b26a29f0b7fc6f0897f043ca366142d2b609dc60756ee6e4e90b5f762c6adc53",
|
||||
"sha256:b64d891da92e232c36976c80ed7ebb383e3f148489796d8d31a5b6a677825efe",
|
||||
"sha256:b9cc34af337a97d470040f99ba4282f6e6bac88407d021688a5d585e44a23184",
|
||||
"sha256:bc718cd47b765e790eecb74d044cc8d37d58562f6c314ee9484df26276d36a38",
|
||||
"sha256:be7292c55101e22f2a3d4d8913944cbea71eea90792bf914add27454a13905df",
|
||||
"sha256:c83203addf554215463b59f6399835201999b5e48019dc17f182ed5ad87205c9",
|
||||
"sha256:c9ec3eaf616d67db0764b3bb983962b4f385a1f08304fd30c7283954e6a7869b",
|
||||
"sha256:ca34efc80a29351897e18888c71c6aca4a359247c87e0b1c7ada14f0ab0c0fb2",
|
||||
"sha256:ca989b91cf3a3ba28930a9fc1e9aeafc2a395448641df1f387a2d394638943b0",
|
||||
"sha256:d02a5399126a53492415d4906ab0ad0375a5456cc05c3fc0fc4ca11771745cda",
|
||||
"sha256:d17bc7c2ccf49c478c5bdd447594e82692c74222698cfc9b5daae7ae7e90743b",
|
||||
"sha256:d5bf6545cd27aaa8a13033ce56354ed9e25ab0e4ac3b5392b763d8d04b08e0c5",
|
||||
"sha256:d6b430a9938a5a5d85fc107d852262ddcd48602c120e3dbb02137c83d212b380",
|
||||
"sha256:da248f93f0418a9e9d94b0080d7ebc407a9a5e6d0b57bb30db9b5cc28de1ad33",
|
||||
"sha256:da4dd7c9c50c059aba52b3524f84d7de956f7fef88f0bafcf4ad7dde94a064e8",
|
||||
"sha256:df0623dcf9668ad0445e0558a21211d4e9a149ea8f5666917c8eeec515f0a6d1",
|
||||
"sha256:e5168986b90a8d1f2f9dc1b841467c74221bd752537b99761a93d2d981e04889",
|
||||
"sha256:efa29c2fe6b4fdd32e8ef81c1528506895eca86e1d8c4657fda04c9b3786ddf9",
|
||||
"sha256:f1496ea22ca2c830cbcbd473de8f114a320da308438ae65abad6bab7867fe38f",
|
||||
"sha256:f49e52d174375a7def9915c9f06ec4e569d235ad428f70751765f48d5926678c"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
|
||||
"version": "==4.9.2"
|
||||
},
|
||||
"mistune": {
|
||||
"hashes": [
|
||||
"sha256:0246113cb2492db875c6be56974a7c893333bf26cd92891c85f63151cee09d34",
|
||||
"sha256:bad7f5d431886fcbaf5f758118ecff70d31f75231b34024a1341120340a65ce8"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==2.0.5"
|
||||
},
|
||||
"mpmath": {
|
||||
"hashes": [
|
||||
"sha256:7a28eb2a9774d00c7bc92411c19a89209d5da7c4c9a9e227be8330a23a25b91f",
|
||||
"sha256:a0b2b9fe80bbcd81a6647ff13108738cfb482d481d826cc0e02f5b35e5c88d2c"
|
||||
],
|
||||
"version": "==1.3.0"
|
||||
},
|
||||
"mysql": {
|
||||
"hashes": [
|
||||
"sha256:8893cb143a5ac525c49ef358a23b8a0dc9721a95646f7bab6ca2f384c18a6a9a",
|
||||
"sha256:fd7bae7d7301ce7cd3932e5ff7f77bbc8e34872108252866e08d16d6b8e8de8c"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==0.0.3"
|
||||
},
|
||||
"mysql-connector-python": {
|
||||
"hashes": [
|
||||
"sha256:0004426e964856148e1cde31e9b8be63ae3013715b048ff0f2ede69a6ddd36f7",
|
||||
"sha256:016662c6252f2c5f47805d9168187be1316d0c1d7109f9fe668482c3d6e5711d",
|
||||
"sha256:241483065ad062256985e082e3cbb3e7d1d6d2275cee17c66d22525b09096201",
|
||||
"sha256:34d5c5f6ec7c1e75bf972def40d097138e097dc694e36dec89a5dd604ef7aada",
|
||||
"sha256:37eace5b7eb676a41ff1edc5cf6ce4ae1c28406d1a6fe84941e6aa396d688195",
|
||||
"sha256:3bedf8265fb31698e4144ca54e241e3386802c3a437745b1a536a74cbe7e4fb9",
|
||||
"sha256:41db4452a99ee28494313eab1aa7749475d3e39bed7b24a0868aee45bd0d9c73",
|
||||
"sha256:46ff8a10c13f39996d60f45c30cf2ea15e883bc71d58259ed2fea0a5a6fb93a3",
|
||||
"sha256:4c82fb70f44f2469c0879434c1d8ee3162f56a40cc8f5ca1cc4d97f06c84cd43",
|
||||
"sha256:7266d7b2550f9fe0cdcea1647aa6aade352e14095042b6a3921c9152cf8543e8",
|
||||
"sha256:7318f416b9defe84b2bd025304bab62b68f8d8fcbe479af5593161eff12ef169",
|
||||
"sha256:753d07fb39a67f7f35fe6e6a4fac12008287661de59f9d5c0bf4da3359d83eb8",
|
||||
"sha256:96f7fb0ccfe96e6e478e5f0f034c99bda961b99ffa1c746cee39cfea45b0c04d",
|
||||
"sha256:9775331fa60b5d5a6925781d77eee4384e2b54a12dea694ffdefd1cf1a9c0fdb",
|
||||
"sha256:984f5649e6abee04461d6f52fbc77387d7137b8fd003c54bac66505006f17183",
|
||||
"sha256:9f5eb33e29742c5f8ef23df2d3f0de0e46f4325e4324016e15aba7f8665a68c0",
|
||||
"sha256:a632d7b0e569a46e6d44e6cd3f8db747995a787a081870697dbfd3ae18949339",
|
||||
"sha256:af9feec311d8ea51261e1ef1f959a442708e30f0024d08d0fb537b07a1271634",
|
||||
"sha256:c20a85a69af41d2d7d5cf52106f0b9473775819d189487c6ff3d3f3946931ca2",
|
||||
"sha256:d8167868ebad8d78ba69babd028626e96a51365cab76edf735b2559731759b62",
|
||||
"sha256:db422b19347c5d00e078dd64e281e5b1e5a19a2d972dc2d9733b136d79c34798",
|
||||
"sha256:e853e12c00e3beabc581f4e039222708ee606fef80a3bac6b1f497ed89a31aea",
|
||||
"sha256:ea05590cb972b114efa027c343b4b7110d8e8450493984ebfb9a651e27674636",
|
||||
"sha256:f324233af7ec9fcb19c23096af27662459708c0465886cb017d78ff3f5b78b55",
|
||||
"sha256:f403ff22d3514d08028590fef463d17dc107ac72ea27a49429614949d82fda40"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==8.0.33"
|
||||
},
|
||||
"mysqlclient": {
|
||||
"hashes": [
|
||||
"sha256:0d1cd3a5a4d28c222fa199002810e8146cffd821410b67851af4cc80aeccd97c",
|
||||
"sha256:828757e419fb11dd6c5ed2576ec92c3efaa93a0f7c39e263586d1ee779c3d782",
|
||||
"sha256:996924f3483fd36a34a5812210c69e71dea5a3d5978d01199b78b7f6d485c855",
|
||||
"sha256:b355c8b5a7d58f2e909acdbb050858390ee1b0e13672ae759e5e784110022994",
|
||||
"sha256:c1ed71bd6244993b526113cca3df66428609f90e4652f37eb51c33496d478b37",
|
||||
"sha256:c812b67e90082a840efb82a8978369e6e69fc62ce1bda4ca8f3084a9d862308b",
|
||||
"sha256:dea88c8d3f5a5d9293dfe7f087c16dd350ceb175f2f6631c9cf4caf3e19b7a96"
|
||||
],
|
||||
"markers": "python_version >= '3.5'",
|
||||
"version": "==2.1.1"
|
||||
},
|
||||
"protobuf": {
|
||||
"hashes": [
|
||||
"sha256:03038ac1cfbc41aa21f6afcbcd357281d7521b4157926f30ebecc8d4ea59dcb7",
|
||||
"sha256:28545383d61f55b57cf4df63eebd9827754fd2dc25f80c5253f9184235db242c",
|
||||
"sha256:2e3427429c9cffebf259491be0af70189607f365c2f41c7c3764af6f337105f2",
|
||||
"sha256:398a9e0c3eaceb34ec1aee71894ca3299605fa8e761544934378bbc6c97de23b",
|
||||
"sha256:44246bab5dd4b7fbd3c0c80b6f16686808fab0e4aca819ade6e8d294a29c7050",
|
||||
"sha256:447d43819997825d4e71bf5769d869b968ce96848b6479397e29fc24c4a5dfe9",
|
||||
"sha256:67a3598f0a2dcbc58d02dd1928544e7d88f764b47d4a286202913f0b2801c2e7",
|
||||
"sha256:74480f79a023f90dc6e18febbf7b8bac7508420f2006fabd512013c0c238f454",
|
||||
"sha256:819559cafa1a373b7096a482b504ae8a857c89593cf3a25af743ac9ecbd23480",
|
||||
"sha256:899dc660cd599d7352d6f10d83c95df430a38b410c1b66b407a6b29265d66469",
|
||||
"sha256:8c0c984a1b8fef4086329ff8dd19ac77576b384079247c770f29cc8ce3afa06c",
|
||||
"sha256:9aae4406ea63d825636cc11ffb34ad3379335803216ee3a856787bcf5ccc751e",
|
||||
"sha256:a7ca6d488aa8ff7f329d4c545b2dbad8ac31464f1d8b1c87ad1346717731e4db",
|
||||
"sha256:b6cc7ba72a8850621bfec987cb72623e703b7fe2b9127a161ce61e61558ad905",
|
||||
"sha256:bf01b5720be110540be4286e791db73f84a2b721072a3711efff6c324cdf074b",
|
||||
"sha256:c02ce36ec760252242a33967d51c289fd0e1c0e6e5cc9397e2279177716add86",
|
||||
"sha256:d9e4432ff660d67d775c66ac42a67cf2453c27cb4d738fc22cb53b5d84c135d4",
|
||||
"sha256:daa564862dd0d39c00f8086f88700fdbe8bc717e993a21e90711acfed02f2402",
|
||||
"sha256:de78575669dddf6099a8a0f46a27e82a1783c557ccc38ee620ed8cc96d3be7d7",
|
||||
"sha256:e64857f395505ebf3d2569935506ae0dfc4a15cb80dc25261176c784662cdcc4",
|
||||
"sha256:f4bd856d702e5b0d96a00ec6b307b0f51c1982c2bf9c0052cf9019e9a544ba99",
|
||||
"sha256:f4c42102bc82a51108e449cbb32b19b180022941c727bac0cfd50170341f16ee"
|
||||
],
|
||||
"markers": "python_version >= '3.7'",
|
||||
"version": "==3.20.3"
|
||||
},
|
||||
"python-docx": {
|
||||
"hashes": [
|
||||
"sha256:1105d233a0956dd8dd1e710d20b159e2d72ac3c301041b95f4d4ceb3e0ebebc4"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==0.8.11"
|
||||
},
|
||||
"sympy": {
|
||||
"hashes": [
|
||||
"sha256:938f984ee2b1e8eae8a07b884c8b7a1146010040fccddc6539c54f401c8f6fcf",
|
||||
"sha256:e32380dce63cb7c0108ed525570092fd45168bdae2faa17e528221ef72e88658"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==1.11.1"
|
||||
}
|
||||
},
|
||||
"develop": {}
|
||||
}
|
||||
190
daniel_jo/ITP4459_assignment_2023/src/python/app.py
Normal file
190
daniel_jo/ITP4459_assignment_2023/src/python/app.py
Normal file
@@ -0,0 +1,190 @@
|
||||
import tkinter
|
||||
import tkinter.ttk
|
||||
|
||||
import constants
|
||||
from models import Programme
|
||||
from widgets import ModuleWidget
|
||||
|
||||
from pprint import pprint
|
||||
|
||||
# app.py - Main GUI dialog
|
||||
|
||||
class App(tkinter.Tk):
|
||||
def __init__(self, db):
|
||||
super().__init__()
|
||||
self.__db = db
|
||||
|
||||
self.__programme = None
|
||||
self.__modules = None
|
||||
|
||||
self.refresh_db()
|
||||
self.setup_ui()
|
||||
|
||||
# NOTE: Do not modify this method
|
||||
def report_callback_exception(self, *args):
|
||||
"""When an error is occurred, raise the exception
|
||||
so main_gui.py will handle the display of error.
|
||||
"""
|
||||
raise args[1].with_traceback(args[2])
|
||||
|
||||
def refresh_db(self):
|
||||
# Fetch all program records from database, and store it in __programme as a list
|
||||
self.__programme = Programme.fetchall(self.__db)
|
||||
|
||||
def setup_ui(self):
|
||||
self.title(constants.APP_NAME)
|
||||
self.minsize(640, 480)
|
||||
|
||||
# Set weight of column 0 for auto resizing on X axis
|
||||
self.columnconfigure(0, weight=1)
|
||||
|
||||
# Header
|
||||
frm_top_bar = tkinter.Frame(self)
|
||||
frm_top_bar.grid(row=0, sticky=tkinter.EW)
|
||||
tkinter.Label(
|
||||
frm_top_bar,
|
||||
text = constants.APP_NAME,
|
||||
font = ('bold', 16)
|
||||
).pack(side = tkinter.LEFT, padx=8, pady=8)
|
||||
tkinter.ttk.Separator(self).grid(row=1, sticky=tkinter.EW)
|
||||
|
||||
# Content
|
||||
frm_content = tkinter.Frame(self)
|
||||
frm_content.grid(row=2, sticky=tkinter.NSEW)
|
||||
self.rowconfigure(2, weight=1)
|
||||
|
||||
# Options
|
||||
frm_filter = tkinter.LabelFrame(frm_content, text = "Options")
|
||||
frm_filter.pack(fill=tkinter.X, side=tkinter.TOP, padx=8, pady=(4, 0))
|
||||
frm_filter_container = tkinter.Frame(frm_filter)
|
||||
frm_filter_container.pack(fill=tkinter.BOTH, side=tkinter.TOP, padx=6, pady=(0, 4))
|
||||
|
||||
# Options variables
|
||||
# Option widgets
|
||||
tkinter.Label(frm_filter_container,text = "Programme",).grid(row=0, column=0, padx=(0, 4))
|
||||
list_of_programme=[]
|
||||
for p in self.__programme:
|
||||
list_of_programme.append(p.get_name())
|
||||
|
||||
self.selected_programme = tkinter.StringVar()
|
||||
self.__om_programme_filter = tkinter.ttk.Combobox(frm_filter_container,textvariable=self.selected_programme, width=50)
|
||||
self.__om_programme_filter.set('Please select')
|
||||
self.__om_programme_filter['values'] = list_of_programme
|
||||
self.__om_programme_filter['state'] = 'readonly'
|
||||
self.__om_programme_filter.grid(row=0, column=1, padx=(0, 4), sticky=tkinter.EW)
|
||||
self.__om_programme_filter.bind('<<ComboboxSelected>>', self.on_programme_changed )
|
||||
|
||||
# Semester select
|
||||
tkinter.Label( frm_filter_container, text = "Semester").grid(row=0, column=2, padx=(0, 4))
|
||||
self.selected_semester = tkinter.StringVar()
|
||||
self.__om_semester_filter = tkinter.ttk.Combobox(frm_filter_container, textvariable=self.selected_semester)
|
||||
self.__om_semester_filter.set('Please select')
|
||||
self.__om_semester_filter['state'] = 'disabled'
|
||||
self.__om_semester_filter.grid(row=0, column=3, padx=(4, 4))
|
||||
self.__om_semester_filter.bind('<<ComboboxSelected>>', self.on_semester_changed)
|
||||
|
||||
# Modules
|
||||
frm_modules = tkinter.LabelFrame(frm_content, text = "Modules")
|
||||
frm_modules.pack(fill=tkinter.BOTH, side=tkinter.TOP, expand=1, padx=8, pady=(4, 0))
|
||||
self.__frm_modules_container = tkinter.Frame(frm_modules)
|
||||
self.__frm_modules_container.pack(fill=tkinter.BOTH, side=tkinter.TOP, padx=6, pady=(0, 4))
|
||||
|
||||
self.__module_widgets = list()
|
||||
for _ in range(constants.APP_MAX_MODULES):
|
||||
widget = ModuleWidget(self.__frm_modules_container, self.on_gpa_changed)
|
||||
widget.get_frame().pack(fill=tkinter.X, side=tkinter.TOP, expand=1, pady=8)
|
||||
widget.set_module(None)
|
||||
|
||||
self.__module_widgets.append(widget)
|
||||
|
||||
# Bottom bar
|
||||
frm_bottom_bar = tkinter.Frame(self)
|
||||
frm_bottom_bar.grid(row=3, column=0,padx=(0, 10), pady=(4,4), sticky=tkinter.E)
|
||||
|
||||
# Result Label
|
||||
self.semester_gpa = tkinter.StringVar()
|
||||
self.lbl_stu_list = tkinter.ttk.Label(frm_bottom_bar, text='Semester GPA: %.2f' % 0, font=("bold",11))
|
||||
self.lbl_stu_list.grid(row=0, column=0, sticky=tkinter.E)
|
||||
|
||||
def setup_module_list(self):
|
||||
# Update the ModuleWidget in list __module_widgets to display
|
||||
# modules for the selected programme and semester
|
||||
current_select_idx = self.__om_semester_filter.current()
|
||||
selected_semester = list(sorted(self.__modules.keys()))[current_select_idx]
|
||||
module_in_semester = self.__modules[selected_semester]
|
||||
for widget in self.__frm_modules_container.winfo_children():
|
||||
widget.destroy()
|
||||
|
||||
self.__module_widgets = list()
|
||||
i = 0
|
||||
for _ in range(constants.APP_MAX_MODULES):
|
||||
widget = ModuleWidget(self.__frm_modules_container, self.on_gpa_changed)
|
||||
widget.get_frame().pack(fill=tkinter.X, side=tkinter.TOP, expand=1, pady=8)
|
||||
if (i < len(module_in_semester)):
|
||||
widget.set_module(module_in_semester[i])
|
||||
|
||||
self.__module_widgets.append(widget)
|
||||
i = i + 1
|
||||
|
||||
def on_programme_changed(self, _):
|
||||
# Get selected programme based on option menu index, and update GUI
|
||||
for program in self.__programme:
|
||||
if (self.selected_programme.get() == program.get_name()):
|
||||
self.__om_semester_filter['values'] = sorted(program.get_modules().keys())
|
||||
self.__modules = program.get_modules()
|
||||
break
|
||||
self.__om_semester_filter['state'] = 'readonly'
|
||||
|
||||
self.__om_semester_filter.set('Please select')
|
||||
|
||||
for widget in self.__frm_modules_container.winfo_children():
|
||||
widget.destroy()
|
||||
|
||||
self.__module_widgets = list()
|
||||
for _ in range(constants.APP_MAX_MODULES):
|
||||
widget = ModuleWidget(self.__frm_modules_container, self.on_gpa_changed)
|
||||
widget.get_frame().pack(fill=tkinter.X, side=tkinter.TOP, expand=1, pady=8)
|
||||
widget.set_module(None)
|
||||
self.__module_widgets.append(widget)
|
||||
|
||||
# reset gpa
|
||||
self.lbl_stu_list.configure(text='Semester GPA: %.2f' % (0))
|
||||
|
||||
|
||||
def on_semester_changed(self, _):
|
||||
# Get modules based on option menu value, and update GUI
|
||||
self.setup_module_list()
|
||||
|
||||
# reset gpa
|
||||
self.lbl_stu_list.configure(text='Semester GPA: %.2f' % (0))
|
||||
|
||||
|
||||
def on_gpa_changed(self):
|
||||
# Calcuate semester GPA based on self.__modules
|
||||
|
||||
# 1. For each modules in self.__modules, App class
|
||||
# 2. Obtain the module credit of current module and add it to the total credits
|
||||
# 3. Calculate the module gpa by multiplying the current module gpa and module credit
|
||||
# 4. Add the calculated module gpa to total module gpa
|
||||
# 5. After all module gpa are calculated, divide the total module gpa by total credits
|
||||
|
||||
total = 0
|
||||
current_select_idx = self.__om_semester_filter.current()
|
||||
selected_semester = list(sorted(self.__modules.keys()))[current_select_idx]
|
||||
module_in_semester = self.__modules[selected_semester]
|
||||
number_of_modules = len(module_in_semester)
|
||||
|
||||
should_update = True
|
||||
all_credit = 0
|
||||
for i in range(0, number_of_modules):
|
||||
gpa = self.__module_widgets[i].get_gpa()
|
||||
if (gpa == -1):
|
||||
# not selected
|
||||
should_update = False
|
||||
else:
|
||||
credit =self.__module_widgets[i].get_module().get_credit()
|
||||
all_credit = all_credit + credit
|
||||
total = total + (gpa * credit)
|
||||
|
||||
if should_update:
|
||||
self.lbl_stu_list.configure(text='Semester GPA: %.2f' % (total / all_credit))
|
||||
26
daniel_jo/ITP4459_assignment_2023/src/python/constants.py
Normal file
26
daniel_jo/ITP4459_assignment_2023/src/python/constants.py
Normal file
@@ -0,0 +1,26 @@
|
||||
# Database
|
||||
# NOTE: Modify the username and password if needed
|
||||
DB_HOST = "localhost"
|
||||
DB_NAME = "itp4459_asg"
|
||||
|
||||
DB_USER = "db_user"
|
||||
DB_PASS = "db_user_pass"
|
||||
|
||||
# Tkinter GUI
|
||||
APP_NAME = "GPA Calculator"
|
||||
APP_MAX_MODULES = 7
|
||||
|
||||
# Application logic
|
||||
GPA_MAPPING = {
|
||||
"A": 4,
|
||||
"A-": 3.7,
|
||||
"B+": 3.3,
|
||||
"B": 3,
|
||||
"B-": 2.7,
|
||||
"C+": 2.3,
|
||||
"C": 2,
|
||||
"C-": 1.7,
|
||||
"D+": 1.3,
|
||||
"D": 1,
|
||||
"F": 0
|
||||
}
|
||||
78
daniel_jo/ITP4459_assignment_2023/src/python/db.py
Normal file
78
daniel_jo/ITP4459_assignment_2023/src/python/db.py
Normal file
@@ -0,0 +1,78 @@
|
||||
from abc import ABC, abstractmethod
|
||||
|
||||
import mysql.connector
|
||||
|
||||
# db.py - Database related classes
|
||||
# NOTE: You do not need to modify this source file
|
||||
|
||||
__all__ = (
|
||||
'MySQLConnection', 'MySQLObject'
|
||||
)
|
||||
|
||||
class MySQLConnection:
|
||||
"""A class to handle all database related functions for this assingment."""
|
||||
|
||||
def __init__(self, host, user, password, database=None):
|
||||
"""Initialize connection to MySQL server.
|
||||
|
||||
Create database if needed automatically.
|
||||
"""
|
||||
self.db = mysql.connector.connect(
|
||||
host=host,
|
||||
user=user,
|
||||
password=password
|
||||
)
|
||||
if database is not None:
|
||||
with self.db.cursor() as cursor:
|
||||
cursor.execute(f"CREATE DATABASE IF NOT EXISTS {database};")
|
||||
self.db.commit()
|
||||
self.db.database = database
|
||||
|
||||
def close(self):
|
||||
"""Closes the database connection."""
|
||||
self.db.close()
|
||||
|
||||
def cursor(self):
|
||||
"""Returns a database cursor, for executing multiple statments
|
||||
in a single transaction.
|
||||
"""
|
||||
return self.db.cursor()
|
||||
|
||||
def commit(self):
|
||||
"""Commit changes to database."""
|
||||
self.db.commit()
|
||||
|
||||
def execute(self, query):
|
||||
"""Commit: Python -> Database
|
||||
|
||||
e.g. CREATE, INSERT and UPDATE
|
||||
"""
|
||||
with self.db.cursor() as cursor:
|
||||
cursor.execute(query)
|
||||
self.db.commit()
|
||||
|
||||
def fetchone(self, query):
|
||||
"""Fetch: Python <- Database
|
||||
|
||||
e.g. SELECT and SHOW
|
||||
"""
|
||||
with self.db.cursor() as cursor:
|
||||
cursor.execute(query)
|
||||
return cursor.fetchone()
|
||||
|
||||
def fetchall(self, query):
|
||||
"""Fetch: Python <- Database
|
||||
|
||||
e.g. SELECT and SHOW
|
||||
"""
|
||||
with self.db.cursor() as cursor:
|
||||
cursor.execute(query)
|
||||
return cursor.fetchall()
|
||||
|
||||
class MySQLObject(ABC):
|
||||
"""A database managed object."""
|
||||
@staticmethod
|
||||
@abstractmethod
|
||||
def fetchall(db):
|
||||
"""Returns a list of object imported from database."""
|
||||
return NotImplemented
|
||||
99
daniel_jo/ITP4459_assignment_2023/src/python/main_db.py
Normal file
99
daniel_jo/ITP4459_assignment_2023/src/python/main_db.py
Normal file
@@ -0,0 +1,99 @@
|
||||
import constants
|
||||
from db import MySQLConnection
|
||||
|
||||
# main_db.py - Program entry point for Part 1 Database
|
||||
|
||||
tbl_programme = """
|
||||
CREATE TABLE IF NOT EXISTS programmes (
|
||||
programme_id VARCHAR(9) NOT NULL,
|
||||
programme_name VARCHAR(100) NOT NULL,
|
||||
PRIMARY KEY (programme_id)
|
||||
)"""
|
||||
|
||||
tbl_module = """
|
||||
CREATE TABLE IF NOT EXISTS modules (
|
||||
module_id VARCHAR(7) NOT NULL,
|
||||
module_name VARCHAR(100) NOT NULL,
|
||||
module_credit INT,
|
||||
PRIMARY KEY (module_id)
|
||||
)"""
|
||||
|
||||
tbl_mapping = """
|
||||
CREATE TABLE IF NOT EXISTS mappings (
|
||||
mapping_programme_id VARCHAR(9) NOT NULL,
|
||||
mapping_semester INT NOT NULL,
|
||||
mapping_module_id VARCHAR(7) NOT NULL,
|
||||
PRIMARY KEY (mapping_programme_id, mapping_semester, mapping_module_id),
|
||||
FOREIGN KEY (mapping_programme_id) REFERENCES programmes(programme_id),
|
||||
FOREIGN KEY (mapping_module_id) REFERENCES modules(module_id)
|
||||
)"""
|
||||
|
||||
radius = (('circle', 1.0),('circle', 2.0),('circle', 3.0))
|
||||
query = "INSERT INTO circles (name, radius) VALUES (%s, %s)"
|
||||
|
||||
insert_programmes = (
|
||||
('IT114124', "Higher Diploma in AI and Smart Technology"),
|
||||
('IT114122', "Higher Diploma in Cybersecurity")
|
||||
)
|
||||
insert_programme_query = "INSERT INTO programmes (programme_id, programme_name) VALUES (%s, %s)"
|
||||
|
||||
insert_modules = (
|
||||
("LAN3100","English & Communication: Workplace Interaction","6"),
|
||||
("LAN3103","English & Communication: Workplace Correspondance","6"),
|
||||
("LAN4107","English & Communication: Reports","9"),
|
||||
("LAN4108","English & Communication: Persuasive Presentations","9"),
|
||||
("LAN4101","English & Communication: Promotional Materials","10"),
|
||||
("LAN3003","Vocational Chinese Communication: Putonghua Conversation and Reports","6"),
|
||||
("LAN4003","Vocational Chinese Communication: Putonghua Presentations, Administrative and Technical Text Writing","9")
|
||||
)
|
||||
|
||||
insert_modules_query = "INSERT INTO modules (module_id, module_name, module_credit) VALUES (%s, %s, %s)"
|
||||
|
||||
insert_mappings = (
|
||||
("IT114124","1","LAN3100"),
|
||||
("IT114124","1","LAN3103"),
|
||||
("IT114124","1","LAN3003"),
|
||||
("IT114124","2","LAN4107"),
|
||||
("IT114124","2","LAN4003"),
|
||||
("IT114124","4","LAN4108"),
|
||||
("IT114124","5","LAN4101"),
|
||||
("IT114122","1","LAN3100"),
|
||||
("IT114122","1","LAN3103"),
|
||||
("IT114122","1","LAN3003"),
|
||||
("IT114122","2","LAN4107"),
|
||||
("IT114122","2","LAN4003"),
|
||||
("IT114122","4","LAN4108"),
|
||||
("IT114122","5","LAN4101")
|
||||
)
|
||||
|
||||
insert_mappings_query = "INSERT INTO mappings (mapping_programme_id, mapping_semester, mapping_module_id) VALUES (%s, %s, %s)"
|
||||
|
||||
if __name__ == "__main__":
|
||||
db = MySQLConnection(
|
||||
host=constants.DB_HOST,
|
||||
user=constants.DB_USER,
|
||||
password=constants.DB_PASS
|
||||
)
|
||||
|
||||
with db.cursor() as cursor:
|
||||
# Recreate database and select as default
|
||||
cursor.execute(f"DROP DATABASE IF EXISTS {constants.DB_NAME};")
|
||||
cursor.execute(f"CREATE DATABASE {constants.DB_NAME};")
|
||||
cursor.execute(f"USE {constants.DB_NAME};")
|
||||
|
||||
# Tables
|
||||
cursor.execute(tbl_programme)
|
||||
cursor.execute(tbl_module)
|
||||
cursor.execute(tbl_mapping)
|
||||
|
||||
# Records
|
||||
# cursor.executemany(query, radius)
|
||||
cursor.executemany(insert_programme_query, insert_programmes)
|
||||
cursor.executemany(insert_modules_query, insert_modules)
|
||||
cursor.executemany(insert_mappings_query, insert_mappings)
|
||||
|
||||
db.commit()
|
||||
db.close()
|
||||
|
||||
|
||||
|
||||
42
daniel_jo/ITP4459_assignment_2023/src/python/main_gui.py
Normal file
42
daniel_jo/ITP4459_assignment_2023/src/python/main_gui.py
Normal file
@@ -0,0 +1,42 @@
|
||||
from tkinter import messagebox
|
||||
|
||||
import constants
|
||||
import traceback
|
||||
from app import App
|
||||
from db import MySQLConnection
|
||||
|
||||
# main_gui.py - Program entry point for Part 2 GUI
|
||||
# NOTE: You do not need to modify this source file
|
||||
|
||||
if __name__ == "__main__":
|
||||
app = None
|
||||
db = None
|
||||
try:
|
||||
# Create connection to database
|
||||
db = MySQLConnection(
|
||||
host=constants.DB_HOST,
|
||||
user=constants.DB_USER,
|
||||
password=constants.DB_PASS,
|
||||
database=constants.DB_NAME)
|
||||
|
||||
# Create and start app
|
||||
app = App(db)
|
||||
app.mainloop()
|
||||
except Exception as e:
|
||||
# Display error in console
|
||||
traceback.print_exception(e)
|
||||
|
||||
# Display error as message box
|
||||
errorType = f"{type(e).__name__}"
|
||||
messagebox.showerror(errorType, e.__str__())
|
||||
|
||||
# Close the app window
|
||||
if app is App:
|
||||
app.destroy()
|
||||
|
||||
# Raise the error again for debugger
|
||||
raise e
|
||||
finally:
|
||||
# Close database connection
|
||||
if db is MySQLConnection:
|
||||
db.close()
|
||||
143
daniel_jo/ITP4459_assignment_2023/src/python/models.py
Normal file
143
daniel_jo/ITP4459_assignment_2023/src/python/models.py
Normal file
@@ -0,0 +1,143 @@
|
||||
import constants
|
||||
from db import MySQLObject
|
||||
|
||||
# models.py - Python representation of database table records
|
||||
|
||||
class Programme(MySQLObject):
|
||||
def __init__(self, id, name):
|
||||
# initialize the id, name and modules attributes.
|
||||
self.__id = id # a non-public string to store the id of the programme.
|
||||
self.__name = name # a non-public string so store the name of the programme.
|
||||
self.__modules = {} # a non-public dictionary to store Module objects, grouped by semester.
|
||||
|
||||
def __eq__(self, other):
|
||||
# returns true if the id attribute is equal to the other object’s id attribute.
|
||||
return self.__id == other.get_id()
|
||||
|
||||
def get_id(self):
|
||||
# returns the id for this programme.
|
||||
return self.__id
|
||||
|
||||
def get_name(self):
|
||||
# returns the name for this programme.
|
||||
return self.__name
|
||||
|
||||
def get_modules(self):
|
||||
# returns Module dictionary object which contains module list of all semesters
|
||||
return self.__modules
|
||||
|
||||
def add_module(self, semester, module):
|
||||
# append module into list inside modules’ dictionary,
|
||||
# using semester as key.
|
||||
if semester in self.__modules:
|
||||
# module already contain semester, fall to end of function to add module into semester
|
||||
pass
|
||||
else:
|
||||
# If the semester key is not found in the dictionary,
|
||||
# an empty list will be created to store the module object, init an empty semester list literally
|
||||
self.__modules[semester]= []
|
||||
|
||||
# the semester will be available as a key after this line, append module into semester
|
||||
self.__modules[semester].append(module)
|
||||
|
||||
def get_modules_by_semester(self, semester):
|
||||
# returns Module list object from modules dictionary, using semester as key
|
||||
return self.__modules[semester]
|
||||
|
||||
@staticmethod
|
||||
def fetchall(db):
|
||||
# overrides the parent class fetchall abstract static method to
|
||||
# create a list of Programme objects retrieved from database table programme.
|
||||
# For each of the created Programme objects,
|
||||
# The modules belongs to the particular programme
|
||||
|
||||
output = []
|
||||
temp= {}
|
||||
|
||||
with db.cursor() as cursor:
|
||||
cursor.execute(f"USE {constants.DB_NAME};")
|
||||
query = '''
|
||||
SELECT programme_id, mapping_semester, module_id, programme_name, module_name, module_credit
|
||||
FROM programmes
|
||||
INNER JOIN mappings ON programmes.programme_id = mappings.mapping_programme_id
|
||||
INNER JOIN modules ON mappings.mapping_module_id = modules.module_id;
|
||||
'''
|
||||
cursor.execute(query)
|
||||
records = cursor.fetchall()
|
||||
|
||||
# row => module, bloat modules
|
||||
for row in records:
|
||||
programme_id = row[0]
|
||||
mapping_semester = row[1]
|
||||
module_id = row[2]
|
||||
programme_name = row[3]
|
||||
module_name = row[4]
|
||||
module_credit = row[5]
|
||||
new_module = Module(module_id, module_name, module_credit)
|
||||
|
||||
if not(programme_id in temp):
|
||||
temp[programme_id] = {'_name': programme_name}
|
||||
if not(mapping_semester in temp[programme_id] ):
|
||||
temp[programme_id][mapping_semester] = []
|
||||
|
||||
temp[programme_id][mapping_semester].append(new_module)
|
||||
|
||||
for programme_id in temp.keys():
|
||||
new_program = Programme(programme_id, temp[programme_id]['_name'])
|
||||
# semester
|
||||
for semester in temp[programme_id].keys():
|
||||
if (type(semester) == type(1)):
|
||||
modules_in_semester = temp[programme_id][semester]
|
||||
for module in modules_in_semester:
|
||||
new_program.add_module(semester, module)
|
||||
|
||||
output.append(new_program)
|
||||
|
||||
return output
|
||||
|
||||
class Module(MySQLObject):
|
||||
def __init__(self, id, name, credit):
|
||||
# initialize the id, name and credit attributes
|
||||
self.__id = id # a non-public string to store the id of the module
|
||||
self.__name = name # a non-public string to store the name of the module
|
||||
self.__credit = credit # a non-public integer to store the credit of the module
|
||||
|
||||
def __eq__(self, other):
|
||||
# returns true of the id attribute is equal to the other object’s id attribute
|
||||
if (hasattr(other,'get_id')):
|
||||
return self.__id == other.get_id()
|
||||
else:
|
||||
return False
|
||||
|
||||
def get_id(self):
|
||||
# returns the id for this module
|
||||
return self.__id
|
||||
|
||||
def get_name(self):
|
||||
# returns the name for this module
|
||||
return self.__name
|
||||
|
||||
def get_credit(self):
|
||||
# returns the credit for this module
|
||||
return self.__credit
|
||||
|
||||
@staticmethod
|
||||
def fetchall(db):
|
||||
# fetchall overrides the parent class fetchall abstract static method to
|
||||
# return a list of Module objects retrieved from database table module.
|
||||
# Module objects will be created and appended into the
|
||||
# returned list for every records retrieved from database.
|
||||
query = "select module_id, module_name, module_credit from modules"
|
||||
output = []
|
||||
|
||||
with db.cursor() as cursor:
|
||||
cursor.execute(f"USE {constants.DB_NAME};")
|
||||
cursor.execute(query)
|
||||
records = cursor.fetchall()
|
||||
for row in records:
|
||||
module_id = row[0]
|
||||
module_name = row[1]
|
||||
module_credit = row[2]
|
||||
output.append(Module(module_id, module_name, module_credit))
|
||||
|
||||
return output
|
||||
11
daniel_jo/ITP4459_assignment_2023/src/python/readme.md
Normal file
11
daniel_jo/ITP4459_assignment_2023/src/python/readme.md
Normal file
@@ -0,0 +1,11 @@
|
||||
```batch
|
||||
|
||||
# start docker
|
||||
docker compose up -d
|
||||
|
||||
pipenv shell
|
||||
|
||||
# inside shell
|
||||
./run.bat
|
||||
|
||||
```
|
||||
10
daniel_jo/ITP4459_assignment_2023/src/python/run.bat
Normal file
10
daniel_jo/ITP4459_assignment_2023/src/python/run.bat
Normal file
@@ -0,0 +1,10 @@
|
||||
@REM feed
|
||||
@REM python ./main_db.py
|
||||
python "./main_gui.py"
|
||||
|
||||
@REM python ./test_combobox.py
|
||||
|
||||
@REM python .\options_test.py
|
||||
@REM python ".\models.py"
|
||||
|
||||
@REM python ".\test_models.py"
|
||||
92
daniel_jo/ITP4459_assignment_2023/src/python/widgets.py
Normal file
92
daniel_jo/ITP4459_assignment_2023/src/python/widgets.py
Normal file
@@ -0,0 +1,92 @@
|
||||
import tkinter
|
||||
from constants import GPA_MAPPING
|
||||
import tkinter as tk
|
||||
import tkinter.ttk as ttk
|
||||
from pprint import pprint
|
||||
|
||||
# widgets.py - Implements a custom GUI widget 'ModuleWidget'
|
||||
# TODO: Remove all 'pass' statements and complete the implementation based on class description
|
||||
|
||||
class ModuleWidget:
|
||||
"""A supplier class for displaying a module and returning the GPA value.
|
||||
"""
|
||||
def __init__(self, master, command):
|
||||
self.__module = None
|
||||
self.__command = command
|
||||
self.__gpa = -1
|
||||
|
||||
self.setup_ui(master)
|
||||
self.set_module(None)
|
||||
|
||||
def set_module(self, module = None):
|
||||
if (module != None):
|
||||
# TODO: When module is provided:
|
||||
# - Update UI to display module information
|
||||
# - Enable option menu
|
||||
|
||||
self.__var_name.set(module.get_name())
|
||||
self.__var_credit.set(module.get_credit())
|
||||
self.__ent_name['state'] = 'readonly'
|
||||
self.__ent_credit['state'] = 'readonly'
|
||||
self.__om_grade['state'] = 'readonly'
|
||||
|
||||
else:
|
||||
# - Clear entry content
|
||||
# - Disable option menu
|
||||
self.__var_name.set('')
|
||||
self.__var_credit.set('')
|
||||
self.__ent_name['state'] = 'disabled'
|
||||
self.__ent_credit['state'] = 'disabled'
|
||||
self.__om_grade['state'] = 'disabled'
|
||||
|
||||
# Finally, update self.__module
|
||||
self.__module = module
|
||||
|
||||
def get_module(self):
|
||||
return self.__module
|
||||
|
||||
def get_frame(self):
|
||||
return self.__frame
|
||||
|
||||
def get_gpa(self):
|
||||
return self.__gpa
|
||||
|
||||
def setup_ui(self, master):
|
||||
# Variables
|
||||
|
||||
# Contianer
|
||||
self.__frame = tkinter.Frame(master)
|
||||
# pprint(self)
|
||||
|
||||
# Module name
|
||||
tkinter.Label(self.__frame, text = "Module", ).grid(row=0, column=0, padx=(0, 4), sticky=tkinter.W)
|
||||
self.__var_name = tkinter.StringVar(value="")
|
||||
self.__ent_name = ttk.Entry(self.__frame, textvariable=self.__var_name)
|
||||
self.__ent_name.config(width=50)
|
||||
self.__ent_name.grid(row=0, column=1, padx=(4, 4), sticky=tkinter.E+tkinter.W)
|
||||
|
||||
# Module credit
|
||||
tkinter.Label(self.__frame, text = "Credit", ).grid(row=0, column=2, padx=(0, 4), sticky=tkinter.W)
|
||||
self.__var_credit = tkinter.StringVar(value="")
|
||||
self.__ent_credit = ttk.Entry(self.__frame, textvariable=self.__var_credit)
|
||||
self.__ent_credit['state'] = 'disabled'
|
||||
self.__ent_credit.config(width=5)
|
||||
self.__ent_credit.grid(row=0, column=3, padx=(4, 4), sticky=tkinter.E+tkinter.W)
|
||||
|
||||
# Grade
|
||||
tkinter.Label(self.__frame, text = "Grade", ).grid(row=0, column=4, padx=(0, 4), sticky=tkinter.W)
|
||||
self.__var_grade = tk.StringVar()
|
||||
self.__om_grade = ttk.Combobox(self.__frame, textvariable=self.__var_grade)
|
||||
self.__om_grade.set('Please select')
|
||||
self.__om_grade['values'] = [gpa for gpa in GPA_MAPPING.keys()]
|
||||
self.__om_grade['state'] = 'disabled'
|
||||
self.__om_grade.config(width=15)
|
||||
self.__om_grade.grid(row=0, column=5, padx=(0, 4), sticky=tkinter.W)
|
||||
self.__om_grade.bind('<<ComboboxSelected>>', self.on_grade_changed)
|
||||
|
||||
def on_grade_changed(self, _):
|
||||
# Get GPA based on combo box value
|
||||
letter_grade = list(GPA_MAPPING.keys())[self.__om_grade.current()]
|
||||
num_grade = GPA_MAPPING[letter_grade]
|
||||
self.__gpa = num_grade
|
||||
self.__command()
|
||||
16
daniel_jo/ITP4459_assignment_2023/src/readme.md
Normal file
16
daniel_jo/ITP4459_assignment_2023/src/readme.md
Normal file
@@ -0,0 +1,16 @@
|
||||
```bash
|
||||
cd docker
|
||||
docker compose build
|
||||
docker compose up -d
|
||||
cd ..
|
||||
|
||||
# after docker up and running
|
||||
cd python
|
||||
pipenv shell
|
||||
pipenv sync
|
||||
|
||||
# inside pip environment
|
||||
python main_db.py
|
||||
python main_gui.py
|
||||
|
||||
```
|
||||
19
daniel_jo/ITP4459_assignment_2023/src/scripts/dev.ps1
Normal file
19
daniel_jo/ITP4459_assignment_2023/src/scripts/dev.ps1
Normal file
@@ -0,0 +1,19 @@
|
||||
pushd docker
|
||||
|
||||
docker-compose pull
|
||||
|
||||
docker-compose build
|
||||
|
||||
docker-compose kill
|
||||
docker-compose down
|
||||
timeout 1
|
||||
|
||||
rmdir docker\volumes
|
||||
|
||||
docker-compose up -d
|
||||
|
||||
# docker-compose logs -f
|
||||
|
||||
# docker-compose exec -it ubuntu bash
|
||||
|
||||
popd
|
||||
106
daniel_jo/ITP4459_assignment_2023/tests/case.md
Normal file
106
daniel_jo/ITP4459_assignment_2023/tests/case.md
Normal file
@@ -0,0 +1,106 @@
|
||||
# TEST CASE:
|
||||
|
||||
- Name: Import data to MySQL database server
|
||||
- Procedure
|
||||
- Configure DB connection by editing `constants.py`
|
||||
- Run `main_db.py`
|
||||
- Start DBMS of your choice and validate the result. (phpmyadmin)
|
||||
- Expected output
|
||||
- Python file ran without errors and the database,
|
||||
- tables and records are successfully created in database.
|
||||
|
||||
- Name: GUI startup
|
||||
- Procedure
|
||||
- Run application via `main_gui.py`
|
||||
- do nothing
|
||||
|
||||
- Expected output
|
||||
- show "Please select" at programme option menu
|
||||
- show "Please select" at Semester option menu
|
||||
- a empty list of module shown
|
||||
|
||||
- Name: Select programme
|
||||
- Procedure
|
||||
- Run application via `main_gui.py`
|
||||
- click programme option menu
|
||||
- watch option available in option menu
|
||||
|
||||
- Expected output
|
||||
- show a list of programme available in DB
|
||||
|
||||
- Name: after user select programme, user proceed to Select semester
|
||||
- Procedure
|
||||
- Run application via `main_gui.py`
|
||||
- click programme option menu
|
||||
- select "Higher Diploma in Cybersecurity" in option menu
|
||||
- watch options available in "Semester"
|
||||
|
||||
- Expected output
|
||||
- 1,2,4,5 should be available in semester list
|
||||
|
||||
- Name: user Selected semester, user proceed to input Grade
|
||||
- Procedure
|
||||
- run procedure "after user select programme, user proceed to Select semester"
|
||||
- select semester "1"
|
||||
|
||||
- Expected output
|
||||
- the modules available to semester should be listed in the "Modules" frame
|
||||
- the "Please Select" should be shown in every "Grade" option menu.
|
||||
- a empty box should be displayed in the Module, Credit and Grade for every modules row for the rest
|
||||
|
||||
- Name: user input grade, get total GPA
|
||||
- Procedure
|
||||
- run procedure "user Selected semester, user proceed to input Grade"
|
||||
- input all gpa
|
||||
|
||||
- Expected output
|
||||
- watch the label at the bottom of the form.
|
||||
- when not all gpa got answered, the gpa won't be updated (keep 0.00)
|
||||
- when app gpa answered, the result gpa will be displayed (1.23)
|
||||
|
||||
- Name: Screen update
|
||||
- Procedure
|
||||
- Run same procedure as stated in "GPA Calculation Test #2"
|
||||
- re-select "Semester" to 2
|
||||
- re-select "Semester" to 4
|
||||
|
||||
- Expected output
|
||||
- when "Semester" is 2
|
||||
- Module list should be updated (3 -> 2 items)
|
||||
- Credit list should be updated (3 -> 2 items)
|
||||
- Grade list should be selected "Please select"
|
||||
- when "Semester" is 4
|
||||
- Module list should be updated (2 -> 1 items)
|
||||
- Credit list should be updated (2 -> 1 items)
|
||||
- Grade list should be selected "Please select"
|
||||
|
||||
- Name: GPA Calculation Test #1
|
||||
- Procedure
|
||||
- Run application via `main_gui.py`
|
||||
- Perform semester GPA calculation with the input data stated in excepted output.
|
||||
- Validate the result with expected output.
|
||||
|
||||
- Input:
|
||||
- Programme: Higher Diploma in Cybersecurity; Semester: 2
|
||||
- Subjects:
|
||||
- "Vocational Chinese Communication: Putonghua Presentations, Administrative and Technical Text Writing" → A-
|
||||
- "English & Communication: Reports" → C+
|
||||
|
||||
- Expected output
|
||||
- Semester GPA: 3.00 shown at bottom
|
||||
|
||||
- Name: GPA Calculation Test #2
|
||||
- Procedure
|
||||
- Run application via `main_gui.py`
|
||||
- Perform semester GPA calculation with the input data stated in excepted output.
|
||||
- Validate the result with expected output.
|
||||
|
||||
- Input:
|
||||
- Programme: Higher Diploma in AI and Smart Technology; Semester: 1
|
||||
- Subjects:
|
||||
- "Vocational Chinese Communication: Putonghua Conversation and Reports" → A
|
||||
- "English & Communication: Workplace Interaction" → C-
|
||||
- "English & Communication: Workplace Correspondance" → B+
|
||||
|
||||
- Expected output
|
||||
- Semester GPA: 3.00 shown at bottom
|
||||
1
daniel_jo/ITP4459_assignment_2023/tests/helloworld.py
Normal file
1
daniel_jo/ITP4459_assignment_2023/tests/helloworld.py
Normal file
@@ -0,0 +1 @@
|
||||
print('helloworld')
|
||||
35
daniel_jo/ITP4459_assignment_2023/tests/options_test.py
Normal file
35
daniel_jo/ITP4459_assignment_2023/tests/options_test.py
Normal file
@@ -0,0 +1,35 @@
|
||||
import tkinter as tk
|
||||
from tkinter import ttk
|
||||
|
||||
root = tk.Tk()
|
||||
|
||||
options = ['Option 1', 'Option 2', 'Option 3']
|
||||
variable = tk.StringVar(value=options[0])
|
||||
|
||||
def on_option_changed(*args):
|
||||
print('helloworld')
|
||||
# This function will be called whenever the selected option changes
|
||||
# We will use it to update the option list based on the selected option
|
||||
|
||||
# Get the selected option
|
||||
selected_option = variable.get()
|
||||
|
||||
# Update the option list based on the selected option
|
||||
if selected_option == 'Option 1':
|
||||
new_options = ['Option 1', 'Option 2', 'Option 3']
|
||||
elif selected_option == 'Option 2':
|
||||
new_options = ['Option 4', 'Option 5', 'Option 6']
|
||||
else:
|
||||
new_options = ['Option 7', 'Option 8', 'Option 9']
|
||||
|
||||
# Update the option list in the widget
|
||||
combobox.configure(values=new_options)
|
||||
|
||||
# Create the widget
|
||||
combobox = ttk.Combobox(root, textvariable=variable, values=options)
|
||||
combobox.pack()
|
||||
|
||||
# Bind the on_option_changed function to the variable
|
||||
variable.trace('w', on_option_changed)
|
||||
|
||||
root.mainloop()
|
||||
41
daniel_jo/ITP4459_assignment_2023/tests/test_combobox.py
Normal file
41
daniel_jo/ITP4459_assignment_2023/tests/test_combobox.py
Normal file
@@ -0,0 +1,41 @@
|
||||
import tkinter as tk
|
||||
from tkinter import ttk
|
||||
from tkinter.messagebox import showinfo
|
||||
from calendar import month_name
|
||||
|
||||
root = tk.Tk()
|
||||
|
||||
# config the root window
|
||||
root.geometry('300x200')
|
||||
root.resizable(False, False)
|
||||
root.title('Combobox Widget')
|
||||
|
||||
# label
|
||||
label = ttk.Label(text="Please select a month:")
|
||||
label.pack(fill=tk.X, padx=5, pady=5)
|
||||
|
||||
# create a combobox
|
||||
selected_month = tk.StringVar()
|
||||
month_cb = ttk.Combobox(root, textvariable=selected_month)
|
||||
|
||||
# get first 3 letters of every month name
|
||||
month_cb['values'] = [month_name[m][0:3] for m in range(1, 13)]
|
||||
|
||||
# prevent typing a value
|
||||
month_cb['state'] = 'readonly'
|
||||
|
||||
# place the widget
|
||||
month_cb.pack(fill=tk.X, padx=5, pady=5)
|
||||
|
||||
|
||||
# bind the selected value changes
|
||||
def month_changed(event):
|
||||
""" handle the month changed event """
|
||||
showinfo(
|
||||
title='Result',
|
||||
message=f'You selected {selected_month.get()}!'
|
||||
)
|
||||
|
||||
month_cb.bind('<<ComboboxSelected>>', month_changed)
|
||||
|
||||
root.mainloop()
|
||||
32
daniel_jo/ITP4459_assignment_2023/tests/test_models.py
Normal file
32
daniel_jo/ITP4459_assignment_2023/tests/test_models.py
Normal file
@@ -0,0 +1,32 @@
|
||||
from pprint import pprint
|
||||
|
||||
import constants
|
||||
|
||||
from db import MySQLConnection
|
||||
from models import Programme
|
||||
from models import Module
|
||||
|
||||
db = MySQLConnection(
|
||||
host=constants.DB_HOST,
|
||||
user=constants.DB_USER,
|
||||
password=constants.DB_PASS
|
||||
)
|
||||
|
||||
list_program = Programme.fetchall(db)
|
||||
list_module = Module.fetchall(db)
|
||||
|
||||
# print(list_program[0].get_modules())
|
||||
# print(list_program[0].get_modules_by_semester(1))
|
||||
# print(list_program[0].get_name())
|
||||
# __eq__
|
||||
# print(list_program[0] == list_program[0])
|
||||
|
||||
# modules
|
||||
# print(list_program[0].get_modules()[1][0][0].get_id())
|
||||
# pprint(list_program[0].get_modules())
|
||||
# pprint(list_program[0].get_modules_by_semester(1)[0])
|
||||
# print(list_program[0].get_modules()[1][0][0].get_credit())
|
||||
|
||||
db.close()
|
||||
|
||||
#
|
||||
BIN
daniel_jo/ITP4459_assignment_2023/tests/testcase.docx
Normal file
BIN
daniel_jo/ITP4459_assignment_2023/tests/testcase.docx
Normal file
Binary file not shown.
Reference in New Issue
Block a user