Changed: Projects

This commit is contained in:
DerGrumpf 2025-01-31 14:34:57 +01:00
parent 4916a83a9c
commit 5d7b1d59c5
8 changed files with 343 additions and 411 deletions

View File

@ -45,6 +45,27 @@ def student_list(app_state: AppState) -> None:
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:
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_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)
def student_graph(app_state: AppState) -> None:
statics = student_graph
if not statics.inited:
statics.id = -1
statics.student = None
statics.group = None
statics.lectures = None
statics.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)
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.max_points = np.sum([l.points for l in statics.lectures])
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
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.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%}")
plot_bar_line_percentage(statics.subs_data, statics.subs_labels, statics.avg)
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):
lecture, points = data
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.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.label = "Lectures"
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,
lecture_selector, submission_selector,
student_info, sex_info,
student_ranking
student_ranking, group_selector
]
def analyzer_layout(app_state: AppState) -> hello_imgui.DockingParams:

View File

@ -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
Abdalaziz,Abunjaila,Male,30.5,15,18,28,17,17,17,22,0,0
Marleen,Adolphi,Female,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
Skofiare,Berisha,Female,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
Cam Thu,Do,Female,31,15,18,34,19,20,21.5,22,12,0
Nova,Eib,Female,31,15,15,34,20,20,21,27,19,0
Nele,Grundke,Female,23.5,13,16,28,20,17,21,18,22,0
Anna,Grünewald,Female,12,14,16,29,16,15,19,9,0,0
Yannik,Haupt,Male,18,6,14,21,13,2,9,0,0,0
Janna,Heiny,Female,30,15,18,33,18,20,22,25,24,30
Milena,Krieger,Female,30,15,18,33,20,20,21.5,26,20,0
Julia,Limbach,Female,27.5,12,18,29,11,19,17.5,26,24,0
Viktoria,Litza,Female,21.5,15,18,27,13,20,22,21,21,0
Leonie,Manthey,Female,28.5,14,18,29,20,10,18,23,16,28
Izabel,Mike,Female,29.5,15,15,35,11,15,19,21,21,27
Lea,Noglik,Female,22.5,15,17,34,13,10,20,21,19,0
Donika,Nuhiu,Female,31,13.5,18,35,14,10,17,18,19,6
Julia,Renner,Female,27.5,10,14,32,20,17,11,20,24,0
Fabian,Rothberger,Male,30.5,15,18,34,17,17,19,22,18,0
Natascha,Rott,Female,29.5,12,18,32,19,20,21,26,23,0
Isabel,Rudolf,Female,27.5,9,17,34,16,19,19,21,16,0
Melina,Sablotny,Female,31,15,18,33,20,20,21,19,11,0
Alea,Schleier,Female,27,14,18,34,16,18,21.5,22,15,22
Flemming,Schur,Male,29.5,15,17,34,19,20,19,22,18,0
Marie,Seeger,Female,27.5,15,18,32,14,9,17,22,9,0
Lucy,Thiele,Female,27.5,15,18,27,20,17,19,18,22,0
Lara,Troschke,Female,28.5,14,17,28,13,19,21,25,12,0
Inga-Brit,Turschner,Female,25.5,14,18,34,20,16,19,22,17,0
Alea,Unger,Female,30,12,18,31,20,20,21,22,15,21.5
Marie,Wallbaum,Female,28.5,14,18,34,17,20,19,24,12,0
Katharina,Walz,Female,31,15,18,31,19,19,17,24,17,14.5
Xiaowei,Wang,Male,30.5,14,18,26,19,17,0,0,0,0
Lilly-Lu,Warnken,Female,30,15,18,30,14,17,19,14,16,0
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,DiKum,30.5,15,18,28,17,17,17,22,0,18
Marleen,Adolphi,Female,MeWi6,29.5,15,18,32,19,20,17,24,23,0
Sarina,Apel,Female,MeWi1,28.5,15,18,32,20,20,21,24,20,0
Skofiare,Berisha,Female,DiKum,29.5,13,18,34,20,17,20,26,16,0
Aurela,Brahimi,Female,MeWi2,17.5,15,15.5,26,16,17,19,16,0,0
Cam Thu,Do,Female,MeWi3,31,15,18,34,19,20,21.5,22,12,0
Nova,Eib,Female,MeWi4,31,15,15,34,20,20,21,27,19,21
Lena,Fricke,Female,MeWi4,0,0,0,0,0,0,0,0,0,0
Nele,Grundke,Female,MeWi6,23.5,13,16,28,20,17,21,18,22,0
Anna,Grünewald,Female,MeWi3,12,14,16,29,16,15,19,9,0,0
Yannik,Haupt,Male,NoGroup,18,6,14,21,13,2,9,0,0,0
Janna,Heiny,Female,MeWi1,30,15,18,33,18,20,22,25,24,30
Milena,Krieger,Female,MeWi1,30,15,18,33,20,20,21.5,26,20,0
Julia,Limbach,Female,MeWi6,27.5,12,18,29,11,19,17.5,26,24,0
Viktoria,Litza,Female,MeWi5,21.5,15,18,27,13,20,22,21,21,0
Leonie,Manthey,Female,MeWi1,28.5,14,18,29,20,10,18,23,16,28
Izabel,Mike,Female,MeWi2,29.5,15,15,35,11,15,19,21,21,27
Lea,Noglik,Female,MeWi5,22.5,15,17,34,13,10,20,21,19,0
Donika,Nuhiu,Female,MeWi5,31,13.5,18,35,14,10,17,18,19,6
Julia,Renner,Female,MeWi4,27.5,10,14,32,20,17,11,20,24,0
Fabian,Rothberger,Male,MeWi3,30.5,15,18,34,17,17,19,22,18,0
Natascha,Rott,Female,MeWi1,29.5,12,18,32,19,20,21,26,23,0
Isabel,Rudolf,Female,MeWi4,27.5,9,17,34,16,19,19,21,16,0
Melina,Sablotny,Female,MeWi6,31,15,18,33,20,20,21,19,11,0
Alea,Schleier,Female,DiKum,27,14,18,34,16,18,21.5,22,15,22
Flemming,Schur,Male,MeWi3,29.5,15,17,34,19,20,19,22,18,0
Marie,Seeger,Female,DiKum,27.5,15,18,32,14,9,17,22,9,0
Lucy,Thiele,Female,MeWi6,27.5,15,18,27,20,17,19,18,22,0
Lara,Troschke,Female,MeWi2,28.5,14,17,28,13,19,21,25,12,0
Inga-Brit,Turschner,Female,MeWi2,25.5,14,18,34,20,16,19,22,17,0
Alea,Unger,Female,MeWi5,30,12,18,31,20,20,21,22,15,21.5
Marie,Wallbaum,Female,MeWi5,28.5,14,18,34,17,20,19,24,12,0
Katharina,Walz,Female,MeWi4,31,15,18,31,19,19,17,24,17,14.5
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

1 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
2 Abdalaziz Abunjaila Male DiKum 30.5 15 18 28 17 17 17 22 0 0 18
3 Marleen Adolphi Female MeWi6 29.5 15 18 32 19 20 17 24 23 0
4 Sarina Apel Female MeWi1 28.5 15 18 32 20 20 21 24 20 0
5 Skofiare Berisha Female DiKum 29.5 13 18 34 20 17 20 26 16 0
6 Aurela Brahimi Female MeWi2 17.5 15 15.5 26 16 17 19 16 0 0
7 Cam Thu Do Female MeWi3 31 15 18 34 19 20 21.5 22 12 0
8 Nova Eib Female MeWi4 31 15 15 34 20 20 21 27 19 0 21
9 Nele Lena Grundke Fricke Female MeWi4 23.5 0 13 0 16 0 28 0 20 0 17 0 21 0 18 0 22 0 0
10 Anna Nele Grünewald Grundke Female MeWi6 12 23.5 14 13 16 29 28 16 20 15 17 19 21 9 18 0 22 0
11 Yannik Anna Haupt Grünewald Male Female MeWi3 18 12 6 14 14 16 21 29 13 16 2 15 9 19 0 9 0 0
12 Janna Yannik Heiny Haupt Female Male NoGroup 30 18 15 6 18 14 33 21 18 13 20 2 22 9 25 0 24 0 30 0
13 Milena Janna Krieger Heiny Female MeWi1 30 15 18 33 20 18 20 21.5 22 26 25 20 24 0 30
14 Julia Milena Limbach Krieger Female MeWi1 27.5 30 12 15 18 29 33 11 20 19 20 17.5 21.5 26 24 20 0
15 Viktoria Julia Litza Limbach Female MeWi6 21.5 27.5 15 12 18 27 29 13 11 20 19 22 17.5 21 26 21 24 0
16 Leonie Viktoria Manthey Litza Female MeWi5 28.5 21.5 14 15 18 29 27 20 13 10 20 18 22 23 21 16 21 28 0
17 Izabel Leonie Mike Manthey Female MeWi1 29.5 28.5 15 14 15 18 35 29 11 20 15 10 19 18 21 23 21 16 27 28
18 Lea Izabel Noglik Mike Female MeWi2 22.5 29.5 15 17 15 34 35 13 11 10 15 20 19 21 19 21 0 27
19 Donika Lea Nuhiu Noglik Female MeWi5 31 22.5 13.5 15 18 17 35 34 14 13 10 17 20 18 21 19 6 0
20 Julia Donika Renner Nuhiu Female MeWi5 27.5 31 10 13.5 14 18 32 35 20 14 17 10 11 17 20 18 24 19 0 6
21 Fabian Julia Rothberger Renner Male Female MeWi4 30.5 27.5 15 10 18 14 34 32 17 20 17 19 11 22 20 18 24 0
22 Natascha Fabian Rott Rothberger Female Male MeWi3 29.5 30.5 12 15 18 32 34 19 17 20 17 21 19 26 22 23 18 0
23 Isabel Natascha Rudolf Rott Female MeWi1 27.5 29.5 9 12 17 18 34 32 16 19 19 20 19 21 21 26 16 23 0
24 Melina Isabel Sablotny Rudolf Female MeWi4 31 27.5 15 9 18 17 33 34 20 16 20 19 21 19 19 21 11 16 0
25 Alea Melina Schleier Sablotny Female MeWi6 27 31 14 15 18 34 33 16 20 18 20 21.5 21 22 19 15 11 22 0
26 Flemming Alea Schur Schleier Male Female DiKum 29.5 27 15 14 17 18 34 19 16 20 18 19 21.5 22 18 15 0 22
27 Marie Flemming Seeger Schur Female Male MeWi3 27.5 29.5 15 18 17 32 34 14 19 9 20 17 19 22 9 18 0
28 Lucy Marie Thiele Seeger Female DiKum 27.5 15 18 27 32 20 14 17 9 19 17 18 22 22 9 0
29 Lara Lucy Troschke Thiele Female MeWi6 28.5 27.5 14 15 17 18 28 27 13 20 19 17 21 19 25 18 12 22 0
30 Inga-Brit Lara Turschner Troschke Female MeWi2 25.5 28.5 14 18 17 34 28 20 13 16 19 19 21 22 25 17 12 0
31 Alea Inga-Brit Unger Turschner Female MeWi2 30 25.5 12 14 18 31 34 20 20 16 21 19 22 15 17 21.5 0
32 Marie Alea Wallbaum Unger Female MeWi5 28.5 30 14 12 18 34 31 17 20 20 19 21 24 22 12 15 0 21.5
33 Katharina Marie Walz Wallbaum Female MeWi5 31 28.5 15 14 18 31 34 19 17 19 20 17 19 24 17 12 14.5 0
34 Xiaowei Katharina Wang Walz Male Female MeWi4 30.5 31 14 15 18 26 31 19 17 19 0 17 0 24 0 17 0 14.5
35 Lilly-Lu Xiaowei Warnken Wang Female Male NoGroup 30 30.5 15 14 18 30 26 14 19 17 19 0 14 0 16 0 0
36 Lilly-Lu Warnken Female DiKum 30 15 18 30 14 17 19 14 16 0
37

Binary file not shown.

View File

@ -19,11 +19,22 @@ courses = {
'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)
db.init("WiSe_24_25.db")
db.connect()
db.create_tables([Class, Student, Lecture, Submission])
db.create_tables([Class, Student, Lecture, Submission, Group])
# Create Class
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)
#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():
s = Student.create(
prename=row["First Name"],
surname=row["Last Name"],
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(
student_id=s.id,
lecture_id=Lecture.select().where(Lecture.title == title),

View File

@ -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
from model import *
from appstate import *
@ -19,22 +27,35 @@ from pathlib import Path
from datetime import datetime
def file_info(path: Path) -> None:
stat = path.stat()
modified = datetime.fromtimestamp(stat.st_atime)
created = datetime.fromtimestamp(stat.st_ctime)
format = '%c'
"""
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()
modified = datetime.fromtimestamp(stat.st_atime) # Last access time
created = datetime.fromtimestamp(stat.st_ctime) # Creation time
format = '%c' # Standard date-time format
# Prepare file data dictionary
data = {
"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),
"Created": created.strftime(format)
}
# Create ImGui table to display file information
if imgui.begin_table("File Info", 2):
imgui.table_setup_column(" ", 0)
imgui.table_setup_column(" ")
# Iterate over file data and populate table
for k, v in data.items():
imgui.push_id(k)
imgui.table_next_row()
@ -43,59 +64,86 @@ def file_info(path: Path) -> None:
imgui.table_next_column()
imgui.text(v)
imgui.pop_id()
imgui.end_table()
@immapp.static(inited=False, res=False)
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:
statics.res = None
statics.current = None
statics.res = None # Stores the selected file result
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:
statics.current = Path(state["DB"])
statics.inited = True
# Render UI title and display file information
imgui_md.render("# Database Manager")
file_info(statics.current)
# Button to open the file selection dialog
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().has_result():
statics.res = im_file_dialog.FileDialog.instance().get_result()
LOG_INFO(f"Load File {statics.res}")
im_file_dialog.FileDialog.instance().close()
# Process the selected file if available
if statics.res:
filename = statics.res.filename()
info = Path(statics.res.path())
imgui.separator()
file_info(info)
imgui.separator() # UI separator for clarity
file_info(info) # Display information about the selected file
file = None
# Load the selected database file
if imgui.button("Load"):
# Ensure any currently open database is closed before loading a new one
if not db.is_closed():
db.close()
# Handle JSON files by converting them to SQLite databases
if statics.res.extension() == '.json':
file = filename.removesuffix('.json')
file = file + '.db'
file = filename.removesuffix('.json') + '.db' # Convert JSON filename to SQLite filename
db.init(file)
db.connect(reuse_if_open=True)
db.create_tables([Class, Student, Lecture, Submission])
load_from_json(str(info))
db.create_tables([Class, Student, Lecture, Submission, Group])
load_from_json(str(info)) # Convert and load JSON data into the database
LOG_INFO(f"Successfully created {file}")
# Handle SQLite database files directly
if statics.res.extension() == '.db':
file = str(statics.res.path())
db.init(file)
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}")
# Save the selected database path to persistent storage
with shelve.open("state") as state:
state["DB"] = file
# Update application state and reset selection result
app_state.update()
statics.res = None
@ -103,77 +151,139 @@ def select_file(app_state: AppState):
def table(app_state: AppState) -> None:
statics = table
if not statics.inited:
statics.class_id = None
statics.lectures = None
statics.points = list()
statics.inited = True
if statics.class_id != app_state.current_class_id:
statics.class_id = app_state.current_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:
imgui.text("No Lecture queried")
return
table_flags = (
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.inited = True
if imgui.begin_table("Overview", len(statics.lectures)+1, table_flags):
if 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.rows = len(statics.students)
statics.cols = len(statics.lectures)
statics.grid = list()
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")
for n, lecture in enumerate(statics.lectures, start=1):
imgui.table_setup_column(f"{n}. {lecture.title} ({lecture.points})")
imgui.table_setup_scroll_freeze(1, 1)
for header in statics.table_header:
imgui.table_setup_column(header)
imgui.table_headers_row()
for k, v in statics.data.items():
imgui.push_id(k)
# Fill Student names
for row in range(statics.rows):
imgui.table_next_row()
imgui.table_next_column()
imgui.text(k)
for points in v:
imgui.table_next_column()
if points.is_integer():
points = int(points)
imgui.text(str(points))
imgui.pop_id()
imgui.table_set_column_index(0)
student = statics.students[row]
imgui.text(f"{student.prename} {student.surname}")
for col in range(statics.cols):
imgui.table_set_column_index(col+1)
changed, value = imgui.input_float(f"##{statics.grid[row][col]}", statics.grid[row][col].points, 0.0, 0.0, "%.1f")
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()
@immapp.static(inited=False)
def class_editor() -> None:
"""
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:
statics.classes = None
statics.selected = 0
statics.value = statics.classes[statics.selected].name if statics.classes else str()
statics.inited = True
statics.classes = Class.select()
# Fetch available classes from the database
# Render the UI for class selection
imgui_md.render("# Edit Classes")
_, statics.selected = imgui.combo("Classes", statics.selected, [c.name for c in statics.classes])
imgui.text(statics.classes[statics.selected].name)
changed, statics.selected = imgui.combo("##Classes", statics.selected, [c.name for c in statics.classes])
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:
"""
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()
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.initial_dock = "MainDockSpace"
split_main_command.new_dock = "CommandSpace"
split_main_command.direction = imgui.Dir.down
split_main_command.ratio = 0.3
# Log Space
split_main_command2 = hello_imgui.DockingSplit()
split_main_command2.initial_dock = "CommandSpace"
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.ratio = 0.2
splits = [split_main_misc, split_main_command, split_main_command2]
return splits
return [split_main_misc, split_main_command, split_main_command2]
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.label = "Database"
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.gui_function = lambda: database_editor(app_state)
return [
file_dialog, log, table_view, editor
]
return [file_dialog, log, table_view, editor]
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.layout_name = "Database Editor"
docking_params.docking_splits = database_docking_splits()
docking_params.dockable_windows = set_database_editor_layout(app_state)
return docking_params

View File

@ -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]

72
gui.py
View File

@ -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
from model import *
from appstate import AppState
from model import * # Importing database models like Class, Student, Lecture, and Submission
from appstate import AppState, LOG_ERROR # Application state management
# Layouts
from analyzer import analyzer_layout
from database import database_editor_layout
from analyzer import analyzer_layout # Main layout for the analyzer
from database import database_editor_layout # Alternative layout for database editing
# 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
import shelve
from typing import List
# Built-in
import shelve # Persistent key-value storage
from typing import List # Type hinting
def menu_bar(runner_params: hello_imgui.RunnerParams) -> None:
"""Defines the application's menu bar."""
try:
hello_imgui.show_app_menu(runner_params)
hello_imgui.show_view_menu(runner_params)
if imgui.begin_menu("File"):
clicked, _ = imgui.menu_item("Open", "", False)
if clicked:
pass
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:
"""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:
"""Main function to initialize and run the application."""
app_state = AppState()
# Load Database
try:
with shelve.open("state") as state:
v = state.get("DB")
v = state.get("DB") # Retrieve stored database connection info
if v:
db.init(v)
db.connect()
db.create_tables([Class, Student, Lecture, Submission])
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
#hello_imgui.set_assets_folder()
# Set Theme
# Set Window Params
# Set Window Parameters
runner_params = hello_imgui.RunnerParams()
runner_params.app_window_params.window_title = "Analyzer"
runner_params.imgui_window_params.menu_app_title = "Analyzer"
@ -53,18 +77,12 @@ def main() -> None:
runner_params.app_window_params.borderless_resizable = True
runner_params.app_window_params.borderless_closable = True
# Load Fonts
#runner_params.callbacks.load_additional_fonts = lambda: f()
# Status Bar & Main Menu
# Configure UI Elements
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_view = False
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)
# 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)
# Application layout
@ -72,7 +90,6 @@ def main() -> None:
hello_imgui.DefaultImGuiWindowType.provide_full_screen_dock_space
)
runner_params.imgui_window_params.enable_viewports = True
runner_params.docking_params = analyzer_layout(app_state)
runner_params.alternative_docking_layouts = [
database_editor_layout(app_state)
@ -81,15 +98,14 @@ def main() -> None:
# Save App Settings
runner_params.ini_folder_type = hello_imgui.IniFolderType.app_user_config_folder
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
# Run it
# Run the Application
add_ons_params = immapp.AddOnsParams()
add_ons_params.with_markdown = True
add_ons_params.with_implot = True
add_ons_params.with_implot3d = True
immapp.run(runner_params, add_ons_params)
if __name__ == "__main__":

View File

@ -15,11 +15,18 @@ class Class(BaseModel):
name = CharField()
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):
prename = CharField()
surname = CharField()
sex = CharField()
class_id = ForeignKeyField(Class, backref='class')
group_id = ForeignKeyField(Group, backref='group')
created_at = DateTimeField(default=datetime.now)
class Lecture(BaseModel):
@ -34,6 +41,7 @@ class Submission(BaseModel):
points = FloatField()
created_at = DateTimeField(default=datetime.now)
def load_from_json(fp: Path) -> None:
'''
Rebuilding Database from a given json