update,
This commit is contained in:
12
ITP4459_assignment_2023/.editorconfig
Normal file
12
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
ITP4459_assignment_2023/.gitignore
vendored
Normal file
181
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
ITP4459_assignment_2023/docs/gars.pdf
Normal file
BIN
ITP4459_assignment_2023/docs/gars.pdf
Normal file
Binary file not shown.
5
ITP4459_assignment_2023/meta.md
Normal file
5
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
ITP4459_assignment_2023/notes/Assignment2223_student_v2_original/Pipfile.lock
generated
Normal file
101
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
|
BIN
ITP4459_assignment_2023/notes/Chap01 - Object and Class.pptx
Normal file
BIN
ITP4459_assignment_2023/notes/Chap01 - Object and Class.pptx
Normal file
Binary file not shown.
BIN
ITP4459_assignment_2023/notes/Chap02 - Initializer.pptx
Normal file
BIN
ITP4459_assignment_2023/notes/Chap02 - Initializer.pptx
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
ITP4459_assignment_2023/notes/Chap06 - Database Connection.pptx
Normal file
BIN
ITP4459_assignment_2023/notes/Chap06 - Database Connection.pptx
Normal file
Binary file not shown.
BIN
ITP4459_assignment_2023/notes/Chap07a - Tkinter Part 1.pptx
Normal file
BIN
ITP4459_assignment_2023/notes/Chap07a - Tkinter Part 1.pptx
Normal file
Binary file not shown.
BIN
ITP4459_assignment_2023/notes/Chap07b - Tkinter Part 2.pptx
Normal file
BIN
ITP4459_assignment_2023/notes/Chap07b - Tkinter Part 2.pptx
Normal file
Binary file not shown.
BIN
ITP4459_assignment_2023/notes/Chap08 - Web Scraping.pptx
Normal file
BIN
ITP4459_assignment_2023/notes/Chap08 - Web Scraping.pptx
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
ITP4459_assignment_2023/notes/Lab_02a - Initializer_Answers.doc
Normal file
BIN
ITP4459_assignment_2023/notes/Lab_02a - Initializer_Answers.doc
Normal file
Binary file not shown.
BIN
ITP4459_assignment_2023/notes/Lab_02b - Initializer_Answers.doc
Normal file
BIN
ITP4459_assignment_2023/notes/Lab_02b - Initializer_Answers.doc
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
ITP4459_assignment_2023/notes/Lab_04b - Inheritance_Answers.doc
Normal file
BIN
ITP4459_assignment_2023/notes/Lab_04b - Inheritance_Answers.doc
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
ITP4459_assignment_2023/notes/Lab_07a - Tkinter_Answers.doc
Normal file
BIN
ITP4459_assignment_2023/notes/Lab_07a - Tkinter_Answers.doc
Normal file
Binary file not shown.
BIN
ITP4459_assignment_2023/notes/Lab_07b - Tkinter_Answers.doc
Normal file
BIN
ITP4459_assignment_2023/notes/Lab_07b - Tkinter_Answers.doc
Normal file
Binary file not shown.
BIN
ITP4459_assignment_2023/notes/Lab_08a - Web Scraping_Answers.doc
Normal file
BIN
ITP4459_assignment_2023/notes/Lab_08a - Web Scraping_Answers.doc
Normal file
Binary file not shown.
Binary file not shown.
587
ITP4459_assignment_2023/package-lock.json
generated
Normal file
587
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
ITP4459_assignment_2023/package.json
Normal file
17
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
ITP4459_assignment_2023/quotation.md
Normal file
41
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.
|
34
ITP4459_assignment_2023/src/docker/docker-compose.yml
Normal file
34
ITP4459_assignment_2023/src/docker/docker-compose.yml
Normal file
@@ -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
ITP4459_assignment_2023/src/python/Pipfile
Normal file
16
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
ITP4459_assignment_2023/src/python/Pipfile.lock
generated
Normal file
214
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
ITP4459_assignment_2023/src/python/app.py
Normal file
190
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
ITP4459_assignment_2023/src/python/constants.py
Normal file
26
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
ITP4459_assignment_2023/src/python/db.py
Normal file
78
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
ITP4459_assignment_2023/src/python/main_db.py
Normal file
99
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
ITP4459_assignment_2023/src/python/main_gui.py
Normal file
42
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
ITP4459_assignment_2023/src/python/models.py
Normal file
143
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
ITP4459_assignment_2023/src/python/readme.md
Normal file
11
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
ITP4459_assignment_2023/src/python/run.bat
Normal file
10
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
ITP4459_assignment_2023/src/python/widgets.py
Normal file
92
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
ITP4459_assignment_2023/src/readme.md
Normal file
16
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
ITP4459_assignment_2023/src/scripts/dev.ps1
Normal file
19
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
ITP4459_assignment_2023/tests/case.md
Normal file
106
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
ITP4459_assignment_2023/tests/helloworld.py
Normal file
1
ITP4459_assignment_2023/tests/helloworld.py
Normal file
@@ -0,0 +1 @@
|
||||
print('helloworld')
|
35
ITP4459_assignment_2023/tests/options_test.py
Normal file
35
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
ITP4459_assignment_2023/tests/test_combobox.py
Normal file
41
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
ITP4459_assignment_2023/tests/test_models.py
Normal file
32
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
ITP4459_assignment_2023/tests/testcase.docx
Normal file
BIN
ITP4459_assignment_2023/tests/testcase.docx
Normal file
Binary file not shown.
Reference in New Issue
Block a user