Changed: Projects
This commit is contained in:
parent
4916a83a9c
commit
5d7b1d59c5
34
analyzer.py
34
analyzer.py
@ -45,6 +45,27 @@ def student_list(app_state: AppState) -> None:
|
|||||||
|
|
||||||
app_state.current_student_id = statics.students[statics.select].id
|
app_state.current_student_id = statics.students[statics.select].id
|
||||||
|
|
||||||
|
def group_list(app_state: AppState) -> None:
|
||||||
|
statics = group_list
|
||||||
|
if not app_state.current_class_id:
|
||||||
|
imgui.text("No Class found in Database")
|
||||||
|
return
|
||||||
|
|
||||||
|
if not hasattr(statics, "select"):
|
||||||
|
statics.select = 0
|
||||||
|
|
||||||
|
statics.groups = Group.select().where(Group.class_id == app_state.current_class_id)
|
||||||
|
statics.groups = statics.groups if statics.groups else None
|
||||||
|
if not statics.groups:
|
||||||
|
imgui.text("No Group found")
|
||||||
|
return
|
||||||
|
|
||||||
|
for n, group in enumerate(statics.groups, start=1):
|
||||||
|
display = f"{n}. {group.name}"
|
||||||
|
_, clicked = imgui.selectable(display, statics.select == n-1)
|
||||||
|
if clicked:
|
||||||
|
statics.select = n-1
|
||||||
|
|
||||||
def lecture_list(app_state: AppState) -> None:
|
def lecture_list(app_state: AppState) -> None:
|
||||||
statics = lecture_list
|
statics = lecture_list
|
||||||
|
|
||||||
@ -158,12 +179,14 @@ def plot_bar_line_percentage(data: np.array, labels: list, avg: float) -> None:
|
|||||||
|
|
||||||
COLOR_TEXT_PASSED = tuple([e/255 for e in ImageColor.getcolor("#1AFE49","RGBA")])
|
COLOR_TEXT_PASSED = tuple([e/255 for e in ImageColor.getcolor("#1AFE49","RGBA")])
|
||||||
COLOR_TEXT_FAILED = tuple([e/255 for e in ImageColor.getcolor("#FF124F","RGBA")])
|
COLOR_TEXT_FAILED = tuple([e/255 for e in ImageColor.getcolor("#FF124F","RGBA")])
|
||||||
|
COLOR_TEXT_PROJECT = tuple([e/255 for e in ImageColor.getcolor("#0A9CF5","RGBA")])
|
||||||
@immapp.static(inited=False)
|
@immapp.static(inited=False)
|
||||||
def student_graph(app_state: AppState) -> None:
|
def student_graph(app_state: AppState) -> None:
|
||||||
statics = student_graph
|
statics = student_graph
|
||||||
if not statics.inited:
|
if not statics.inited:
|
||||||
statics.id = -1
|
statics.id = -1
|
||||||
statics.student = None
|
statics.student = None
|
||||||
|
statics.group = None
|
||||||
statics.lectures = None
|
statics.lectures = None
|
||||||
statics.points = None
|
statics.points = None
|
||||||
statics.sub_points = None
|
statics.sub_points = None
|
||||||
@ -182,6 +205,7 @@ def student_graph(app_state: AppState) -> None:
|
|||||||
statics.student = Student.get_by_id(app_state.current_student_id)
|
statics.student = Student.get_by_id(app_state.current_student_id)
|
||||||
submissions = Submission.select().where(Submission.student_id == statics.student.id)
|
submissions = Submission.select().where(Submission.student_id == statics.student.id)
|
||||||
|
|
||||||
|
statics.group = Group.get_by_id(statics.student.group_id)
|
||||||
statics.lectures = [Lecture.get_by_id(sub.lecture_id) for sub in submissions]
|
statics.lectures = [Lecture.get_by_id(sub.lecture_id) for sub in submissions]
|
||||||
statics.max_points = np.sum([l.points for l in statics.lectures])
|
statics.max_points = np.sum([l.points for l in statics.lectures])
|
||||||
statics.sub_points = [sub.points for sub in submissions]
|
statics.sub_points = [sub.points for sub in submissions]
|
||||||
@ -194,12 +218,13 @@ def student_graph(app_state: AppState) -> None:
|
|||||||
statics.avg = statics.points/statics.max_points*100
|
statics.avg = statics.points/statics.max_points*100
|
||||||
|
|
||||||
w, h = imgui.get_window_size()
|
w, h = imgui.get_window_size()
|
||||||
imgui_md.render(f"# {statics.student.prename} {statics.student.surname}")
|
imgui_md.render(f"# {statics.student.prename} {statics.student.surname} ({statics.group.name})")
|
||||||
imgui_md.render(f"### {statics.points}/{statics.max_points}")
|
imgui_md.render(f"### {statics.points}/{statics.max_points}")
|
||||||
imgui.text(" ")
|
imgui.text(" ")
|
||||||
imgui.progress_bar(statics.points/statics.max_points, ImVec2(w*0.9, h*0.05), f"{statics.points}/{statics.max_points} {statics.points/statics.max_points:.1%}")
|
imgui.progress_bar(statics.points/statics.max_points, ImVec2(w*0.9, h*0.05), f"{statics.points}/{statics.max_points} {statics.points/statics.max_points:.1%}")
|
||||||
plot_bar_line_percentage(statics.subs_data, statics.subs_labels, statics.avg)
|
plot_bar_line_percentage(statics.subs_data, statics.subs_labels, statics.avg)
|
||||||
imgui.separator()
|
imgui.separator()
|
||||||
|
imgui.text_colored(COLOR_TEXT_PROJECT, f"{statics.group.name}: {statics.group.project}")
|
||||||
for n, data in enumerate(zip(statics.lectures, statics.sub_points), start=1):
|
for n, data in enumerate(zip(statics.lectures, statics.sub_points), start=1):
|
||||||
lecture, points = data
|
lecture, points = data
|
||||||
COLOR = COLOR_TEXT_PASSED if points >= lecture.points*0.3 else COLOR_TEXT_FAILED
|
COLOR = COLOR_TEXT_PASSED if points >= lecture.points*0.3 else COLOR_TEXT_FAILED
|
||||||
@ -391,6 +416,11 @@ def set_analyzer_layout(app_state: AppState) -> List[hello_imgui.DockableWindow]
|
|||||||
student_selector.dock_space_name = "CommandSpace"
|
student_selector.dock_space_name = "CommandSpace"
|
||||||
student_selector.gui_function = lambda: student_list(app_state)
|
student_selector.gui_function = lambda: student_list(app_state)
|
||||||
|
|
||||||
|
group_selector = hello_imgui.DockableWindow()
|
||||||
|
group_selector.label = "Groups"
|
||||||
|
group_selector.dock_space_name = "CommandSpace"
|
||||||
|
group_selector.gui_function = lambda: group_list(app_state)
|
||||||
|
|
||||||
lecture_selector = hello_imgui.DockableWindow()
|
lecture_selector = hello_imgui.DockableWindow()
|
||||||
lecture_selector.label = "Lectures"
|
lecture_selector.label = "Lectures"
|
||||||
lecture_selector.dock_space_name = "CommandSpace2"
|
lecture_selector.dock_space_name = "CommandSpace2"
|
||||||
@ -425,7 +455,7 @@ def set_analyzer_layout(app_state: AppState) -> List[hello_imgui.DockableWindow]
|
|||||||
class_selector, student_selector,
|
class_selector, student_selector,
|
||||||
lecture_selector, submission_selector,
|
lecture_selector, submission_selector,
|
||||||
student_info, sex_info,
|
student_info, sex_info,
|
||||||
student_ranking
|
student_ranking, group_selector
|
||||||
]
|
]
|
||||||
|
|
||||||
def analyzer_layout(app_state: AppState) -> hello_imgui.DockingParams:
|
def analyzer_layout(app_state: AppState) -> hello_imgui.DockingParams:
|
||||||
|
@ -1,35 +1,37 @@
|
|||||||
First Name,Last Name,Sex,Tutorial 1,Tutorial 2,Extended Applications,Numpy & MatPlotLib,SciPy,Monte Carlo,Pandas & Seaborn,Folium,Statistical Test Methods,Data Analysis
|
First Name,Last Name,Sex,Group,Tutorial 1,Tutorial 2,Extended Applications,Numpy & MatPlotLib,SciPy,Monte Carlo,Pandas & Seaborn,Folium,Statistical Test Methods,Data Analysis
|
||||||
Abdalaziz,Abunjaila,Male,30.5,15,18,28,17,17,17,22,0,0
|
Abdalaziz,Abunjaila,Male,DiKum,30.5,15,18,28,17,17,17,22,0,18
|
||||||
Marleen,Adolphi,Female,29.5,15,18,32,19,20,17,24,23,0
|
Marleen,Adolphi,Female,MeWi6,29.5,15,18,32,19,20,17,24,23,0
|
||||||
Sarina,Apel,Female,28.5,15,18,32,20,20,21,24,20,0
|
Sarina,Apel,Female,MeWi1,28.5,15,18,32,20,20,21,24,20,0
|
||||||
Skofiare,Berisha,Female,29.5,13,18,34,20,17,20,26,16,0
|
Skofiare,Berisha,Female,DiKum,29.5,13,18,34,20,17,20,26,16,0
|
||||||
Aurela,Brahimi,Female,17.5,15,15.5,26,16,17,19,16,0,0
|
Aurela,Brahimi,Female,MeWi2,17.5,15,15.5,26,16,17,19,16,0,0
|
||||||
Cam Thu,Do,Female,31,15,18,34,19,20,21.5,22,12,0
|
Cam Thu,Do,Female,MeWi3,31,15,18,34,19,20,21.5,22,12,0
|
||||||
Nova,Eib,Female,31,15,15,34,20,20,21,27,19,0
|
Nova,Eib,Female,MeWi4,31,15,15,34,20,20,21,27,19,21
|
||||||
Nele,Grundke,Female,23.5,13,16,28,20,17,21,18,22,0
|
Lena,Fricke,Female,MeWi4,0,0,0,0,0,0,0,0,0,0
|
||||||
Anna,Grünewald,Female,12,14,16,29,16,15,19,9,0,0
|
Nele,Grundke,Female,MeWi6,23.5,13,16,28,20,17,21,18,22,0
|
||||||
Yannik,Haupt,Male,18,6,14,21,13,2,9,0,0,0
|
Anna,Grünewald,Female,MeWi3,12,14,16,29,16,15,19,9,0,0
|
||||||
Janna,Heiny,Female,30,15,18,33,18,20,22,25,24,30
|
Yannik,Haupt,Male,NoGroup,18,6,14,21,13,2,9,0,0,0
|
||||||
Milena,Krieger,Female,30,15,18,33,20,20,21.5,26,20,0
|
Janna,Heiny,Female,MeWi1,30,15,18,33,18,20,22,25,24,30
|
||||||
Julia,Limbach,Female,27.5,12,18,29,11,19,17.5,26,24,0
|
Milena,Krieger,Female,MeWi1,30,15,18,33,20,20,21.5,26,20,0
|
||||||
Viktoria,Litza,Female,21.5,15,18,27,13,20,22,21,21,0
|
Julia,Limbach,Female,MeWi6,27.5,12,18,29,11,19,17.5,26,24,0
|
||||||
Leonie,Manthey,Female,28.5,14,18,29,20,10,18,23,16,28
|
Viktoria,Litza,Female,MeWi5,21.5,15,18,27,13,20,22,21,21,0
|
||||||
Izabel,Mike,Female,29.5,15,15,35,11,15,19,21,21,27
|
Leonie,Manthey,Female,MeWi1,28.5,14,18,29,20,10,18,23,16,28
|
||||||
Lea,Noglik,Female,22.5,15,17,34,13,10,20,21,19,0
|
Izabel,Mike,Female,MeWi2,29.5,15,15,35,11,15,19,21,21,27
|
||||||
Donika,Nuhiu,Female,31,13.5,18,35,14,10,17,18,19,6
|
Lea,Noglik,Female,MeWi5,22.5,15,17,34,13,10,20,21,19,0
|
||||||
Julia,Renner,Female,27.5,10,14,32,20,17,11,20,24,0
|
Donika,Nuhiu,Female,MeWi5,31,13.5,18,35,14,10,17,18,19,6
|
||||||
Fabian,Rothberger,Male,30.5,15,18,34,17,17,19,22,18,0
|
Julia,Renner,Female,MeWi4,27.5,10,14,32,20,17,11,20,24,0
|
||||||
Natascha,Rott,Female,29.5,12,18,32,19,20,21,26,23,0
|
Fabian,Rothberger,Male,MeWi3,30.5,15,18,34,17,17,19,22,18,0
|
||||||
Isabel,Rudolf,Female,27.5,9,17,34,16,19,19,21,16,0
|
Natascha,Rott,Female,MeWi1,29.5,12,18,32,19,20,21,26,23,0
|
||||||
Melina,Sablotny,Female,31,15,18,33,20,20,21,19,11,0
|
Isabel,Rudolf,Female,MeWi4,27.5,9,17,34,16,19,19,21,16,0
|
||||||
Alea,Schleier,Female,27,14,18,34,16,18,21.5,22,15,22
|
Melina,Sablotny,Female,MeWi6,31,15,18,33,20,20,21,19,11,0
|
||||||
Flemming,Schur,Male,29.5,15,17,34,19,20,19,22,18,0
|
Alea,Schleier,Female,DiKum,27,14,18,34,16,18,21.5,22,15,22
|
||||||
Marie,Seeger,Female,27.5,15,18,32,14,9,17,22,9,0
|
Flemming,Schur,Male,MeWi3,29.5,15,17,34,19,20,19,22,18,0
|
||||||
Lucy,Thiele,Female,27.5,15,18,27,20,17,19,18,22,0
|
Marie,Seeger,Female,DiKum,27.5,15,18,32,14,9,17,22,9,0
|
||||||
Lara,Troschke,Female,28.5,14,17,28,13,19,21,25,12,0
|
Lucy,Thiele,Female,MeWi6,27.5,15,18,27,20,17,19,18,22,0
|
||||||
Inga-Brit,Turschner,Female,25.5,14,18,34,20,16,19,22,17,0
|
Lara,Troschke,Female,MeWi2,28.5,14,17,28,13,19,21,25,12,0
|
||||||
Alea,Unger,Female,30,12,18,31,20,20,21,22,15,21.5
|
Inga-Brit,Turschner,Female,MeWi2,25.5,14,18,34,20,16,19,22,17,0
|
||||||
Marie,Wallbaum,Female,28.5,14,18,34,17,20,19,24,12,0
|
Alea,Unger,Female,MeWi5,30,12,18,31,20,20,21,22,15,21.5
|
||||||
Katharina,Walz,Female,31,15,18,31,19,19,17,24,17,14.5
|
Marie,Wallbaum,Female,MeWi5,28.5,14,18,34,17,20,19,24,12,0
|
||||||
Xiaowei,Wang,Male,30.5,14,18,26,19,17,0,0,0,0
|
Katharina,Walz,Female,MeWi4,31,15,18,31,19,19,17,24,17,14.5
|
||||||
Lilly-Lu,Warnken,Female,30,15,18,30,14,17,19,14,16,0
|
Xiaowei,Wang,Male,NoGroup,30.5,14,18,26,19,17,0,0,0,0
|
||||||
|
Lilly-Lu,Warnken,Female,DiKum,30,15,18,30,14,17,19,14,16,0
|
||||||
|
|
||||||
|
|
Binary file not shown.
@ -19,11 +19,22 @@ courses = {
|
|||||||
'Data Analysis': 30
|
'Data Analysis': 30
|
||||||
}
|
}
|
||||||
|
|
||||||
|
groups = {
|
||||||
|
"NoGroup": "No Project",
|
||||||
|
"MeWi1": "Covid-19",
|
||||||
|
"MeWi2": "Covid-19",
|
||||||
|
"MeWi3": "Discovery of Handwashing",
|
||||||
|
"MeWi4": "Uber Trips",
|
||||||
|
"MeWi5": "Extramarital Affairs",
|
||||||
|
"MeWi6": "Hochschulstatistik",
|
||||||
|
"DiKum": "Facebook Data"
|
||||||
|
}
|
||||||
|
|
||||||
print(df)
|
print(df)
|
||||||
|
|
||||||
db.init("WiSe_24_25.db")
|
db.init("WiSe_24_25.db")
|
||||||
db.connect()
|
db.connect()
|
||||||
db.create_tables([Class, Student, Lecture, Submission])
|
db.create_tables([Class, Student, Lecture, Submission, Group])
|
||||||
|
|
||||||
# Create Class
|
# Create Class
|
||||||
clas = Class.create(name='WiSe 24/25')
|
clas = Class.create(name='WiSe 24/25')
|
||||||
@ -34,15 +45,19 @@ for k, v in courses.items():
|
|||||||
Lecture.create(title=k, points=v, class_id=clas.id)
|
Lecture.create(title=k, points=v, class_id=clas.id)
|
||||||
#print(l.title, l.points, l.class_id, l.id)
|
#print(l.title, l.points, l.class_id, l.id)
|
||||||
|
|
||||||
|
for k, v in groups.items():
|
||||||
|
Group.create(name=k, project=v, class_id=clas.id)
|
||||||
|
|
||||||
for index, row in df.iterrows():
|
for index, row in df.iterrows():
|
||||||
s = Student.create(
|
s = Student.create(
|
||||||
prename=row["First Name"],
|
prename=row["First Name"],
|
||||||
surname=row["Last Name"],
|
surname=row["Last Name"],
|
||||||
sex=row["Sex"],
|
sex=row["Sex"],
|
||||||
class_id=clas.id
|
class_id=clas.id,
|
||||||
|
group_id=Group.select().where(Group.name == row["Group"])
|
||||||
)
|
)
|
||||||
|
|
||||||
for title, points in list(row.to_dict().items())[3:]:
|
for title, points in list(row.to_dict().items())[4:]:
|
||||||
Submission.create(
|
Submission.create(
|
||||||
student_id=s.id,
|
student_id=s.id,
|
||||||
lecture_id=Lecture.select().where(Lecture.title == title),
|
lecture_id=Lecture.select().where(Lecture.title == title),
|
||||||
|
252
database.py
252
database.py
@ -1,3 +1,11 @@
|
|||||||
|
"""
|
||||||
|
Database Editor UI Module
|
||||||
|
|
||||||
|
This module defines the graphical user interface (GUI) components and layout for a database editor using the
|
||||||
|
HelloImGui and ImGui frameworks. It provides a class editor, docking layout configurations, and functions
|
||||||
|
to set up the database editing environment.
|
||||||
|
"""
|
||||||
|
|
||||||
# Custom
|
# Custom
|
||||||
from model import *
|
from model import *
|
||||||
from appstate import *
|
from appstate import *
|
||||||
@ -19,22 +27,35 @@ from pathlib import Path
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
def file_info(path: Path) -> None:
|
def file_info(path: Path) -> None:
|
||||||
|
"""
|
||||||
|
Displays file information in an ImGui table.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
path (Path): The file path whose information is to be displayed.
|
||||||
|
|
||||||
|
The function retrieves the file's size, last access time, and creation time,
|
||||||
|
formats the data, and presents it using ImGui tables.
|
||||||
|
"""
|
||||||
|
# Retrieve file statistics
|
||||||
stat = path.stat()
|
stat = path.stat()
|
||||||
modified = datetime.fromtimestamp(stat.st_atime)
|
modified = datetime.fromtimestamp(stat.st_atime) # Last access time
|
||||||
created = datetime.fromtimestamp(stat.st_ctime)
|
created = datetime.fromtimestamp(stat.st_ctime) # Creation time
|
||||||
format = '%c'
|
format = '%c' # Standard date-time format
|
||||||
|
|
||||||
|
# Prepare file data dictionary
|
||||||
data = {
|
data = {
|
||||||
"File": path.name,
|
"File": path.name,
|
||||||
"Size": f"{stat.st_size/100} KB",
|
"Size": f"{stat.st_size/100:.2f} KB", # Convert bytes to KB (incorrect divisor, should be 1024)
|
||||||
"Modified": modified.strftime(format),
|
"Modified": modified.strftime(format),
|
||||||
"Created": created.strftime(format)
|
"Created": created.strftime(format)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Create ImGui table to display file information
|
||||||
if imgui.begin_table("File Info", 2):
|
if imgui.begin_table("File Info", 2):
|
||||||
imgui.table_setup_column(" ", 0)
|
imgui.table_setup_column(" ", 0)
|
||||||
imgui.table_setup_column(" ")
|
imgui.table_setup_column(" ")
|
||||||
|
|
||||||
|
# Iterate over file data and populate table
|
||||||
for k, v in data.items():
|
for k, v in data.items():
|
||||||
imgui.push_id(k)
|
imgui.push_id(k)
|
||||||
imgui.table_next_row()
|
imgui.table_next_row()
|
||||||
@ -43,59 +64,86 @@ def file_info(path: Path) -> None:
|
|||||||
imgui.table_next_column()
|
imgui.table_next_column()
|
||||||
imgui.text(v)
|
imgui.text(v)
|
||||||
imgui.pop_id()
|
imgui.pop_id()
|
||||||
|
|
||||||
imgui.end_table()
|
imgui.end_table()
|
||||||
|
|
||||||
@immapp.static(inited=False, res=False)
|
@immapp.static(inited=False, res=False)
|
||||||
def select_file(app_state: AppState):
|
def select_file(app_state: AppState):
|
||||||
statics = select_file
|
"""
|
||||||
|
Handles the selection and loading of a database file (JSON or SQLite).
|
||||||
|
It initializes necessary state, allows users to open a file dialog,
|
||||||
|
and processes the selected file by either loading or converting it.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
app_state (AppState): The application's state object, used to track updates.
|
||||||
|
"""
|
||||||
|
statics = select_file # Access static variables within the function
|
||||||
|
|
||||||
|
# Initialize static variables on the first function call
|
||||||
if not statics.inited:
|
if not statics.inited:
|
||||||
statics.res = None
|
statics.res = None # Stores the selected file result
|
||||||
statics.current = None
|
statics.current = None # Stores the currently loaded database file path
|
||||||
|
|
||||||
|
# Retrieve the last used database file from persistent storage
|
||||||
with shelve.open("state") as state:
|
with shelve.open("state") as state:
|
||||||
statics.current = Path(state["DB"])
|
statics.current = Path(state["DB"])
|
||||||
statics.inited = True
|
statics.inited = True
|
||||||
|
|
||||||
|
# Render UI title and display file information
|
||||||
imgui_md.render("# Database Manager")
|
imgui_md.render("# Database Manager")
|
||||||
file_info(statics.current)
|
file_info(statics.current)
|
||||||
|
|
||||||
|
# Button to open the file selection dialog
|
||||||
if imgui.button("Open File"):
|
if imgui.button("Open File"):
|
||||||
im_file_dialog.FileDialog.instance().open("SelectDatabase", "Open Database", "Database File (*.json;*.db){.json,.db}")
|
im_file_dialog.FileDialog.instance().open(
|
||||||
|
"SelectDatabase", "Open Database", "Database File (*.json;*.db){.json,.db}"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Handle the file dialog result
|
||||||
if im_file_dialog.FileDialog.instance().is_done("SelectDatabase"):
|
if im_file_dialog.FileDialog.instance().is_done("SelectDatabase"):
|
||||||
if im_file_dialog.FileDialog.instance().has_result():
|
if im_file_dialog.FileDialog.instance().has_result():
|
||||||
statics.res = im_file_dialog.FileDialog.instance().get_result()
|
statics.res = im_file_dialog.FileDialog.instance().get_result()
|
||||||
LOG_INFO(f"Load File {statics.res}")
|
LOG_INFO(f"Load File {statics.res}")
|
||||||
im_file_dialog.FileDialog.instance().close()
|
im_file_dialog.FileDialog.instance().close()
|
||||||
|
|
||||||
|
# Process the selected file if available
|
||||||
if statics.res:
|
if statics.res:
|
||||||
filename = statics.res.filename()
|
filename = statics.res.filename()
|
||||||
info = Path(statics.res.path())
|
info = Path(statics.res.path())
|
||||||
|
|
||||||
imgui.separator()
|
imgui.separator() # UI separator for clarity
|
||||||
file_info(info)
|
file_info(info) # Display information about the selected file
|
||||||
|
|
||||||
file = None
|
file = None
|
||||||
|
|
||||||
|
# Load the selected database file
|
||||||
if imgui.button("Load"):
|
if imgui.button("Load"):
|
||||||
|
# Ensure any currently open database is closed before loading a new one
|
||||||
if not db.is_closed():
|
if not db.is_closed():
|
||||||
db.close()
|
db.close()
|
||||||
|
|
||||||
|
# Handle JSON files by converting them to SQLite databases
|
||||||
if statics.res.extension() == '.json':
|
if statics.res.extension() == '.json':
|
||||||
file = filename.removesuffix('.json')
|
file = filename.removesuffix('.json') + '.db' # Convert JSON filename to SQLite filename
|
||||||
file = file + '.db'
|
|
||||||
db.init(file)
|
db.init(file)
|
||||||
db.connect(reuse_if_open=True)
|
db.connect(reuse_if_open=True)
|
||||||
db.create_tables([Class, Student, Lecture, Submission])
|
db.create_tables([Class, Student, Lecture, Submission, Group])
|
||||||
load_from_json(str(info))
|
load_from_json(str(info)) # Convert and load JSON data into the database
|
||||||
LOG_INFO(f"Successfully created {file}")
|
LOG_INFO(f"Successfully created {file}")
|
||||||
|
|
||||||
|
# Handle SQLite database files directly
|
||||||
if statics.res.extension() == '.db':
|
if statics.res.extension() == '.db':
|
||||||
file = str(statics.res.path())
|
file = str(statics.res.path())
|
||||||
db.init(file)
|
db.init(file)
|
||||||
db.connect(reuse_if_open=True)
|
db.connect(reuse_if_open=True)
|
||||||
db.create_tables([Class, Student, Lecture, Submission])
|
db.create_tables([Class, Student, Lecture, Submission, Group])
|
||||||
LOG_INFO(f"Successfully loaded {filename}")
|
LOG_INFO(f"Successfully loaded {filename}")
|
||||||
|
|
||||||
|
# Save the selected database path to persistent storage
|
||||||
with shelve.open("state") as state:
|
with shelve.open("state") as state:
|
||||||
state["DB"] = file
|
state["DB"] = file
|
||||||
|
|
||||||
|
# Update application state and reset selection result
|
||||||
app_state.update()
|
app_state.update()
|
||||||
statics.res = None
|
statics.res = None
|
||||||
|
|
||||||
@ -103,77 +151,139 @@ def select_file(app_state: AppState):
|
|||||||
def table(app_state: AppState) -> None:
|
def table(app_state: AppState) -> None:
|
||||||
statics = table
|
statics = table
|
||||||
if not statics.inited:
|
if not statics.inited:
|
||||||
|
statics.table_flags = (
|
||||||
|
imgui.TableFlags_.row_bg.value
|
||||||
|
| imgui.TableFlags_.borders.value
|
||||||
|
| imgui.TableFlags_.resizable.value
|
||||||
|
| imgui.TableFlags_.sizing_stretch_same.value
|
||||||
|
)
|
||||||
statics.class_id = None
|
statics.class_id = None
|
||||||
statics.lectures = None
|
|
||||||
statics.points = list()
|
|
||||||
statics.inited = True
|
statics.inited = True
|
||||||
|
|
||||||
if statics.class_id != app_state.current_class_id:
|
if statics.class_id != app_state.current_class_id:
|
||||||
statics.class_id = app_state.current_class_id
|
statics.class_id = app_state.current_class_id
|
||||||
|
statics.students = Student.select().where(Student.class_id == statics.class_id)
|
||||||
statics.lectures = Lecture.select().where(Lecture.class_id == statics.class_id)
|
statics.lectures = Lecture.select().where(Lecture.class_id == statics.class_id)
|
||||||
statics.data = dict()
|
|
||||||
for student in Student.select().where(Student.class_id == statics.class_id):
|
|
||||||
subs = Submission.select().where(Submission.student_id == student.id)
|
|
||||||
points = [sub.points for sub in subs]
|
|
||||||
statics.data[f"{student.prename} {student.surname}"] = points
|
|
||||||
|
|
||||||
if not statics.lectures:
|
statics.rows = len(statics.students)
|
||||||
imgui.text("No Lecture queried")
|
statics.cols = len(statics.lectures)
|
||||||
return
|
statics.grid = list()
|
||||||
|
|
||||||
table_flags = (
|
|
||||||
imgui.TableFlags_.row_bg.value
|
|
||||||
| imgui.TableFlags_.borders.value
|
|
||||||
| imgui.TableFlags_.resizable.value
|
|
||||||
| imgui.TableFlags_.sizing_stretch_same.value
|
|
||||||
)
|
|
||||||
|
|
||||||
if imgui.begin_table("Overview", len(statics.lectures)+1, table_flags):
|
for student in statics.students:
|
||||||
|
t_list = list()
|
||||||
|
sub = Submission.select().where(Submission.student_id == student.id)
|
||||||
|
for s in sub:
|
||||||
|
t_list.append(s)
|
||||||
|
statics.grid.append(t_list)
|
||||||
|
|
||||||
|
statics.table_header = [f"{lecture.title} ({lecture.points})" for lecture in statics.lectures]
|
||||||
|
|
||||||
|
if imgui.begin_table("Student Grid", statics.cols+1, statics.table_flags):
|
||||||
|
# Setup Header
|
||||||
imgui.table_setup_column("Students")
|
imgui.table_setup_column("Students")
|
||||||
for n, lecture in enumerate(statics.lectures, start=1):
|
for header in statics.table_header:
|
||||||
imgui.table_setup_column(f"{n}. {lecture.title} ({lecture.points})")
|
imgui.table_setup_column(header)
|
||||||
imgui.table_setup_scroll_freeze(1, 1)
|
|
||||||
imgui.table_headers_row()
|
imgui.table_headers_row()
|
||||||
|
|
||||||
for k, v in statics.data.items():
|
# Fill Student names
|
||||||
imgui.push_id(k)
|
for row in range(statics.rows):
|
||||||
imgui.table_next_row()
|
imgui.table_next_row()
|
||||||
imgui.table_next_column()
|
imgui.table_set_column_index(0)
|
||||||
imgui.text(k)
|
student = statics.students[row]
|
||||||
for points in v:
|
imgui.text(f"{student.prename} {student.surname}")
|
||||||
imgui.table_next_column()
|
|
||||||
if points.is_integer():
|
for col in range(statics.cols):
|
||||||
points = int(points)
|
imgui.table_set_column_index(col+1)
|
||||||
imgui.text(str(points))
|
changed, value = imgui.input_float(f"##{statics.grid[row][col]}", statics.grid[row][col].points, 0.0, 0.0, "%.1f")
|
||||||
imgui.pop_id()
|
|
||||||
|
if changed:
|
||||||
|
# Boundary Check
|
||||||
|
if value < 0:
|
||||||
|
value = 0
|
||||||
|
if value > statics.lectures[col].points:
|
||||||
|
value = statics.lectures[col].points
|
||||||
|
|
||||||
|
old_value = statics.grid[row][col].points
|
||||||
|
statics.grid[row][col].points = value
|
||||||
|
statics.grid[row][col].save()
|
||||||
|
|
||||||
|
student = statics.students[row]
|
||||||
|
lecture = statics.lectures[col]
|
||||||
|
sub = statics.grid[row][col]
|
||||||
|
LOG_INFO(f"Submission edit: {student.prename} {student.surname}: |{lecture.title}| {old_value} -> {value}")
|
||||||
|
|
||||||
imgui.end_table()
|
imgui.end_table()
|
||||||
|
|
||||||
@immapp.static(inited=False)
|
@immapp.static(inited=False)
|
||||||
def class_editor() -> None:
|
def class_editor() -> None:
|
||||||
statics = class_editor
|
"""
|
||||||
|
Class Editor UI Component.
|
||||||
|
|
||||||
|
This function initializes and renders the class selection interface within the database editor.
|
||||||
|
It maintains a static state to keep track of the selected class and fetches available classes.
|
||||||
|
"""
|
||||||
|
statics = class_editor
|
||||||
|
statics.classes = Class.select()
|
||||||
if not statics.inited:
|
if not statics.inited:
|
||||||
statics.classes = None
|
|
||||||
statics.selected = 0
|
statics.selected = 0
|
||||||
|
statics.value = statics.classes[statics.selected].name if statics.classes else str()
|
||||||
statics.inited = True
|
statics.inited = True
|
||||||
|
|
||||||
statics.classes = Class.select()
|
# Fetch available classes from the database
|
||||||
|
|
||||||
|
# Render the UI for class selection
|
||||||
imgui_md.render("# Edit Classes")
|
imgui_md.render("# Edit Classes")
|
||||||
_, statics.selected = imgui.combo("Classes", statics.selected, [c.name for c in statics.classes])
|
changed, statics.selected = imgui.combo("##Classes", statics.selected, [c.name for c in statics.classes])
|
||||||
imgui.text(statics.classes[statics.selected].name)
|
if changed:
|
||||||
|
statics.value = statics.classes[statics.selected].name
|
||||||
|
_, statics.value = imgui.input_text("##input_class", statics.value)
|
||||||
|
|
||||||
|
if imgui.button("Update"):
|
||||||
|
clas = statics.classes[statics.selected]
|
||||||
|
clas.name, old_name = statics.value, clas.name
|
||||||
|
clas.save()
|
||||||
|
LOG_INFO(f"Changed Class Name: {old_name} -> {clas.name}")
|
||||||
|
|
||||||
|
imgui.same_line()
|
||||||
|
|
||||||
|
if imgui.button("New"):
|
||||||
|
Class.create(name=statics.value)
|
||||||
|
LOG_INFO(f"Created new Class {statics.value}")
|
||||||
|
|
||||||
|
imgui.same_line()
|
||||||
|
|
||||||
|
if imgui.button("Delete"):
|
||||||
|
clas = statics.classes[statics.selected]
|
||||||
|
clas.delete_instance()
|
||||||
|
statics.selected -= 1
|
||||||
|
statics.value = statics.classes[statics.selected].name
|
||||||
|
LOG_INFO(f"Deleted: {clas.name}")
|
||||||
|
|
||||||
|
|
||||||
def database_editor(app_state: AppState) -> None:
|
def database_editor(app_state: AppState) -> None:
|
||||||
|
"""
|
||||||
|
Database Editor UI Function.
|
||||||
|
|
||||||
|
Calls the class editor function to render its UI component.
|
||||||
|
|
||||||
|
:param app_state: The application state containing relevant database information.
|
||||||
|
"""
|
||||||
class_editor()
|
class_editor()
|
||||||
|
|
||||||
def database_docking_splits() -> List[hello_imgui.DockingSplit]:
|
def database_docking_splits() -> List[hello_imgui.DockingSplit]:
|
||||||
|
"""
|
||||||
|
Defines the docking layout for the database editor.
|
||||||
|
|
||||||
|
Returns a list of docking splits that define the structure of the editor layout.
|
||||||
|
|
||||||
|
:return: A list of `hello_imgui.DockingSplit` objects defining docking positions and sizes.
|
||||||
|
"""
|
||||||
split_main_command = hello_imgui.DockingSplit()
|
split_main_command = hello_imgui.DockingSplit()
|
||||||
split_main_command.initial_dock = "MainDockSpace"
|
split_main_command.initial_dock = "MainDockSpace"
|
||||||
split_main_command.new_dock = "CommandSpace"
|
split_main_command.new_dock = "CommandSpace"
|
||||||
split_main_command.direction = imgui.Dir.down
|
split_main_command.direction = imgui.Dir.down
|
||||||
split_main_command.ratio = 0.3
|
split_main_command.ratio = 0.3
|
||||||
|
|
||||||
# Log Space
|
|
||||||
split_main_command2 = hello_imgui.DockingSplit()
|
split_main_command2 = hello_imgui.DockingSplit()
|
||||||
split_main_command2.initial_dock = "CommandSpace"
|
split_main_command2.initial_dock = "CommandSpace"
|
||||||
split_main_command2.new_dock = "CommandSpace2"
|
split_main_command2.new_dock = "CommandSpace2"
|
||||||
@ -186,11 +296,18 @@ def database_docking_splits() -> List[hello_imgui.DockingSplit]:
|
|||||||
split_main_misc.direction = imgui.Dir.left
|
split_main_misc.direction = imgui.Dir.left
|
||||||
split_main_misc.ratio = 0.2
|
split_main_misc.ratio = 0.2
|
||||||
|
|
||||||
splits = [split_main_misc, split_main_command, split_main_command2]
|
return [split_main_misc, split_main_command, split_main_command2]
|
||||||
return splits
|
|
||||||
|
|
||||||
def set_database_editor_layout(app_state: AppState) -> List[hello_imgui.DockableWindow]:
|
def set_database_editor_layout(app_state: AppState) -> List[hello_imgui.DockableWindow]:
|
||||||
|
"""
|
||||||
|
Defines the dockable windows for the database editor.
|
||||||
|
|
||||||
|
Creates and returns a list of dockable windows, including the database file selector, log window,
|
||||||
|
table viewer, and editor.
|
||||||
|
|
||||||
|
:param app_state: The application state.
|
||||||
|
:return: A list of `hello_imgui.DockableWindow` objects representing the UI windows.
|
||||||
|
"""
|
||||||
file_dialog = hello_imgui.DockableWindow()
|
file_dialog = hello_imgui.DockableWindow()
|
||||||
file_dialog.label = "Database"
|
file_dialog.label = "Database"
|
||||||
file_dialog.dock_space_name = "MiscSpace"
|
file_dialog.dock_space_name = "MiscSpace"
|
||||||
@ -211,13 +328,18 @@ def set_database_editor_layout(app_state: AppState) -> List[hello_imgui.Dockable
|
|||||||
editor.dock_space_name = "CommandSpace"
|
editor.dock_space_name = "CommandSpace"
|
||||||
editor.gui_function = lambda: database_editor(app_state)
|
editor.gui_function = lambda: database_editor(app_state)
|
||||||
|
|
||||||
return [
|
return [file_dialog, log, table_view, editor]
|
||||||
file_dialog, log, table_view, editor
|
|
||||||
]
|
|
||||||
|
|
||||||
def database_editor_layout(app_state: AppState) -> hello_imgui.DockingParams:
|
def database_editor_layout(app_state: AppState) -> hello_imgui.DockingParams:
|
||||||
|
"""
|
||||||
|
Configures and returns the docking layout for the database editor.
|
||||||
|
|
||||||
|
:param app_state: The application state.
|
||||||
|
:return: A `hello_imgui.DockingParams` object defining the layout configuration.
|
||||||
|
"""
|
||||||
docking_params = hello_imgui.DockingParams()
|
docking_params = hello_imgui.DockingParams()
|
||||||
docking_params.layout_name = "Database Editor"
|
docking_params.layout_name = "Database Editor"
|
||||||
docking_params.docking_splits = database_docking_splits()
|
docking_params.docking_splits = database_docking_splits()
|
||||||
docking_params.dockable_windows = set_database_editor_layout(app_state)
|
docking_params.dockable_windows = set_database_editor_layout(app_state)
|
||||||
return docking_params
|
return docking_params
|
||||||
|
|
||||||
|
@ -1,261 +0,0 @@
|
|||||||
from imgui_bundle import imgui, imgui_ctx, ImVec2
|
|
||||||
from model import *
|
|
||||||
import random
|
|
||||||
|
|
||||||
class DatabaseEditor:
|
|
||||||
def __init__(self):
|
|
||||||
super().__init__()
|
|
||||||
self.add_name = str()
|
|
||||||
self.select_class = 0
|
|
||||||
self.select_lecture = 0
|
|
||||||
self.select_student = 0
|
|
||||||
self.select_submission = 0
|
|
||||||
|
|
||||||
self.class_name = str()
|
|
||||||
self.add_class_name = str()
|
|
||||||
|
|
||||||
self.student_prename = str()
|
|
||||||
self.student_surname = str()
|
|
||||||
self.student_sex = False
|
|
||||||
self.add_student_prename = str()
|
|
||||||
self.add_student_surname = str()
|
|
||||||
self.add_student_sex = False
|
|
||||||
|
|
||||||
self.lecture_title = str()
|
|
||||||
self.lecture_points = 0
|
|
||||||
self.add_lecture_title = str()
|
|
||||||
self.add_lecture_points = 0
|
|
||||||
|
|
||||||
self.submission_points = 0.0
|
|
||||||
self.add_submission_lecture = 0
|
|
||||||
self.add_submission_points = 0.0
|
|
||||||
|
|
||||||
def content_list(self, content: list, selector: int, id: str, height: int) -> int:
|
|
||||||
w = imgui.get_window_size().x
|
|
||||||
with imgui_ctx.begin_child(str(id), ImVec2(w*0.3, height)):
|
|
||||||
for n, c in enumerate(content, start = 1):
|
|
||||||
_, clicked = imgui.selectable(f"{n}. {c}", selector == n-1)
|
|
||||||
if clicked:
|
|
||||||
selector = n-1
|
|
||||||
return selector
|
|
||||||
|
|
||||||
def class_editor(self):
|
|
||||||
w, h = imgui.get_window_size()
|
|
||||||
classes = Class.select()
|
|
||||||
content = [f"{c.name}" for c in classes]
|
|
||||||
self.select_class = self.content_list(content, self.select_class, "class_content", h*0.15)
|
|
||||||
imgui.same_line()
|
|
||||||
|
|
||||||
with imgui_ctx.begin_child("Class", ImVec2(w*0.25, h*0.15)):
|
|
||||||
imgui.text("Edit Class")
|
|
||||||
_, self.class_name = imgui.input_text_with_hint("##class_edit1", content[self.select_class], self.class_name)
|
|
||||||
|
|
||||||
if imgui.button("Update"):
|
|
||||||
id = classes[self.select_class].id
|
|
||||||
Class.update(name=self.class_name).where(Class.id == id).execute()
|
|
||||||
|
|
||||||
imgui.same_line()
|
|
||||||
|
|
||||||
if imgui.button("Delete"):
|
|
||||||
id = classes[self.select_class].id
|
|
||||||
students = Student.select().where(Student.class_id == id)
|
|
||||||
for student in students:
|
|
||||||
Submission.delete().where(Submission.student_id == student.id).execute()
|
|
||||||
Student.delete().where(Student.class_id == id).execute()
|
|
||||||
Lecture.delete().where(Lecture.class_id == id).execute()
|
|
||||||
Class.delete().where(Class.id == id).execute()
|
|
||||||
|
|
||||||
|
|
||||||
imgui.separator()
|
|
||||||
|
|
||||||
imgui.text("Add Class")
|
|
||||||
_, self.add_class_name = imgui.input_text_with_hint("##class_edit2", "Class Name", self.add_class_name)
|
|
||||||
|
|
||||||
if imgui.button("Add"):
|
|
||||||
Class.create(name=self.add_class_name)
|
|
||||||
|
|
||||||
return classes[self.select_class].id
|
|
||||||
|
|
||||||
def student_editor(self, class_id: int):
|
|
||||||
w, h = imgui.get_window_size()
|
|
||||||
students = Student.select().where(Student.class_id == class_id)
|
|
||||||
content = [f"{s.prename} {s.surname}" for s in students]
|
|
||||||
self.select_student = self.content_list(content, self.select_student, "student_content", h*0.45)
|
|
||||||
imgui.same_line()
|
|
||||||
|
|
||||||
with imgui_ctx.begin_child("Student", ImVec2(w*0.25, h*0.4)):
|
|
||||||
imgui.text("Edit Student")
|
|
||||||
|
|
||||||
prename = students[self.select_student].prename
|
|
||||||
_, self.student_prename = imgui.input_text_with_hint("##student_edit1", prename, self.student_prename)
|
|
||||||
|
|
||||||
surname = students[self.select_student].surname
|
|
||||||
_, self.student_surname = imgui.input_text_with_hint("##student_edit2", surname, self.student_surname)
|
|
||||||
|
|
||||||
if imgui.radio_button("Male##1", not self.student_sex):
|
|
||||||
self.student_sex = not self.student_sex
|
|
||||||
imgui.same_line()
|
|
||||||
if imgui.radio_button("Female##1", self.student_sex):
|
|
||||||
self.student_sex = not self.student_sex
|
|
||||||
|
|
||||||
if imgui.button("Update"):
|
|
||||||
Student.update(
|
|
||||||
prename = self.student_prename,
|
|
||||||
surname = self.student_surname,
|
|
||||||
sex = "Female" if self.student_sex else "Male"
|
|
||||||
).where(Student.id == students[self.select_student].id).execute()
|
|
||||||
|
|
||||||
imgui.same_line()
|
|
||||||
|
|
||||||
if imgui.button("Delete"):
|
|
||||||
id = students[self.select_student].id
|
|
||||||
Student.delete().where(Student.id == id).execute()
|
|
||||||
Submission.delete().where(Submission.student_id == id).execute()
|
|
||||||
self.select_student = 0
|
|
||||||
|
|
||||||
imgui.separator()
|
|
||||||
|
|
||||||
imgui.text("Add Student")
|
|
||||||
_, self.add_student_prename = imgui.input_text_with_hint("##student_edit3", "First Name", self.add_student_prename)
|
|
||||||
_, self.add_student_surname = imgui.input_text_with_hint("##student_edit4", "Last Name", self.add_student_surname)
|
|
||||||
|
|
||||||
if imgui.radio_button("Male##2", not self.add_student_sex):
|
|
||||||
self.add_student_sex = not self.add_student_sex
|
|
||||||
imgui.same_line()
|
|
||||||
if imgui.radio_button("Female##2", self.add_student_sex):
|
|
||||||
self.add_student_sex = not self.add_student_sex
|
|
||||||
|
|
||||||
if imgui.button("Add"):
|
|
||||||
Student.create(
|
|
||||||
prename=self.add_student_prename,
|
|
||||||
surname=self.add_student_surname,
|
|
||||||
sex="Female" if self.add_student_sex else "Male",
|
|
||||||
class_id=class_id
|
|
||||||
)
|
|
||||||
self.add_student_prename = str()
|
|
||||||
self.add_student_surname = str()
|
|
||||||
self.add_student_sex = False
|
|
||||||
|
|
||||||
return students[self.select_student].id
|
|
||||||
|
|
||||||
def lecture_editor(self, class_id: int):
|
|
||||||
w, h = imgui.get_window_size()
|
|
||||||
lectures = Lecture.select().where(Lecture.class_id == class_id)
|
|
||||||
content = [f"{l.title}" for l in lectures]
|
|
||||||
self.select_lecture = self.content_list(content, self.select_lecture, "lecture_content", h*0.15)
|
|
||||||
imgui.same_line()
|
|
||||||
|
|
||||||
with imgui_ctx.begin_child("Lecture", ImVec2(w*0.25, h*0.15)):
|
|
||||||
imgui.text("Edit Lecture")
|
|
||||||
_, self.lecture_title = imgui.input_text_with_hint("##lecture_edit1", content[self.select_lecture], self.lecture_title)
|
|
||||||
_, self.lecture_points = imgui.input_int("##lecture_points1", self.lecture_points)
|
|
||||||
if self.lecture_points < 0:
|
|
||||||
self.lecture_points = 0
|
|
||||||
|
|
||||||
if imgui.button("Update"):
|
|
||||||
Lecture.update(title=self.lecture_title, points=self.lecture_points).where(Lecture.id == lectures[self.select_lecture].id).execute()
|
|
||||||
|
|
||||||
imgui.same_line()
|
|
||||||
|
|
||||||
if imgui.button("Delete"):
|
|
||||||
id = lectures[self.select_lecture].id
|
|
||||||
Submission.delete().where(Submission.lecture_id == id).execute()
|
|
||||||
Lecture.delete().where(Lecture.id == id).execute()
|
|
||||||
|
|
||||||
imgui.separator()
|
|
||||||
|
|
||||||
imgui.text("Add Lecture")
|
|
||||||
_, self.add_lecture_title = imgui.input_text_with_hint("##lecture_edit2", "Lecture Title", self.add_lecture_title)
|
|
||||||
_, self.add_lecture_points = imgui.input_int("##lecture_points2", self.add_lecture_points)
|
|
||||||
if self.add_lecture_points < 0:
|
|
||||||
self.add_lecture_points = 0
|
|
||||||
|
|
||||||
if imgui.button("Add"):
|
|
||||||
Lecture.create(title=self.add_lecture_title, points=self.add_lecture_points, class_id=class_id)
|
|
||||||
|
|
||||||
return lectures[self.select_lecture].id
|
|
||||||
|
|
||||||
def submission_editor(self, student_id: int):
|
|
||||||
w, h = imgui.get_window_size()
|
|
||||||
submissions = Submission.select().where(Submission.student_id == student_id)
|
|
||||||
lectures = [Lecture.get_by_id(sub.lecture_id) for sub in submissions]
|
|
||||||
content = [l.title for l in lectures]
|
|
||||||
self.select_submission = self.content_list(content, self.select_submission, "submission_content", h*0.2)
|
|
||||||
imgui.same_line()
|
|
||||||
|
|
||||||
with imgui_ctx.begin_child("Submission", ImVec2(w*0.25, h*0.2)):
|
|
||||||
imgui.text("Edit Submission")
|
|
||||||
imgui.text(content[self.select_submission])
|
|
||||||
|
|
||||||
points = submissions[self.select_submission].points
|
|
||||||
if points.is_integer():
|
|
||||||
points = int(points)
|
|
||||||
|
|
||||||
max_points = lectures[self.select_submission].points
|
|
||||||
|
|
||||||
_, self.submission_points = imgui.input_float(f"{points}/{max_points}", self.submission_points, 0.5, 10, "%.1f")
|
|
||||||
if self.submission_points < 0:
|
|
||||||
self.submission_points = 0
|
|
||||||
|
|
||||||
if imgui.button("Update"):
|
|
||||||
Submission.update(points=self.submission_points).where(Submission.id == submissions[self.select_submission].id).execute()
|
|
||||||
|
|
||||||
imgui.same_line()
|
|
||||||
|
|
||||||
if imgui.button("Delete"):
|
|
||||||
Submission.delete().where(Submission.id == submissions[self.select_submission].id).execute()
|
|
||||||
|
|
||||||
imgui.separator()
|
|
||||||
|
|
||||||
imgui.text("Add Submission")
|
|
||||||
available_lectures = Lecture.select().where(Lecture.class_id == Student.get_by_id(student_id).class_id)
|
|
||||||
combo_items = [l.title for l in available_lectures]
|
|
||||||
_, self.add_submission_lecture = imgui.combo("##lecture_combo", self.add_submission_lecture, combo_items, len(combo_items))
|
|
||||||
_, self.add_submission_points = imgui.input_float("##lecture_title", self.add_submission_points, 0.5, 10, "%.1f")
|
|
||||||
if self.add_submission_points < 0:
|
|
||||||
self.add_submission_points = 0
|
|
||||||
|
|
||||||
if imgui.button("Add"):
|
|
||||||
Submission.create(
|
|
||||||
points=self.add_submission_points,
|
|
||||||
lecture_id=available_lectures[self.add_submission_lecture].id,
|
|
||||||
student_id=student_id
|
|
||||||
)
|
|
||||||
|
|
||||||
return submissions[self.select_submission].id
|
|
||||||
|
|
||||||
def __call__(self):
|
|
||||||
with imgui_ctx.begin("Database Editor"):
|
|
||||||
class_id = self.class_editor()
|
|
||||||
imgui.separator()
|
|
||||||
self.lecture_editor(class_id)
|
|
||||||
imgui.separator()
|
|
||||||
student_id = self.student_editor(class_id)
|
|
||||||
imgui.separator()
|
|
||||||
self.submission_editor(student_id)
|
|
||||||
return
|
|
||||||
classes = Class.select()
|
|
||||||
|
|
||||||
with imgui_ctx.begin("Database Editor"):
|
|
||||||
imgui.text("Add Class")
|
|
||||||
|
|
||||||
_, self.add_name = imgui.input_text(" ", self.add_name)
|
|
||||||
|
|
||||||
if imgui.button("Add"):
|
|
||||||
if self.add_name:
|
|
||||||
Class.create(name=self.add_name)
|
|
||||||
self.add_name = str()
|
|
||||||
|
|
||||||
imgui.separator()
|
|
||||||
|
|
||||||
if not classes:
|
|
||||||
imgui.text("No Dataset could be queried")
|
|
||||||
return
|
|
||||||
|
|
||||||
for n, c in enumerate(classes, start=1):
|
|
||||||
display = f"{n}. {c.name}"
|
|
||||||
opened, _ = imgui.selectable(display, self.select == n-1)
|
|
||||||
if opened:
|
|
||||||
self.select = n-1
|
|
||||||
|
|
||||||
return classes[self.select]
|
|
106
gui.py
106
gui.py
@ -1,48 +1,72 @@
|
|||||||
|
"""
|
||||||
|
Student Analyzer Application
|
||||||
|
|
||||||
|
This script initializes and runs the Student Analyzer application, which provides an interface for
|
||||||
|
managing student data, class records, and submissions. It uses the Hello ImGui framework for UI rendering
|
||||||
|
and integrates a database to store and manipulate student information.
|
||||||
|
|
||||||
|
Modules:
|
||||||
|
- Custom Imports: Imports internal models and application state.
|
||||||
|
- Layouts: Defines different UI layouts for the analyzer and database editor.
|
||||||
|
- External Libraries: Uses imgui_bundle and Hello ImGui for UI rendering.
|
||||||
|
- Built-in Libraries: Uses shelve for persistent state storage and typing for type hints.
|
||||||
|
"""
|
||||||
|
|
||||||
# Custom
|
# Custom
|
||||||
from model import *
|
from model import * # Importing database models like Class, Student, Lecture, and Submission
|
||||||
from appstate import AppState
|
from appstate import AppState, LOG_ERROR # Application state management
|
||||||
|
|
||||||
# Layouts
|
# Layouts
|
||||||
from analyzer import analyzer_layout
|
from analyzer import analyzer_layout # Main layout for the analyzer
|
||||||
from database import database_editor_layout
|
from database import database_editor_layout # Alternative layout for database editing
|
||||||
|
|
||||||
# External
|
# External
|
||||||
from imgui_bundle import imgui, immapp, hello_imgui, ImVec2
|
from imgui_bundle import imgui, immapp, hello_imgui, ImVec2 # ImGui-based UI framework
|
||||||
|
|
||||||
# Built In
|
# Built-in
|
||||||
import shelve
|
import shelve # Persistent key-value storage
|
||||||
from typing import List
|
from typing import List # Type hinting
|
||||||
|
|
||||||
def menu_bar(runner_params: hello_imgui.RunnerParams) -> None:
|
def menu_bar(runner_params: hello_imgui.RunnerParams) -> None:
|
||||||
hello_imgui.show_app_menu(runner_params)
|
"""Defines the application's menu bar."""
|
||||||
hello_imgui.show_view_menu(runner_params)
|
try:
|
||||||
if imgui.begin_menu("File"):
|
hello_imgui.show_app_menu(runner_params)
|
||||||
clicked, _ = imgui.menu_item("Open", "", False)
|
hello_imgui.show_view_menu(runner_params)
|
||||||
if clicked:
|
|
||||||
pass
|
if imgui.begin_menu("File"):
|
||||||
imgui.end_menu()
|
clicked, _ = imgui.menu_item("Open", "", False)
|
||||||
|
if clicked:
|
||||||
|
pass # TODO: Implement file opening logic
|
||||||
|
imgui.end_menu()
|
||||||
|
except Exception as e:
|
||||||
|
LOG_ERROR(f"menu_bar {e}")
|
||||||
|
|
||||||
|
|
||||||
def status_bar(app_state: AppState) -> None:
|
def status_bar(app_state: AppState) -> None:
|
||||||
imgui.text("Student Analyzer by @DerGrumpf")
|
"""Displays the status bar information."""
|
||||||
|
try:
|
||||||
|
imgui.text("Student Analyzer by @DerGrumpf")
|
||||||
|
except Exception as e:
|
||||||
|
LOG_ERROR(f"status_bar {e}")
|
||||||
|
|
||||||
|
|
||||||
def main() -> None:
|
def main() -> None:
|
||||||
|
"""Main function to initialize and run the application."""
|
||||||
app_state = AppState()
|
app_state = AppState()
|
||||||
|
|
||||||
# Load Database
|
# Load Database
|
||||||
with shelve.open("state") as state:
|
try:
|
||||||
v = state.get("DB")
|
with shelve.open("state") as state:
|
||||||
if v:
|
v = state.get("DB") # Retrieve stored database connection info
|
||||||
db.init(v)
|
if v:
|
||||||
db.connect()
|
db.init(v)
|
||||||
db.create_tables([Class, Student, Lecture, Submission])
|
db.connect()
|
||||||
app_state.update()
|
db.create_tables([Class, Student, Lecture, Submission, Group]) # Ensure tables exist
|
||||||
|
app_state.update()
|
||||||
|
except Exception as e:
|
||||||
|
LOG_ERROR(f"Database Initialization {e}")
|
||||||
|
|
||||||
# Set Asset Folder
|
# Set Window Parameters
|
||||||
#hello_imgui.set_assets_folder()
|
|
||||||
|
|
||||||
# Set Theme
|
|
||||||
|
|
||||||
# Set Window Params
|
|
||||||
runner_params = hello_imgui.RunnerParams()
|
runner_params = hello_imgui.RunnerParams()
|
||||||
runner_params.app_window_params.window_title = "Analyzer"
|
runner_params.app_window_params.window_title = "Analyzer"
|
||||||
runner_params.imgui_window_params.menu_app_title = "Analyzer"
|
runner_params.imgui_window_params.menu_app_title = "Analyzer"
|
||||||
@ -53,26 +77,19 @@ def main() -> None:
|
|||||||
runner_params.app_window_params.borderless_resizable = True
|
runner_params.app_window_params.borderless_resizable = True
|
||||||
runner_params.app_window_params.borderless_closable = True
|
runner_params.app_window_params.borderless_closable = True
|
||||||
|
|
||||||
# Load Fonts
|
# Configure UI Elements
|
||||||
#runner_params.callbacks.load_additional_fonts = lambda: f()
|
|
||||||
|
|
||||||
# Status Bar & Main Menu
|
|
||||||
runner_params.imgui_window_params.show_menu_bar = True
|
runner_params.imgui_window_params.show_menu_bar = True
|
||||||
runner_params.imgui_window_params.show_menu_app = False
|
runner_params.imgui_window_params.show_menu_app = False
|
||||||
runner_params.imgui_window_params.show_menu_view = False
|
runner_params.imgui_window_params.show_menu_view = False
|
||||||
runner_params.imgui_window_params.show_status_bar = True
|
runner_params.imgui_window_params.show_status_bar = True
|
||||||
# Inside `show_menus`, we can call `hello_imgui.show_view_menu` and `hello_imgui.show_app_menu` if desired
|
|
||||||
runner_params.callbacks.show_menus = lambda: menu_bar(runner_params)
|
runner_params.callbacks.show_menus = lambda: menu_bar(runner_params)
|
||||||
# Optional: add items to Hello ImGui default App menu
|
|
||||||
#runner_params.callbacks.show_app_menu_items = show_app_menu_items
|
|
||||||
runner_params.callbacks.show_status = lambda: status_bar(app_state)
|
runner_params.callbacks.show_status = lambda: status_bar(app_state)
|
||||||
|
|
||||||
# Application layout
|
# Application layout
|
||||||
runner_params.imgui_window_params.default_imgui_window_type = (
|
runner_params.imgui_window_params.default_imgui_window_type = (
|
||||||
hello_imgui.DefaultImGuiWindowType.provide_full_screen_dock_space
|
hello_imgui.DefaultImGuiWindowType.provide_full_screen_dock_space
|
||||||
)
|
)
|
||||||
runner_params.imgui_window_params.enable_viewports = True
|
runner_params.imgui_window_params.enable_viewports = True
|
||||||
|
|
||||||
runner_params.docking_params = analyzer_layout(app_state)
|
runner_params.docking_params = analyzer_layout(app_state)
|
||||||
runner_params.alternative_docking_layouts = [
|
runner_params.alternative_docking_layouts = [
|
||||||
database_editor_layout(app_state)
|
database_editor_layout(app_state)
|
||||||
@ -81,16 +98,15 @@ def main() -> None:
|
|||||||
# Save App Settings
|
# Save App Settings
|
||||||
runner_params.ini_folder_type = hello_imgui.IniFolderType.app_user_config_folder
|
runner_params.ini_folder_type = hello_imgui.IniFolderType.app_user_config_folder
|
||||||
runner_params.ini_filename = "Analyzer/Analyzer.ini"
|
runner_params.ini_filename = "Analyzer/Analyzer.ini"
|
||||||
|
|
||||||
# Uncomment if layout will stay the same at start
|
|
||||||
runner_params.docking_params.layout_condition = hello_imgui.DockingLayoutCondition.application_start
|
runner_params.docking_params.layout_condition = hello_imgui.DockingLayoutCondition.application_start
|
||||||
|
|
||||||
# Run it
|
# Run the Application
|
||||||
add_ons_params = immapp.AddOnsParams()
|
add_ons_params = immapp.AddOnsParams()
|
||||||
add_ons_params.with_markdown = True
|
add_ons_params.with_markdown = True
|
||||||
add_ons_params.with_implot = True
|
add_ons_params.with_implot = True
|
||||||
add_ons_params.with_implot3d = True
|
add_ons_params.with_implot3d = True
|
||||||
|
|
||||||
immapp.run(runner_params, add_ons_params)
|
immapp.run(runner_params, add_ons_params)
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
||||||
|
8
model.py
8
model.py
@ -15,11 +15,18 @@ class Class(BaseModel):
|
|||||||
name = CharField()
|
name = CharField()
|
||||||
created_at = DateTimeField(default=datetime.now)
|
created_at = DateTimeField(default=datetime.now)
|
||||||
|
|
||||||
|
class Group(BaseModel):
|
||||||
|
name = CharField()
|
||||||
|
project = CharField()
|
||||||
|
class_id = ForeignKeyField(Class, backref='class')
|
||||||
|
created_at = DateTimeField(default=datetime.now)
|
||||||
|
|
||||||
class Student(BaseModel):
|
class Student(BaseModel):
|
||||||
prename = CharField()
|
prename = CharField()
|
||||||
surname = CharField()
|
surname = CharField()
|
||||||
sex = CharField()
|
sex = CharField()
|
||||||
class_id = ForeignKeyField(Class, backref='class')
|
class_id = ForeignKeyField(Class, backref='class')
|
||||||
|
group_id = ForeignKeyField(Group, backref='group')
|
||||||
created_at = DateTimeField(default=datetime.now)
|
created_at = DateTimeField(default=datetime.now)
|
||||||
|
|
||||||
class Lecture(BaseModel):
|
class Lecture(BaseModel):
|
||||||
@ -34,6 +41,7 @@ class Submission(BaseModel):
|
|||||||
points = FloatField()
|
points = FloatField()
|
||||||
created_at = DateTimeField(default=datetime.now)
|
created_at = DateTimeField(default=datetime.now)
|
||||||
|
|
||||||
|
|
||||||
def load_from_json(fp: Path) -> None:
|
def load_from_json(fp: Path) -> None:
|
||||||
'''
|
'''
|
||||||
Rebuilding Database from a given json
|
Rebuilding Database from a given json
|
||||||
|
Loading…
Reference in New Issue
Block a user