Changed: Projects
This commit is contained in:
		
							
								
								
									
										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
 | 
			
		||||
 | 
			
		||||
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:
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
		
		
			
  | 
										
											Binary file not shown.
										
									
								
							@@ -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),
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										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
 | 
			
		||||
from model import *
 | 
			
		||||
from appstate import * 
 | 
			
		||||
@@ -19,22 +27,35 @@ from pathlib import Path
 | 
			
		||||
from datetime import datetime
 | 
			
		||||
 | 
			
		||||
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()
 | 
			
		||||
    modified = datetime.fromtimestamp(stat.st_atime)
 | 
			
		||||
    created = datetime.fromtimestamp(stat.st_ctime)
 | 
			
		||||
    format = '%c'
 | 
			
		||||
    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.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.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.students = Student.select().where(Student.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:
 | 
			
		||||
        imgui.text("No Lecture queried")
 | 
			
		||||
        return
 | 
			
		||||
    
 | 
			
		||||
    table_flags = (
 | 
			
		||||
        imgui.TableFlags_.row_bg.value
 | 
			
		||||
        | imgui.TableFlags_.borders.value 
 | 
			
		||||
        | imgui.TableFlags_.resizable.value 
 | 
			
		||||
        | imgui.TableFlags_.sizing_stretch_same.value
 | 
			
		||||
    )
 | 
			
		||||
        statics.rows = len(statics.students)
 | 
			
		||||
        statics.cols = len(statics.lectures)
 | 
			
		||||
        statics.grid = list()
 | 
			
		||||
 | 
			
		||||
    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")
 | 
			
		||||
        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:
 | 
			
		||||
    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:
 | 
			
		||||
        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]: 
 | 
			
		||||
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
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
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:
 | 
			
		||||
    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
 | 
			
		||||
        imgui.end_menu()
 | 
			
		||||
    """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  # 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:
 | 
			
		||||
    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:
 | 
			
		||||
    """Main function to initialize and run the application."""
 | 
			
		||||
    app_state = AppState()
 | 
			
		||||
    
 | 
			
		||||
    # Load Database 
 | 
			
		||||
    with shelve.open("state") as state:
 | 
			
		||||
        v = state.get("DB")
 | 
			
		||||
        if v:
 | 
			
		||||
            db.init(v)
 | 
			
		||||
            db.connect()
 | 
			
		||||
            db.create_tables([Class, Student, Lecture, Submission])
 | 
			
		||||
            app_state.update()
 | 
			
		||||
        
 | 
			
		||||
    # Load Database
 | 
			
		||||
    try:
 | 
			
		||||
        with shelve.open("state") as state:
 | 
			
		||||
            v = state.get("DB")  # Retrieve stored database connection info
 | 
			
		||||
            if v:
 | 
			
		||||
                db.init(v)
 | 
			
		||||
                db.connect()
 | 
			
		||||
                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,26 +77,19 @@ 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
 | 
			
		||||
    runner_params.imgui_window_params.default_imgui_window_type = (
 | 
			
		||||
        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.alternative_docking_layouts = [
 | 
			
		||||
        database_editor_layout(app_state)
 | 
			
		||||
@@ -81,16 +98,15 @@ 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__":
 | 
			
		||||
    main()
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										8
									
								
								model.py
									
									
									
									
									
								
							
							
						
						
									
										8
									
								
								model.py
									
									
									
									
									
								
							@@ -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
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user