Changed: Structure Overhal
This commit is contained in:
		@@ -1,38 +1,39 @@
 | 
			
		||||
First Name,Last Name,Sex,Group,Grader,Tutorial 1,Tutorial 2,Extended Applications,Numpy & MatPlotLib,SciPy,Monte Carlo,Pandas & Seaborn,Folium,Statistical Test Methods,Data Analysis
 | 
			
		||||
Abdalaziz,Abunjaila,Male,DiKum,30 Percent,30.5,15,18,28,17,17,17,22,0,18
 | 
			
		||||
Marleen,Adolphi,Female,MeWi6,30 Percent,29.5,15,18,32,19,20,17,24,23,0
 | 
			
		||||
Sarina,Apel,Female,MeWi1,30 Percent,28.5,15,18,32,20,20,21,24,20,23
 | 
			
		||||
Skofiare,Berisha,Female,DiKum,30 Percent,29.5,13,18,34,20,17,20,26,16,0
 | 
			
		||||
Aurela,Brahimi,Female,MeWi2,30 Percent,17.5,15,15.5,26,16,17,19,16,0,0
 | 
			
		||||
Cam Thu,Do,Female,MeWi3,30 Percent,31,15,18,34,19,20,21.5,22,12,0
 | 
			
		||||
Nova,Eib,Female,MeWi4,30 Percent,31,15,15,34,20,20,21,27,19,21
 | 
			
		||||
Lena,Fricke,Female,MeWi4,30 Percent,0,0,0,0,0,0,0,0,0,0
 | 
			
		||||
Nele,Grundke,Female,MeWi6,30 Percent,23.5,13,16,28,20,17,21,18,22,11
 | 
			
		||||
Anna,Grünewald,Female,MeWi3,30 Percent,12,14,16,29,16,15,19,9,0,0
 | 
			
		||||
Yannik,Haupt,Male,NoGroup,30 Percent,18,6,14,21,13,2,9,0,0,0
 | 
			
		||||
Janna,Heiny,Female,MeWi1,30 Percent,30,15,18,33,18,20,22,25,24,30
 | 
			
		||||
Milena,Krieger,Female,MeWi1,30 Percent,30,15,18,33,20,20,21.5,26,20,22
 | 
			
		||||
Julia,Limbach,Female,MeWi6,30 Percent,27.5,12,18,29,11,19,17.5,26,24,28
 | 
			
		||||
Viktoria,Litza,Female,MeWi5,30 Percent,21.5,15,18,27,13,20,22,21,21,30
 | 
			
		||||
Leonie,Manthey,Female,MeWi1,30 Percent,28.5,14,18,29,20,10,18,23,16,28
 | 
			
		||||
Izabel,Mike,Female,MeWi2,30 Percent,29.5,15,15,35,11,15,19,21,21,27
 | 
			
		||||
Lea,Noglik,Female,MeWi5,30 Percent,22.5,15,17,34,13,10,20,21,19,6
 | 
			
		||||
Donika,Nuhiu,Female,MeWi5,30 Percent,31,13.5,18,35,14,10,17,18,19,8
 | 
			
		||||
Julia,Renner,Female,MeWi4,30 Percent,27.5,10,14,32,20,17,11,20,24,14
 | 
			
		||||
Fabian,Rothberger,Male,MeWi3,30 Percent,30.5,15,18,34,17,17,19,22,18,30
 | 
			
		||||
Natascha,Rott,Female,MeWi1,30 Percent,29.5,12,18,32,19,20,21,26,23,26
 | 
			
		||||
Isabel,Rudolf,Female,MeWi4,30 Percent,27.5,9,17,34,16,19,19,21,16,14
 | 
			
		||||
Melina,Sablotny,Female,MeWi6,30 Percent,31,15,18,33,20,20,21,19,11,28
 | 
			
		||||
Alea,Schleier,Female,DiKum,30 Percent,27,14,18,34,16,18,21.5,22,15,22
 | 
			
		||||
Flemming,Schur,Male,MeWi3,30 Percent,29.5,15,17,34,19,20,19,22,18,27
 | 
			
		||||
Marie,Seeger,Female,DiKum,30 Percent,27.5,15,18,32,14,9,17,22,9,25
 | 
			
		||||
Lucy,Thiele,Female,MeWi6,30 Percent,27.5,15,18,27,20,17,19,18,22,25
 | 
			
		||||
Lara,Troschke,Female,MeWi2,30 Percent,28.5,14,17,28,13,19,21,25,12,24
 | 
			
		||||
Inga-Brit,Turschner,Female,MeWi2,30 Percent,25.5,14,18,34,20,16,19,22,17,30
 | 
			
		||||
Alea,Unger,Female,MeWi5,30 Percent,30,12,18,31,20,20,21,22,15,21.5
 | 
			
		||||
Marie,Wallbaum,Female,MeWi5,30 Percent,28.5,14,18,34,17,20,19,24,12,22
 | 
			
		||||
Katharina,Walz,Female,MeWi4,30 Percent,31,15,18,31,19,19,17,24,17,14.5
 | 
			
		||||
Xiaowei,Wang,Male,NoGroup,30 Percent,30.5,14,18,26,19,17,0,0,0,0
 | 
			
		||||
Lilly-Lu,Warnken,Female,DiKum,30 Percent,30,15,18,30,14,17,19,14,16,24
 | 
			
		||||
Abdalaziz,Abunjaila,Male,DiKum,30%,30.5,15,18,28,17,17,17,22,0,18
 | 
			
		||||
Marleen,Adolphi,Female,MeWi6,30%,29.5,15,18,32,19,20,17,24,23,0
 | 
			
		||||
Sarina,Apel,Female,MeWi1,30%,28.5,15,18,32,20,20,21,24,20,23
 | 
			
		||||
Skofiare,Berisha,Female,DiKum,30%,29.5,13,18,34,20,17,20,26,16,0
 | 
			
		||||
Aurela,Brahimi,Female,MeWi2,30%,17.5,15,15.5,26,16,17,19,16,0,0
 | 
			
		||||
Cam Thu,Do,Female,MeWi3,30%,31,15,18,34,19,20,21.5,22,12,0
 | 
			
		||||
Nova,Eib,Female,MeWi4,30%,31,15,15,34,20,20,21,27,19,21
 | 
			
		||||
Lena,Fricke,Female,MeWi4,30%,0,0,0,0,0,0,0,0,0,0
 | 
			
		||||
Nele,Grundke,Female,MeWi6,30%,23.5,13,16,28,20,17,21,18,22,11
 | 
			
		||||
Anna,Grünewald,Female,MeWi3,30%,12,14,16,29,16,15,19,9,0,0
 | 
			
		||||
Yannik,Haupt,Male,NoGroup,30%,18,6,14,21,13,2,9,0,0,0
 | 
			
		||||
Janna,Heiny,Female,MeWi1,30%,30,15,18,33,18,20,22,25,24,30
 | 
			
		||||
Milena,Krieger,Female,MeWi1,30%,30,15,18,33,20,20,21.5,26,20,22
 | 
			
		||||
Julia,Limbach,Female,MeWi6,30%,27.5,12,18,29,11,19,17.5,26,24,28
 | 
			
		||||
Viktoria,Litza,Female,MeWi5,30%,21.5,15,18,27,13,20,22,21,21,30
 | 
			
		||||
Leonie,Manthey,Female,MeWi1,30%,28.5,14,18,29,20,10,18,23,16,28
 | 
			
		||||
Izabel,Mike,Female,MeWi2,30%,29.5,15,15,35,11,15,19,21,21,27
 | 
			
		||||
Lea,Noglik,Female,MeWi5,30%,22.5,15,17,34,13,10,20,21,19,6
 | 
			
		||||
Donika,Nuhiu,Female,MeWi5,30%,31,13.5,18,35,14,10,17,18,19,8
 | 
			
		||||
Julia,Renner,Female,MeWi4,30%,27.5,10,14,32,20,17,11,20,24,14
 | 
			
		||||
Fabian,Rothberger,Male,MeWi3,30%,30.5,15,18,34,17,17,19,22,18,30
 | 
			
		||||
Natascha,Rott,Female,MeWi1,30%,29.5,12,18,32,19,20,21,26,23,26
 | 
			
		||||
Isabel,Rudolf,Female,MeWi4,30%,27.5,9,17,34,16,19,19,21,16,14
 | 
			
		||||
Melina,Sablotny,Female,MeWi6,30%,31,15,18,33,20,20,21,19,11,28
 | 
			
		||||
Alea,Schleier,Female,DiKum,30%,27,14,18,34,16,18,21.5,22,15,22
 | 
			
		||||
Flemming,Schur,Male,MeWi3,30%,29.5,15,17,34,19,20,19,22,18,27
 | 
			
		||||
Marie,Seeger,Female,DiKum,30%,27.5,15,18,32,14,9,17,22,9,25
 | 
			
		||||
Lucy,Thiele,Female,MeWi6,30%,27.5,15,18,27,20,17,19,18,22,25
 | 
			
		||||
Lara,Troschke,Female,MeWi2,30%,28.5,14,17,28,13,19,21,25,12,24
 | 
			
		||||
Inga-Brit,Turschner,Female,MeWi2,30%,25.5,14,18,34,20,16,19,22,17,30
 | 
			
		||||
Alea,Unger,Female,MeWi5,30%,30,12,18,31,20,20,21,22,15,21.5
 | 
			
		||||
Marie,Wallbaum,Female,MeWi5,30%,28.5,14,18,34,17,20,19,24,12,22
 | 
			
		||||
Katharina,Walz,Female,MeWi4,30%,31,15,18,31,19,19,17,24,17,14.5
 | 
			
		||||
Xiaowei,Wang,Male,NoGroup,30%,30.5,14,18,26,19,17,0,0,0,0
 | 
			
		||||
Lilly-Lu,Warnken,Female,DiKum,30%,30,15,18,30,14,17,19,14,16,24
 | 
			
		||||
,,,,,,,,,,,,,,
 | 
			
		||||
,,,,,,,,,,,,,,
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
		
		
			
  | 
										
											Binary file not shown.
										
									
								
							@@ -1,8 +1,9 @@
 | 
			
		||||
import pandas as pd
 | 
			
		||||
import pprint
 | 
			
		||||
import sys 
 | 
			
		||||
sys.path.append('..')
 | 
			
		||||
sys.path.append('../grapher/dbmodel')
 | 
			
		||||
from model import *
 | 
			
		||||
from utils import *
 | 
			
		||||
 | 
			
		||||
df = pd.read_csv("Student_list.csv")
 | 
			
		||||
df = df.dropna()
 | 
			
		||||
@@ -31,10 +32,8 @@ groups = {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
print(df)
 | 
			
		||||
init_db('WiSe_24_25.db')
 | 
			
		||||
 | 
			
		||||
db.init("WiSe_24_25.db")
 | 
			
		||||
db.connect()
 | 
			
		||||
db.create_tables([Class, Student, Lecture, Submission, Group])
 | 
			
		||||
 | 
			
		||||
# Create Class
 | 
			
		||||
clas = Class.create(name='WiSe 24/25')
 | 
			
		||||
@@ -55,7 +54,8 @@ for index, row in df.iterrows():
 | 
			
		||||
        sex=row["Sex"],
 | 
			
		||||
        class_id=clas.id,
 | 
			
		||||
        group_id=Group.select().where(Group.name == row["Group"]),
 | 
			
		||||
        grader=row["Grader"]
 | 
			
		||||
        grader=row["Grader"],
 | 
			
		||||
        residence_id=-1
 | 
			
		||||
    )
 | 
			
		||||
    
 | 
			
		||||
    for title, points in list(row.to_dict().items())[5:]:
 | 
			
		||||
 
 | 
			
		||||
@@ -1,76 +0,0 @@
 | 
			
		||||
import sys
 | 
			
		||||
sys.path.append("..")
 | 
			
		||||
 | 
			
		||||
from valuation import BaseGrading
 | 
			
		||||
 | 
			
		||||
# Testing
 | 
			
		||||
import unittest
 | 
			
		||||
from unittest.mock import patch
 | 
			
		||||
class TestBaseGrading(unittest.TestCase):
 | 
			
		||||
    test_schema = {"Grade1": 0.1, "Grade2": 0.3}
 | 
			
		||||
    
 | 
			
		||||
    @patch.multiple(BaseGrading, __abstractmethods__=set())
 | 
			
		||||
    def get_base_grader(self):
 | 
			
		||||
        return BaseGrading(self.test_schema, "TestGrader")
 | 
			
		||||
 | 
			
		||||
    def test_getter(self):
 | 
			
		||||
        grader = self.get_base_grader()
 | 
			
		||||
        self.assertEqual(grader.get("Grade1"), self.test_schema["Grade1"])
 | 
			
		||||
        self.assertEqual(grader.get("grade1"), self.test_schema["Grade1"])
 | 
			
		||||
 | 
			
		||||
    def test_len(self):
 | 
			
		||||
        grader = self.get_base_grader()
 | 
			
		||||
        self.assertEqual(len(grader), len(self.test_schema))
 | 
			
		||||
 | 
			
		||||
    def test_contains(self):
 | 
			
		||||
        grader = self.get_base_grader()
 | 
			
		||||
        self.assertTrue(0.1 in grader)
 | 
			
		||||
        self.assertTrue(0.9 in grader)
 | 
			
		||||
        self.assertFalse(100 in grader)
 | 
			
		||||
        self.assertFalse(None in grader)
 | 
			
		||||
        self.assertTrue("Grade1" in grader)
 | 
			
		||||
        self.assertTrue("gRADE2" in grader)
 | 
			
		||||
 | 
			
		||||
    def test_iter(self):
 | 
			
		||||
        grader = self.get_base_grader()
 | 
			
		||||
        for grade, test in zip(grader, self.test_schema):
 | 
			
		||||
            self.assertEqual(grade, test)
 | 
			
		||||
 | 
			
		||||
    def test_reversed(self):
 | 
			
		||||
        grader = self.get_base_grader()
 | 
			
		||||
        for grade, test in zip(reversed(grader), reversed(self.test_schema)):
 | 
			
		||||
            self.assertEqual(grade, test)
 | 
			
		||||
 | 
			
		||||
    def test_str(self):
 | 
			
		||||
        grader = self.get_base_grader()
 | 
			
		||||
        self.assertEqual(str(grader), "TestGrader")
 | 
			
		||||
    
 | 
			
		||||
    def test_repr(self):
 | 
			
		||||
        grader = self.get_base_grader()
 | 
			
		||||
        self.assertEqual(repr(grader), f"<TestGrader: ({str(self.test_schema)})>")
 | 
			
		||||
 | 
			
		||||
    def test_eq(self):
 | 
			
		||||
        grader = self.get_base_grader()
 | 
			
		||||
        self.assertTrue(grader == grader)
 | 
			
		||||
        self.assertTrue(grader != grader)
 | 
			
		||||
 | 
			
		||||
    def test_keys(self):
 | 
			
		||||
        grader = self.get_base_grader()
 | 
			
		||||
        for k1, t1 in zip(grader.keys(), self.test_schema.keys()):
 | 
			
		||||
            self.assertEqual(k1, t1)
 | 
			
		||||
 | 
			
		||||
    def test_items(self):
 | 
			
		||||
        grader = self.get_base_grader()
 | 
			
		||||
        for v1, t1 in zip(grader.values(), self.test_schema.values()):
 | 
			
		||||
            self.assertEqual(v1, t1)
 | 
			
		||||
 | 
			
		||||
    def test_items(self):
 | 
			
		||||
        grader = self.get_base_grader()
 | 
			
		||||
        for g1, t1 in zip(grader.items(), self.test_schema.items()):
 | 
			
		||||
            k, v = g1 
 | 
			
		||||
            tk, tv = t1
 | 
			
		||||
            self.assertEqual(k, tk)
 | 
			
		||||
            self.assertEqual(v, tv)
 | 
			
		||||
 | 
			
		||||
if __name__ == "__main__":
 | 
			
		||||
    unittest.main()
 | 
			
		||||
							
								
								
									
										15
									
								
								grapher/dbmodel/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								grapher/dbmodel/__init__.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,15 @@
 | 
			
		||||
from .utils import (
 | 
			
		||||
    tables,
 | 
			
		||||
    table_labels,
 | 
			
		||||
    init_db,
 | 
			
		||||
    save_as_json,
 | 
			
		||||
    create_from_json
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
from .model import db 
 | 
			
		||||
from .view import Cache
 | 
			
		||||
 | 
			
		||||
# Export tables & corresponding View
 | 
			
		||||
for table in tables:
 | 
			
		||||
    globals()[table.__name__] = table
 | 
			
		||||
    globals()[f"{table.__name__}Cache"] = Cache(table)
 | 
			
		||||
							
								
								
									
										61
									
								
								grapher/dbmodel/model.dbml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										61
									
								
								grapher/dbmodel/model.dbml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,61 @@
 | 
			
		||||
Table Class {
 | 
			
		||||
  id integer [primary key]
 | 
			
		||||
  name varchar
 | 
			
		||||
  created_at timestamp
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
Table Student {
 | 
			
		||||
  id integer [primary key]
 | 
			
		||||
  name varchar
 | 
			
		||||
  sex varchar
 | 
			
		||||
  class_id integer [ref: < Class.id]
 | 
			
		||||
  group_id integer [ref: < Group.id]
 | 
			
		||||
  grader varchar
 | 
			
		||||
  residence integer [ref: - Residence.id]
 | 
			
		||||
  created_at timestamp 
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
Table Lecture {
 | 
			
		||||
  id integer [primary key]
 | 
			
		||||
  title varchar
 | 
			
		||||
  points integer
 | 
			
		||||
  class_id integer [ref: < Class.id]
 | 
			
		||||
  created_at timestamp
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
Table Submission {
 | 
			
		||||
  id integer [primary key]
 | 
			
		||||
  student_id integer [ref: < Student.id]
 | 
			
		||||
  lecture_id integer [ref: < Lecture.id]
 | 
			
		||||
  points float
 | 
			
		||||
  created_at timestamp
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
Table Group {
 | 
			
		||||
  id integer [primary key]
 | 
			
		||||
  name varchar
 | 
			
		||||
  project varchar 
 | 
			
		||||
  class_id integer [ref: < Class.id]
 | 
			
		||||
  created_at timestamp
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
Table Town {
 | 
			
		||||
  plz integer [primary key]
 | 
			
		||||
  name varchar 
 | 
			
		||||
  created_at timestamp
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
Table Address {
 | 
			
		||||
  id integer [primary key]
 | 
			
		||||
  street varchar 
 | 
			
		||||
  number integer
 | 
			
		||||
  created_at timestamp
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
Table Residence {
 | 
			
		||||
  id integer [primary key]
 | 
			
		||||
  town_plz integer [ref: > Town.plz]
 | 
			
		||||
  address_id integer [ref: > Address.id]
 | 
			
		||||
  created_at timestamp 
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										117
									
								
								grapher/dbmodel/model.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										117
									
								
								grapher/dbmodel/model.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,117 @@
 | 
			
		||||
'''
 | 
			
		||||
peewee ORM Database Model definition 
 | 
			
		||||
Documentation: https://docs.peewee-orm.com
 | 
			
		||||
 | 
			
		||||
please look up model.dbml for Documentation
 | 
			
		||||
Online Viewer: https://dbdiagram.io 
 | 
			
		||||
 | 
			
		||||
'''
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
from peewee import *
 | 
			
		||||
from datetime import datetime
 | 
			
		||||
 | 
			
		||||
import json
 | 
			
		||||
from typing import TextIO
 | 
			
		||||
from pathlib import Path
 | 
			
		||||
 | 
			
		||||
db = DatabaseProxy()
 | 
			
		||||
 | 
			
		||||
# WIP: Add Switch Function
 | 
			
		||||
if True:
 | 
			
		||||
    database = SqliteDatabase(None, autoconnect=False)
 | 
			
		||||
else:
 | 
			
		||||
    database = PostgresqlDatabase(None, autoconnect=False)
 | 
			
		||||
 | 
			
		||||
db.initialize(database)
 | 
			
		||||
 | 
			
		||||
class BaseModel(Model):
 | 
			
		||||
    '''
 | 
			
		||||
    Base Model (needed for peewee) defines the Class Meta 
 | 
			
		||||
    and bounds Global db obj to every Table
 | 
			
		||||
    '''
 | 
			
		||||
    class Meta:
 | 
			
		||||
        database = db
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Town(BaseModel):
 | 
			
		||||
    '''
 | 
			
		||||
    Table for Storing Town Data 
 | 
			
		||||
    '''
 | 
			
		||||
    plz = IntegerField(primary_key=True)
 | 
			
		||||
    name = CharField()
 | 
			
		||||
    created_at = DateTimeField(default=datetime.now)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Address(BaseModel):
 | 
			
		||||
    '''
 | 
			
		||||
    Table for Storing Address Data
 | 
			
		||||
    '''
 | 
			
		||||
    street = CharField()
 | 
			
		||||
    number = IntegerField()
 | 
			
		||||
    created_at = DateTimeField(default=datetime.now)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Residence(BaseModel):
 | 
			
		||||
    '''
 | 
			
		||||
    Table for Storing a unique Postal Adress 
 | 
			
		||||
    by combining Towntable Data with Addresstable Data
 | 
			
		||||
    '''
 | 
			
		||||
    town_plz = ForeignKeyField(Town, backref='plz')
 | 
			
		||||
    address_id = ForeignKeyField(Address, backref='address')
 | 
			
		||||
    created_at = DateTimeField(default=datetime.now)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Class(BaseModel):
 | 
			
		||||
    '''
 | 
			
		||||
    Baseline Order Base 
 | 
			
		||||
    
 | 
			
		||||
    Table for Storing a Class 
 | 
			
		||||
    '''
 | 
			
		||||
    name = CharField()
 | 
			
		||||
    created_at = DateTimeField(default=datetime.now)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Group(BaseModel):
 | 
			
		||||
    '''
 | 
			
		||||
    Table for Storing a project Group 
 | 
			
		||||
    '''
 | 
			
		||||
    name = CharField()
 | 
			
		||||
    project = CharField()
 | 
			
		||||
    class_id = ForeignKeyField(Class, backref='class')
 | 
			
		||||
    created_at = DateTimeField(default=datetime.now)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Student(BaseModel):
 | 
			
		||||
    '''
 | 
			
		||||
    Table for Storing a Student and linking him to appropiate Tables
 | 
			
		||||
    '''
 | 
			
		||||
    prename = CharField()
 | 
			
		||||
    surname = CharField()
 | 
			
		||||
    sex = CharField()
 | 
			
		||||
    class_id = ForeignKeyField(Class, backref='class')
 | 
			
		||||
    group_id = ForeignKeyField(Group, backref='group')
 | 
			
		||||
    grader = CharField()
 | 
			
		||||
    residence = ForeignKeyField(Residence, backref='residence', null=True)
 | 
			
		||||
    created_at = DateTimeField(default=datetime.now)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Lecture(BaseModel):
 | 
			
		||||
    '''
 | 
			
		||||
    Table for defining a Lecture
 | 
			
		||||
    '''
 | 
			
		||||
    title = CharField()
 | 
			
		||||
    points = IntegerField()
 | 
			
		||||
    class_id = ForeignKeyField(Class, backref='class')
 | 
			
		||||
    created_at = DateTimeField(default=datetime.now)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Submission(BaseModel):
 | 
			
		||||
    '''
 | 
			
		||||
    Table for defining a Submission from a Student for Lecture
 | 
			
		||||
    '''
 | 
			
		||||
    student_id = ForeignKeyField(Student, backref='student')
 | 
			
		||||
    lecture_id = ForeignKeyField(Lecture, backref='lecture')
 | 
			
		||||
    points = FloatField()
 | 
			
		||||
    created_at = DateTimeField(default=datetime.now)
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										172
									
								
								grapher/dbmodel/utils.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										172
									
								
								grapher/dbmodel/utils.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,172 @@
 | 
			
		||||
'''
 | 
			
		||||
Module provids Utilities to Interface with Database Model
 | 
			
		||||
 | 
			
		||||
Includes:
 | 
			
		||||
    - DateTime De-/Encoder for JSON loads/dumps
 | 
			
		||||
    - Database Initialize Helper
 | 
			
		||||
    - Module Class Summarizer
 | 
			
		||||
'''
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
import sys, inspect, json 
 | 
			
		||||
from datetime import datetime, date
 | 
			
		||||
from pathlib import Path
 | 
			
		||||
from playhouse.shortcuts import model_to_dict, dict_to_model
 | 
			
		||||
from .model import *
 | 
			
		||||
 | 
			
		||||
class DateTimeEncoder(json.JSONEncoder):
 | 
			
		||||
    '''
 | 
			
		||||
    Helper Class converting datetime.datetime -> isoformated String
 | 
			
		||||
    '''
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    def default(self, obj):
 | 
			
		||||
        if isinstance(obj, (datetime, date)):
 | 
			
		||||
            return obj.isoformat()
 | 
			
		||||
 | 
			
		||||
# Predefined used timestamp keys
 | 
			
		||||
KEYNAMES = [
 | 
			
		||||
    "date", "timestamp", "last_updatet", "last_created", "last_editet", "created_at"
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
def DateTimeDecoder(obj: dict) -> dict:
 | 
			
		||||
    '''
 | 
			
		||||
    Helper Function converting isoformated String -> datetime.datetime
 | 
			
		||||
    '''
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    for key, value in obj.items():
 | 
			
		||||
        if key not in KEYNAMES:
 | 
			
		||||
            continue
 | 
			
		||||
        try:
 | 
			
		||||
            obj[key] = datetime.fromisoformat(value)
 | 
			
		||||
        except ValueError:
 | 
			
		||||
            pass
 | 
			
		||||
    return obj
 | 
			
		||||
   
 | 
			
		||||
 | 
			
		||||
def get_module_cls(module: str) -> tuple[tuple[str, object]]:
 | 
			
		||||
    '''
 | 
			
		||||
    Given a module name function returns a list of Classes defined only in that Module
 | 
			
		||||
    '''
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    assert type(module) is str, "Provided Module isn't a String"
 | 
			
		||||
    members = inspect.getmembers(sys.modules[module], inspect.isclass)
 | 
			
		||||
    return tuple(member for member in members if member[1].__module__ == module)
 | 
			
		||||
 | 
			
		||||
# precalculated from model.py 
 | 
			
		||||
tables = tuple(table[1] for table in get_module_cls('dbmodel.model') if not table[1] is BaseModel)
 | 
			
		||||
table_labels = tuple(table[0] for table in get_module_cls('dbmodel.model') if not table[1] is BaseModel)
 | 
			
		||||
 | 
			
		||||
def init_db(name: Path | str) -> None:
 | 
			
		||||
    '''
 | 
			
		||||
    (Creates,) Connects and Initializes the db descriptor from a given *.db file 
 | 
			
		||||
    '''
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    assert isinstance(name, (Path, str)), "Provided Name isn't of type Path | str"
 | 
			
		||||
    
 | 
			
		||||
    # convert to String
 | 
			
		||||
    if type(name) is Path:
 | 
			
		||||
        name = str(name)
 | 
			
		||||
    
 | 
			
		||||
    # Switch Database; Initialize if needed
 | 
			
		||||
    if not db.is_closed():
 | 
			
		||||
        db.close()
 | 
			
		||||
    db.init(name)
 | 
			
		||||
    db.connect()
 | 
			
		||||
    db.create_tables(tables)  # Ensure tables exist
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def save_as_json(filename: str, path: Path | str = Path('.')) -> Path:
 | 
			
		||||
    '''
 | 
			
		||||
    Saves the current loaded Database as <filename>.json to given <path>
 | 
			
		||||
 | 
			
		||||
    JSON Format:
 | 
			
		||||
        {
 | 
			
		||||
            <Tablename>: [
 | 
			
		||||
                {<Tableparam>: <Value>, ...}
 | 
			
		||||
            ],
 | 
			
		||||
            ...
 | 
			
		||||
        }
 | 
			
		||||
    '''
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    assert type(filename) is str, "Provided Filename isn't a String"
 | 
			
		||||
    assert isinstance(path, Path | str), "Provided Path isn't of type Path | str"
 | 
			
		||||
 | 
			
		||||
    # Convert given path to Path
 | 
			
		||||
    if type(path) is str:
 | 
			
		||||
        path = Path(path)
 | 
			
		||||
    
 | 
			
		||||
    filename = Path(filename)
 | 
			
		||||
 | 
			
		||||
    # Set Correct Suffix
 | 
			
		||||
    if filename.suffix != '.json':
 | 
			
		||||
        filename = filename.with_suffix('.json')
 | 
			
		||||
 | 
			
		||||
    file = path.resolve().absolute() / filename
 | 
			
		||||
    
 | 
			
		||||
    # dump db
 | 
			
		||||
    database = {
 | 
			
		||||
        table.__name__: list(table.select().dicts())
 | 
			
		||||
        for table in tables
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    # db -> json
 | 
			
		||||
    with open(file, "w") as fp:
 | 
			
		||||
        json.dump(database, fp, cls=DateTimeEncoder)
 | 
			
		||||
 | 
			
		||||
    return file
 | 
			
		||||
 | 
			
		||||
def create_from_json(dbname: str, file: Path | str) -> Path:
 | 
			
		||||
    '''
 | 
			
		||||
    Creates a new Database <dbname>.db in from given <file>
 | 
			
		||||
 | 
			
		||||
    Valid JSON Format:
 | 
			
		||||
        {
 | 
			
		||||
            <Tablename>: [
 | 
			
		||||
                {<Tableparam>: <Value>, ...}
 | 
			
		||||
            ],
 | 
			
		||||
            ...
 | 
			
		||||
        }
 | 
			
		||||
    '''
 | 
			
		||||
 | 
			
		||||
    assert type(dbname) is str, "Provided Database name isn't a String"
 | 
			
		||||
    assert isinstance(file, (Path | str)), "Provided file descriptor isn't of type Path | str"
 | 
			
		||||
 | 
			
		||||
    # Convert file to Path 
 | 
			
		||||
    if type(file) is str:
 | 
			
		||||
        file = Path(file)
 | 
			
		||||
    assert file.suffix == '.json', "File isn't a JSON"
 | 
			
		||||
    
 | 
			
		||||
    # Set correct Suffix and connect
 | 
			
		||||
    dbname = Path(dbname).resolve().absolute()
 | 
			
		||||
    if dbname.suffix != '.db':
 | 
			
		||||
        dbname = dbname.with_suffix('.db')
 | 
			
		||||
    init_db(dbname)
 | 
			
		||||
 | 
			
		||||
    # load from json 
 | 
			
		||||
    with open(file, "r") as fp:
 | 
			
		||||
        data = json.load(fp, object_hook=DateTimeDecoder)
 | 
			
		||||
    
 | 
			
		||||
    assert all([keys == table.__name__ for keys, table in zip(data.keys(), tables)]), f"{file.name} can't be convert to Database"
 | 
			
		||||
 | 
			
		||||
    # Insert Data 
 | 
			
		||||
    for k, v in data.items():
 | 
			
		||||
        if not v:
 | 
			
		||||
            continue
 | 
			
		||||
 | 
			
		||||
        table = next((table for table in tables if table.__name__ == k))
 | 
			
		||||
        table.insert_many(v).execute()
 | 
			
		||||
    
 | 
			
		||||
    return dbname 
 | 
			
		||||
 | 
			
		||||
if __name__ == "__main__":
 | 
			
		||||
    init_db('/home/phil/programming/grapher/assets/WiSe_24_25.db')
 | 
			
		||||
    f = save_as_json("file")
 | 
			
		||||
    print(f)
 | 
			
		||||
    print(create_from_json("test", f))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										122
									
								
								grapher/dbmodel/view.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										122
									
								
								grapher/dbmodel/view.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,122 @@
 | 
			
		||||
'''
 | 
			
		||||
Module providing Database Views
 | 
			
		||||
'''
 | 
			
		||||
 | 
			
		||||
from collections.abc import Sequence 
 | 
			
		||||
from typing import Any
 | 
			
		||||
 | 
			
		||||
from playhouse.shortcuts import model_to_dict
 | 
			
		||||
 | 
			
		||||
from .model import *
 | 
			
		||||
from .utils import tables
 | 
			
		||||
 | 
			
		||||
class Cache(Sequence):
 | 
			
		||||
    '''
 | 
			
		||||
    Sequence Class caching Data from a BaseModel Table 
 | 
			
		||||
    '''
 | 
			
		||||
    def __init__(self, table: BaseModel) -> None:
 | 
			
		||||
        assert any(table.__name__ == t.__name__ for t in tables), f"Must provide a BaseModel Object; not {type(table)}"
 | 
			
		||||
 | 
			
		||||
        self.table = table
 | 
			
		||||
        self.data = None
 | 
			
		||||
 | 
			
		||||
    def update(self, value: Any, update_param: str = 'id') -> None:
 | 
			
		||||
        '''
 | 
			
		||||
        Updates Cache by selecting data given by self.table.<update_param> == value 
 | 
			
		||||
 | 
			
		||||
        '''
 | 
			
		||||
        assert update_param in self.table._meta.sorted_field_names, f"Update Parameter must exist on {self.table.__name__}"
 | 
			
		||||
        
 | 
			
		||||
        param = getattr(self.table, update_param)
 | 
			
		||||
        data = self.table.select().where(param == value)
 | 
			
		||||
        self.data = list(data) if data else None
 | 
			
		||||
 | 
			
		||||
    def is_None(self) -> bool:
 | 
			
		||||
        '''
 | 
			
		||||
        Check if Cache is Empty
 | 
			
		||||
        '''
 | 
			
		||||
        return self.data is None
 | 
			
		||||
 | 
			
		||||
    def __getitem__(self, id: int) -> BaseModel | None:
 | 
			
		||||
        '''
 | 
			
		||||
        Get item from Cache based on provided id
 | 
			
		||||
        '''
 | 
			
		||||
        assert type(id) is int, "ID must be an Integer"
 | 
			
		||||
        for el in self.data:
 | 
			
		||||
            if el.get_id() == id:
 | 
			
		||||
                return el
 | 
			
		||||
        return None
 | 
			
		||||
 
 | 
			
		||||
    def __len__(self) -> int:
 | 
			
		||||
        '''
 | 
			
		||||
        returns Number of Elements Stored in Cache
 | 
			
		||||
        '''
 | 
			
		||||
        return len(self.data)
 | 
			
		||||
 | 
			
		||||
    def __contains__(self, data: BaseModel | int) -> bool:
 | 
			
		||||
        '''
 | 
			
		||||
        Check if provided BaseModel object is stored in Cache
 | 
			
		||||
        '''
 | 
			
		||||
        assert isinstance(data, (self.table, int)), f"Check could only be made if Object of type {self.table.__name__} or integer id"
 | 
			
		||||
 | 
			
		||||
        if isinstance(data, BaseModel):
 | 
			
		||||
            return data in self.data
 | 
			
		||||
        if isinstance(data, int):
 | 
			
		||||
            for el in self.data:
 | 
			
		||||
                if el.get_id() == data:
 | 
			
		||||
                    return True
 | 
			
		||||
        return False
 | 
			
		||||
 | 
			
		||||
    def __iter__(self) -> BaseModel:
 | 
			
		||||
        '''
 | 
			
		||||
        Generator returning BaseModel Objects stored in Cache
 | 
			
		||||
        Note: Like SQL there is no real Order to the Data
 | 
			
		||||
        '''
 | 
			
		||||
        yield from self.data 
 | 
			
		||||
 | 
			
		||||
    def __reversed__(self) -> BaseModel:
 | 
			
		||||
        '''
 | 
			
		||||
        Same as __iter__ but reversed
 | 
			
		||||
        '''
 | 
			
		||||
        yield from reversed(self.data)
 | 
			
		||||
    
 | 
			
		||||
    def index(self, value, start=0, stop=None):
 | 
			
		||||
        '''
 | 
			
		||||
        Not Implemented do to SQL Order Issue
 | 
			
		||||
        '''
 | 
			
		||||
        return NotImplemented
 | 
			
		||||
 | 
			
		||||
    def count(self, value):
 | 
			
		||||
        '''
 | 
			
		||||
        Not Implemented do to assumed Uniqness of Data
 | 
			
		||||
        '''
 | 
			
		||||
        return NotImplemented
 | 
			
		||||
 | 
			
		||||
    def to_dict(self, recurse=False) -> list[dict]:
 | 
			
		||||
        '''
 | 
			
		||||
        Returns a list containing the resolved BaseModel data as dict 
 | 
			
		||||
        
 | 
			
		||||
        If recurse == True Database trys to recurse all Relationships
 | 
			
		||||
        Note: This Operation isn't Cache performed!
 | 
			
		||||
 | 
			
		||||
        Format:
 | 
			
		||||
        [
 | 
			
		||||
            {<TableParam>: <Value>, ...},
 | 
			
		||||
            ...
 | 
			
		||||
        ]
 | 
			
		||||
        '''
 | 
			
		||||
        assert type(recurse) is bool, "recurse must be bool"
 | 
			
		||||
        return [model_to_dict(data, recurse=recurse) for data in self.data]
 | 
			
		||||
 | 
			
		||||
    def filter(self):
 | 
			
		||||
        '''
 | 
			
		||||
        WIP
 | 
			
		||||
        '''
 | 
			
		||||
        pass
 | 
			
		||||
 | 
			
		||||
if __name__ == "__main__":
 | 
			
		||||
    from utils import init_db
 | 
			
		||||
    init_db('test.db')
 | 
			
		||||
    from pprint import pprint
 | 
			
		||||
   
 | 
			
		||||
    help(TownCache)
 | 
			
		||||
							
								
								
									
										8
									
								
								grapher/grader/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								grapher/grader/__init__.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,8 @@
 | 
			
		||||
from .valuation import (
 | 
			
		||||
    get_gradings,
 | 
			
		||||
    get_grader,
 | 
			
		||||
    Std30PercentRule,
 | 
			
		||||
    Std50PercentRule,
 | 
			
		||||
    StdGermanGradingMiddleSchool,
 | 
			
		||||
    StdGermanGradingHighSchool
 | 
			
		||||
)
 | 
			
		||||
@@ -158,12 +158,12 @@ class StdGermanGrading(BaseGrading):
 | 
			
		||||
Std30PercentRule = StdPercentRule({
 | 
			
		||||
    "pAssed": 0.3,
 | 
			
		||||
    "Failed": 0.0
 | 
			
		||||
}, "Std30PercentRule", "30 Percent")
 | 
			
		||||
}, "Std30PercentRule", "30%")
 | 
			
		||||
 | 
			
		||||
Std50PercentRule = StdPercentRule({
 | 
			
		||||
    "Passed": 0.5,
 | 
			
		||||
    "Failed": 0.0
 | 
			
		||||
}, "Std50PercentRule", "50 Percent")
 | 
			
		||||
}, "Std50PercentRule", "50%")
 | 
			
		||||
 | 
			
		||||
StdGermanGradingMiddleSchool = StdGermanGrading({
 | 
			
		||||
    1: 0.96,
 | 
			
		||||
@@ -172,7 +172,7 @@ StdGermanGradingMiddleSchool = StdGermanGrading({
 | 
			
		||||
    4: 0.45,
 | 
			
		||||
    5: 0.16,
 | 
			
		||||
    6: 0.00
 | 
			
		||||
}, "StdGermanGradingMiddleSchool", "Secondary School")
 | 
			
		||||
}, "StdGermanGradingMiddleSchool", "Mittelstufe")
 | 
			
		||||
 | 
			
		||||
StdGermanGradingHighSchool = StdGermanGrading({
 | 
			
		||||
    15: 0.95,
 | 
			
		||||
							
								
								
									
										4
									
								
								grapher/gui/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								grapher/gui/__init__.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,4 @@
 | 
			
		||||
from .appstate import AppState
 | 
			
		||||
from .analyzer import analyzer_layout
 | 
			
		||||
from .database import database_editor_layout
 | 
			
		||||
from .gui import menu_bar, status_bar
 | 
			
		||||
							
								
								
									
										63
									
								
								grapher/gui/analyzer/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										63
									
								
								grapher/gui/analyzer/__init__.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,63 @@
 | 
			
		||||
from imgui_bundle import hello_imgui, imgui
 | 
			
		||||
#from app_state import AppState
 | 
			
		||||
from typing import List
 | 
			
		||||
 | 
			
		||||
from .analyzer import *
 | 
			
		||||
 | 
			
		||||
def analyzer_docking_splits() -> List[hello_imgui.DockingSplit]:
 | 
			
		||||
    split_main_misc = hello_imgui.DockingSplit()
 | 
			
		||||
    split_main_misc.initial_dock = "MainDockSpace"
 | 
			
		||||
    split_main_misc.new_dock = "MiscSpace"
 | 
			
		||||
    split_main_misc.direction = imgui.Dir.down
 | 
			
		||||
    split_main_misc.ratio = 0.25
 | 
			
		||||
 | 
			
		||||
    # Then, add a space to the left which occupies a column whose width is 25% of the app width
 | 
			
		||||
    split_main_command = hello_imgui.DockingSplit()
 | 
			
		||||
    split_main_command.initial_dock = "MainDockSpace"
 | 
			
		||||
    split_main_command.new_dock = "CommandSpace"
 | 
			
		||||
    split_main_command.direction = imgui.Dir.left
 | 
			
		||||
    split_main_command.ratio = 0.2
 | 
			
		||||
 | 
			
		||||
    # Then, add CommandSpace2 below MainDockSpace
 | 
			
		||||
    split_main_command2 = hello_imgui.DockingSplit()
 | 
			
		||||
    split_main_command2.initial_dock = "MainDockSpace"
 | 
			
		||||
    split_main_command2.new_dock = "CommandSpace2"
 | 
			
		||||
    split_main_command2.direction = imgui.Dir.down
 | 
			
		||||
    split_main_command2.ratio = 0.25
 | 
			
		||||
 | 
			
		||||
    splits = [split_main_misc, split_main_command, split_main_command2]
 | 
			
		||||
    return splits
 | 
			
		||||
 | 
			
		||||
def set_analyzer_layout(app_state) -> List[hello_imgui.DockableWindow]:
 | 
			
		||||
    student_selector = hello_imgui.DockableWindow()
 | 
			
		||||
    student_selector.label = "Students"
 | 
			
		||||
    student_selector.dock_space_name = "CommandSpace"
 | 
			
		||||
    student_selector.gui_function = lambda: student_list(app_state)
 | 
			
		||||
    
 | 
			
		||||
    student_info = hello_imgui.DockableWindow()
 | 
			
		||||
    student_info.label = "Student Analyzer"
 | 
			
		||||
    student_info.dock_space_name = "MainDockSpace"
 | 
			
		||||
    student_info.gui_function = lambda: student_graph(app_state)
 | 
			
		||||
 | 
			
		||||
    sex_info = hello_imgui.DockableWindow()
 | 
			
		||||
    sex_info.label = "Analyze by Gender"
 | 
			
		||||
    sex_info.dock_space_name = "MainDockSpace"
 | 
			
		||||
    sex_info.gui_function = lambda: sex_graph(app_state)
 | 
			
		||||
 | 
			
		||||
    student_ranking = hello_imgui.DockableWindow()
 | 
			
		||||
    student_ranking.label = "Ranking"
 | 
			
		||||
    student_ranking.dock_space_name = "MainDockSpace"
 | 
			
		||||
    student_ranking.gui_function = lambda: ranking(app_state)
 | 
			
		||||
 | 
			
		||||
    return [
 | 
			
		||||
        student_selector, 
 | 
			
		||||
        student_info, sex_info,
 | 
			
		||||
        student_ranking, 
 | 
			
		||||
    ]
 | 
			
		||||
 | 
			
		||||
def analyzer_layout(app_state) -> hello_imgui.DockingParams:
 | 
			
		||||
    docking_params = hello_imgui.DockingParams()
 | 
			
		||||
    docking_params.layout_name = "Analyzer"
 | 
			
		||||
    docking_params.docking_splits = analyzer_docking_splits() 
 | 
			
		||||
    docking_params.dockable_windows = set_analyzer_layout(app_state)
 | 
			
		||||
    return docking_params
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
# Custom 
 | 
			
		||||
from model import *
 | 
			
		||||
from appstate import AppState
 | 
			
		||||
from dbmodel import *
 | 
			
		||||
from gui import AppState
 | 
			
		||||
from grader.valuation import *
 | 
			
		||||
 | 
			
		||||
# External
 | 
			
		||||
@@ -140,8 +140,8 @@ def student_graph(app_state: AppState) -> None:
 | 
			
		||||
        statics.points = np.sum(statics.sub_points)
 | 
			
		||||
        if statics.points.is_integer():
 | 
			
		||||
            statics.points = int(statics.points)
 | 
			
		||||
        statics.grader = get_grader("Oberstufe")
 | 
			
		||||
        #statics.grader = get_grader(statics.student.grader)
 | 
			
		||||
        #statics.grader = get_grader("Oberstufe")
 | 
			
		||||
        statics.grader = get_grader(statics.student.grader)
 | 
			
		||||
 | 
			
		||||
        statics.subs_data = np.array([p/mp.points for p, mp in zip(statics.sub_points, statics.lectures)], dtype=np.float32)*100
 | 
			
		||||
        statics.subs_labels = [f"{l.title} {int(points) if points.is_integer() else points}/{l.points}" for l, points in zip(statics.lectures, statics.sub_points)]
 | 
			
		||||
@@ -316,60 +316,4 @@ def ranking(app_state: AppState) -> None:
 | 
			
		||||
    if imgui.button("Change"):
 | 
			
		||||
        statics.state = not statics.state
 | 
			
		||||
 | 
			
		||||
def analyzer_docking_splits() -> List[hello_imgui.DockingSplit]:
 | 
			
		||||
    split_main_misc = hello_imgui.DockingSplit()
 | 
			
		||||
    split_main_misc.initial_dock = "MainDockSpace"
 | 
			
		||||
    split_main_misc.new_dock = "MiscSpace"
 | 
			
		||||
    split_main_misc.direction = imgui.Dir.down
 | 
			
		||||
    split_main_misc.ratio = 0.25
 | 
			
		||||
 | 
			
		||||
    # Then, add a space to the left which occupies a column whose width is 25% of the app width
 | 
			
		||||
    split_main_command = hello_imgui.DockingSplit()
 | 
			
		||||
    split_main_command.initial_dock = "MainDockSpace"
 | 
			
		||||
    split_main_command.new_dock = "CommandSpace"
 | 
			
		||||
    split_main_command.direction = imgui.Dir.left
 | 
			
		||||
    split_main_command.ratio = 0.2
 | 
			
		||||
 | 
			
		||||
    # Then, add CommandSpace2 below MainDockSpace
 | 
			
		||||
    split_main_command2 = hello_imgui.DockingSplit()
 | 
			
		||||
    split_main_command2.initial_dock = "MainDockSpace"
 | 
			
		||||
    split_main_command2.new_dock = "CommandSpace2"
 | 
			
		||||
    split_main_command2.direction = imgui.Dir.down
 | 
			
		||||
    split_main_command2.ratio = 0.25
 | 
			
		||||
 | 
			
		||||
    splits = [split_main_misc, split_main_command, split_main_command2]
 | 
			
		||||
    return splits
 | 
			
		||||
 | 
			
		||||
def set_analyzer_layout(app_state: AppState) -> List[hello_imgui.DockableWindow]:
 | 
			
		||||
    student_selector = hello_imgui.DockableWindow()
 | 
			
		||||
    student_selector.label = "Students"
 | 
			
		||||
    student_selector.dock_space_name = "CommandSpace"
 | 
			
		||||
    student_selector.gui_function = lambda: student_list(app_state)
 | 
			
		||||
    
 | 
			
		||||
    student_info = hello_imgui.DockableWindow()
 | 
			
		||||
    student_info.label = "Student Analyzer"
 | 
			
		||||
    student_info.dock_space_name = "MainDockSpace"
 | 
			
		||||
    student_info.gui_function = lambda: student_graph(app_state)
 | 
			
		||||
 | 
			
		||||
    sex_info = hello_imgui.DockableWindow()
 | 
			
		||||
    sex_info.label = "Analyze by Gender"
 | 
			
		||||
    sex_info.dock_space_name = "MainDockSpace"
 | 
			
		||||
    sex_info.gui_function = lambda: sex_graph(app_state)
 | 
			
		||||
 | 
			
		||||
    student_ranking = hello_imgui.DockableWindow()
 | 
			
		||||
    student_ranking.label = "Ranking"
 | 
			
		||||
    student_ranking.dock_space_name = "MainDockSpace"
 | 
			
		||||
    student_ranking.gui_function = lambda: ranking(app_state)
 | 
			
		||||
 | 
			
		||||
    return [
 | 
			
		||||
        student_selector, 
 | 
			
		||||
        student_info, sex_info,
 | 
			
		||||
        student_ranking, 
 | 
			
		||||
    ]
 | 
			
		||||
 | 
			
		||||
def analyzer_layout(app_state: AppState) -> hello_imgui.DockingParams:
 | 
			
		||||
    docking_params = hello_imgui.DockingParams()
 | 
			
		||||
    docking_params.layout_name = "Analyzer"
 | 
			
		||||
    docking_params.docking_splits = analyzer_docking_splits() 
 | 
			
		||||
    docking_params.dockable_windows = set_analyzer_layout(app_state)
 | 
			
		||||
    return docking_params
 | 
			
		||||
@@ -1,6 +1,4 @@
 | 
			
		||||
from imgui_bundle import hello_imgui
 | 
			
		||||
from model import *
 | 
			
		||||
from datetime import datetime
 | 
			
		||||
from dbmodel import *
 | 
			
		||||
 | 
			
		||||
class AppState:
 | 
			
		||||
    current_class_id: int 
 | 
			
		||||
@@ -28,8 +26,6 @@ class AppState:
 | 
			
		||||
        submissions = Submission.select().where(Submission.lecture_id == self.current_lecture_id and Submission.student_id == self.current_student_id)
 | 
			
		||||
        self.current_submission_id = submissions[0].id if submissions else None
 | 
			
		||||
        
 | 
			
		||||
        LOG_DEBUG(f"Updated App State {repr(self)}")
 | 
			
		||||
 | 
			
		||||
    def __repr__(self):
 | 
			
		||||
        return f'''
 | 
			
		||||
        Class ID: {self.current_class_id}
 | 
			
		||||
@@ -38,12 +34,5 @@ class AppState:
 | 
			
		||||
        Submission ID: {self.current_submission_id}
 | 
			
		||||
        '''
 | 
			
		||||
 | 
			
		||||
def log(log_level: hello_imgui.LogLevel, msg: str) -> None:
 | 
			
		||||
    time = datetime.now().strftime("%X")
 | 
			
		||||
    hello_imgui.log(log_level, f"[{time}] {msg}")
 | 
			
		||||
 | 
			
		||||
LOG_DEBUG = lambda msg: log(hello_imgui.LogLevel.debug, msg)
 | 
			
		||||
LOG_INFO = lambda msg: log(hello_imgui.LogLevel.info, msg)
 | 
			
		||||
LOG_WARNING = lambda msg: log(hello_imgui.LogLevel.warning, msg)
 | 
			
		||||
LOG_ERROR = lambda msg: log(hello_imgui.LogLevel.error, msg)
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										80
									
								
								grapher/gui/database/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										80
									
								
								grapher/gui/database/__init__.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,80 @@
 | 
			
		||||
from imgui_bundle import hello_imgui, imgui
 | 
			
		||||
#from ..app_state import AppState
 | 
			
		||||
from typing import List
 | 
			
		||||
 | 
			
		||||
from .database import *
 | 
			
		||||
 | 
			
		||||
from .editor import 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
 | 
			
		||||
 | 
			
		||||
    split_main_command2 = hello_imgui.DockingSplit()
 | 
			
		||||
    split_main_command2.initial_dock = "CommandSpace"
 | 
			
		||||
    split_main_command2.new_dock = "CommandSpace2"
 | 
			
		||||
    split_main_command2.direction = imgui.Dir.right
 | 
			
		||||
    split_main_command2.ratio = 0.3
 | 
			
		||||
 | 
			
		||||
    split_main_misc = hello_imgui.DockingSplit()
 | 
			
		||||
    split_main_misc.initial_dock = "MainDockSpace"
 | 
			
		||||
    split_main_misc.new_dock = "MiscSpace"
 | 
			
		||||
    split_main_misc.direction = imgui.Dir.left
 | 
			
		||||
    split_main_misc.ratio = 0.2
 | 
			
		||||
 | 
			
		||||
    return [split_main_misc, split_main_command, split_main_command2]
 | 
			
		||||
 | 
			
		||||
def set_database_editor_layout(app_state) -> 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"
 | 
			
		||||
    file_dialog.gui_function = lambda: select_file(app_state)
 | 
			
		||||
 | 
			
		||||
    log = hello_imgui.DockableWindow()
 | 
			
		||||
    log.label = "Logs"
 | 
			
		||||
    log.dock_space_name = "CommandSpace2"
 | 
			
		||||
    log.gui_function = hello_imgui.log_gui
 | 
			
		||||
 | 
			
		||||
    table_view = hello_imgui.DockableWindow()
 | 
			
		||||
    table_view.label = "Table"
 | 
			
		||||
    table_view.dock_space_name = "MainDockSpace"
 | 
			
		||||
    table_view.gui_function = lambda: table(app_state)
 | 
			
		||||
 | 
			
		||||
    eeditor = hello_imgui.DockableWindow()
 | 
			
		||||
    eeditor.label = "Editor"
 | 
			
		||||
    eeditor.dock_space_name = "CommandSpace"
 | 
			
		||||
    eeditor.gui_function = lambda: editor(app_state)
 | 
			
		||||
    
 | 
			
		||||
    return [file_dialog, log, table_view, eeditor]
 | 
			
		||||
 | 
			
		||||
def database_editor_layout(app_state) -> 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
 | 
			
		||||
@@ -7,8 +7,9 @@ to set up the database editing environment.
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
# Custom
 | 
			
		||||
from model import *
 | 
			
		||||
from appstate import * 
 | 
			
		||||
from dbmodel import *
 | 
			
		||||
from gui import * 
 | 
			
		||||
from grader import get_gradings
 | 
			
		||||
 | 
			
		||||
# External
 | 
			
		||||
from imgui_bundle import (
 | 
			
		||||
@@ -20,11 +21,18 @@ from imgui_bundle import (
 | 
			
		||||
    hello_imgui
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
import peewee
 | 
			
		||||
 | 
			
		||||
# Built In 
 | 
			
		||||
from typing import List
 | 
			
		||||
import shelve
 | 
			
		||||
from pathlib import Path
 | 
			
		||||
from datetime import datetime
 | 
			
		||||
from datetime import datetime, timezone
 | 
			
		||||
import pytz
 | 
			
		||||
from tzlocal import get_localzone
 | 
			
		||||
 | 
			
		||||
LOG_INFO = lambda x: x
 | 
			
		||||
LOG_DEBUG = lambda x: x
 | 
			
		||||
 | 
			
		||||
def file_info(path: Path) -> None:
 | 
			
		||||
    """
 | 
			
		||||
@@ -86,7 +94,7 @@ def select_file(app_state: AppState):
 | 
			
		||||
        
 | 
			
		||||
        # Retrieve the last used database file from persistent storage
 | 
			
		||||
        with shelve.open("state") as state:
 | 
			
		||||
            statics.current = Path(state["DB"])
 | 
			
		||||
            statics.current = Path(state.get("DB") or "")
 | 
			
		||||
        statics.inited = True
 | 
			
		||||
    
 | 
			
		||||
    # Render UI title and display file information
 | 
			
		||||
@@ -125,18 +133,14 @@ def select_file(app_state: AppState):
 | 
			
		||||
            # Handle JSON files by converting them to SQLite databases
 | 
			
		||||
            if statics.res.extension() == '.json':
 | 
			
		||||
                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, Group])
 | 
			
		||||
                init_db(file) 
 | 
			
		||||
                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, Group])
 | 
			
		||||
                init_db(file)
 | 
			
		||||
                LOG_INFO(f"Successfully loaded {filename}")
 | 
			
		||||
            
 | 
			
		||||
            # Save the selected database path to persistent storage
 | 
			
		||||
@@ -150,6 +154,11 @@ def select_file(app_state: AppState):
 | 
			
		||||
@immapp.static(inited=False)
 | 
			
		||||
def table(app_state: AppState) -> None:
 | 
			
		||||
    statics = table
 | 
			
		||||
    
 | 
			
		||||
    if db.is_closed():
 | 
			
		||||
        imgui.text("DB")
 | 
			
		||||
        return
 | 
			
		||||
 | 
			
		||||
    if not statics.inited:
 | 
			
		||||
        statics.table_flags = (
 | 
			
		||||
            imgui.TableFlags_.row_bg.value
 | 
			
		||||
@@ -215,7 +224,7 @@ def table(app_state: AppState) -> None:
 | 
			
		||||
        imgui.end_table()
 | 
			
		||||
 | 
			
		||||
@immapp.static(inited=False)
 | 
			
		||||
def class_editor() -> None:
 | 
			
		||||
def editor() -> None:
 | 
			
		||||
    """
 | 
			
		||||
    Class Editor UI Component.
 | 
			
		||||
    
 | 
			
		||||
@@ -223,6 +232,9 @@ def class_editor() -> None:
 | 
			
		||||
    It maintains a static state to keep track of the selected class and fetches available classes.
 | 
			
		||||
    """
 | 
			
		||||
    statics = class_editor
 | 
			
		||||
    if db.is_closed():
 | 
			
		||||
        imgui.text("DB")
 | 
			
		||||
        return
 | 
			
		||||
    statics.classes = Class.select()
 | 
			
		||||
    if not statics.inited:
 | 
			
		||||
        statics.selected = 0
 | 
			
		||||
@@ -260,86 +272,91 @@ def class_editor() -> None:
 | 
			
		||||
        LOG_INFO(f"Deleted: {clas.name}")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def database_editor(app_state: AppState) -> None:
 | 
			
		||||
    """
 | 
			
		||||
    Database Editor UI Function.
 | 
			
		||||
def create_editor_popup(table, action: str, selectors: dict) -> None:
 | 
			
		||||
    table_flags = (
 | 
			
		||||
        imgui.TableFlags_.row_bg.value
 | 
			
		||||
    )
 | 
			
		||||
    cols = 2 
 | 
			
		||||
    rows = len(table._meta.fields)
 | 
			
		||||
 | 
			
		||||
    Calls the class editor function to render its UI component.
 | 
			
		||||
    if imgui.begin_table("Editor Grid", cols, table_flags):
 | 
			
		||||
        # Setup Header 
 | 
			
		||||
        for header in ["Attribute", "Value"]:
 | 
			
		||||
            imgui.table_setup_column(header, imgui.TableColumnFlags_.width_stretch.value, 1/len(header))
 | 
			
		||||
        imgui.table_headers_row()
 | 
			
		||||
    
 | 
			
		||||
    :param app_state: The application state containing relevant database information.
 | 
			
		||||
    """
 | 
			
		||||
    class_editor()
 | 
			
		||||
        id = 0
 | 
			
		||||
        for k, v in table._meta.fields.items():
 | 
			
		||||
            # Don't show Fields
 | 
			
		||||
            match type(v):
 | 
			
		||||
                case peewee.AutoField:
 | 
			
		||||
                    continue 
 | 
			
		||||
                case peewee.DateTimeField:
 | 
			
		||||
                    continue
 | 
			
		||||
 | 
			
		||||
def database_docking_splits() -> List[hello_imgui.DockingSplit]:
 | 
			
		||||
    """
 | 
			
		||||
    Defines the docking layout for the database editor.
 | 
			
		||||
            imgui.table_next_row()
 | 
			
		||||
            imgui.table_set_column_index(0)
 | 
			
		||||
            label = str(k).removesuffix('_id')
 | 
			
		||||
            imgui.text(label.title())
 | 
			
		||||
            imgui.table_set_column_index(1)
 | 
			
		||||
            
 | 
			
		||||
    Returns a list of docking splits that define the structure of the editor layout.
 | 
			
		||||
            # Generate Input for Type
 | 
			
		||||
            match type(v):
 | 
			
		||||
                case peewee.IntegerField:
 | 
			
		||||
                    if id not in selectors:
 | 
			
		||||
                        selectors[id] = int()
 | 
			
		||||
                    _, selectors[id] = imgui.input_int(f"##{id}", selectors[id], 1)
 | 
			
		||||
                case peewee.CharField:
 | 
			
		||||
                    if id not in selectors:
 | 
			
		||||
                        if k == 'grader':
 | 
			
		||||
                            selectors[id] = int()
 | 
			
		||||
                        else:
 | 
			
		||||
                            selectors[id] = str()
 | 
			
		||||
                    
 | 
			
		||||
    :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
 | 
			
		||||
                    if k == 'grader':
 | 
			
		||||
                        graders = [g.alt_name for g in get_gradings()]
 | 
			
		||||
                        _, selectors[id] = imgui.combo(f"##{id}", selectors[id], graders)
 | 
			
		||||
                    else:
 | 
			
		||||
                        _, selectors[id] = imgui.input_text(f"##{id}", selectors[id])
 | 
			
		||||
                case peewee.FloatField:
 | 
			
		||||
                    if id not in selectors:
 | 
			
		||||
                        selectors[id] = float()
 | 
			
		||||
                    _, selectors[id] = imgui.input_float(f"##{id}", selectors[id])
 | 
			
		||||
                case peewee.ForeignKeyField:
 | 
			
		||||
                    if id not in selectors:
 | 
			
		||||
                        selectors[id] = int()
 | 
			
		||||
                    
 | 
			
		||||
    split_main_command2 = hello_imgui.DockingSplit()
 | 
			
		||||
    split_main_command2.initial_dock = "CommandSpace"
 | 
			
		||||
    split_main_command2.new_dock = "CommandSpace2"
 | 
			
		||||
    split_main_command2.direction = imgui.Dir.right
 | 
			
		||||
    split_main_command2.ratio = 0.3
 | 
			
		||||
                    labels = list()
 | 
			
		||||
                    match k:
 | 
			
		||||
                        case 'class_id':
 | 
			
		||||
                            labels = [clas.name for clas in Class.select()]
 | 
			
		||||
                        case 'lecture_id':
 | 
			
		||||
                            labels = [lecture.title for lecture in Lecture.select()]
 | 
			
		||||
                    
 | 
			
		||||
    split_main_misc = hello_imgui.DockingSplit()
 | 
			
		||||
    split_main_misc.initial_dock = "MainDockSpace"
 | 
			
		||||
    split_main_misc.new_dock = "MiscSpace"
 | 
			
		||||
    split_main_misc.direction = imgui.Dir.left
 | 
			
		||||
    split_main_misc.ratio = 0.2
 | 
			
		||||
                    if not labels:
 | 
			
		||||
                        imgui.text("No Element for this Attribute")
 | 
			
		||||
                    else:
 | 
			
		||||
                        _, selectors[id] = imgui.combo(f"##{id}", selectors[id], labels)
 | 
			
		||||
                case _:
 | 
			
		||||
                    imgui.text(f"Unknown Field {k}")
 | 
			
		||||
            
 | 
			
		||||
    return [split_main_misc, split_main_command, split_main_command2]
 | 
			
		||||
            id += 1
 | 
			
		||||
        imgui.end_table()
 | 
			
		||||
 | 
			
		||||
def set_database_editor_layout(app_state: AppState) -> List[hello_imgui.DockableWindow]:
 | 
			
		||||
    """
 | 
			
		||||
    Defines the dockable windows for the database editor.
 | 
			
		||||
    if imgui.button(action):
 | 
			
		||||
        match action:
 | 
			
		||||
            case "Create":
 | 
			
		||||
                print("Create")
 | 
			
		||||
            case "Update":
 | 
			
		||||
                print("Update")
 | 
			
		||||
            case "Delete":
 | 
			
		||||
                print("Delete")
 | 
			
		||||
            case _:
 | 
			
		||||
                print("Unknown Case")
 | 
			
		||||
 | 
			
		||||
    Creates and returns a list of dockable windows, including the database file selector, log window,
 | 
			
		||||
    table viewer, and editor.
 | 
			
		||||
        # Clear & Close Popup
 | 
			
		||||
        selectors.clear()
 | 
			
		||||
        imgui.close_current_popup()
 | 
			
		||||
 | 
			
		||||
    :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"
 | 
			
		||||
    file_dialog.gui_function = lambda: select_file(app_state)
 | 
			
		||||
 | 
			
		||||
    log = hello_imgui.DockableWindow()
 | 
			
		||||
    log.label = "Logs"
 | 
			
		||||
    log.dock_space_name = "CommandSpace2"
 | 
			
		||||
    log.gui_function = hello_imgui.log_gui
 | 
			
		||||
 | 
			
		||||
    table_view = hello_imgui.DockableWindow()
 | 
			
		||||
    table_view.label = "Table"
 | 
			
		||||
    table_view.dock_space_name = "MainDockSpace"
 | 
			
		||||
    table_view.gui_function = lambda: table(app_state)
 | 
			
		||||
 | 
			
		||||
    editor = hello_imgui.DockableWindow()
 | 
			
		||||
    editor.label = "Editor"
 | 
			
		||||
    editor.dock_space_name = "CommandSpace"
 | 
			
		||||
    editor.gui_function = lambda: database_editor(app_state)
 | 
			
		||||
    
 | 
			
		||||
    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
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										142
									
								
								grapher/gui/database/editor.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										142
									
								
								grapher/gui/database/editor.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,142 @@
 | 
			
		||||
from imgui_bundle import (
 | 
			
		||||
    imgui,
 | 
			
		||||
    immapp,
 | 
			
		||||
    imgui_md,
 | 
			
		||||
    hello_imgui
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
from grader import get_gradings, get_grader
 | 
			
		||||
 | 
			
		||||
from dbmodel import *
 | 
			
		||||
 | 
			
		||||
@immapp.static(inited=False)
 | 
			
		||||
def editor(app_state) -> 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.
 | 
			
		||||
    """
 | 
			
		||||
    if db.is_closed():
 | 
			
		||||
        imgui.text("Please open a Database")
 | 
			
		||||
        return 
 | 
			
		||||
 | 
			
		||||
    statics = editor
 | 
			
		||||
    if not statics.inited:
 | 
			
		||||
        statics.selected = 0
 | 
			
		||||
        statics.actions = ["Create", "Update", "Delete"]
 | 
			
		||||
        statics.action = str()
 | 
			
		||||
        statics.inited = True
 | 
			
		||||
        statics.selectors = dict()
 | 
			
		||||
 | 
			
		||||
    imgui.text("Select what you want to Edit:")
 | 
			
		||||
    changed, statics.selected = imgui.combo('##DBSelector', statics.selected, table_labels)
 | 
			
		||||
    for action in statics.actions:
 | 
			
		||||
        if imgui.button(action):
 | 
			
		||||
            imgui.open_popup(table_labels[statics.selected])
 | 
			
		||||
            statics.action = action
 | 
			
		||||
        imgui.same_line()
 | 
			
		||||
    
 | 
			
		||||
    if imgui.begin_popup_modal(table_labels[statics.selected])[0]:
 | 
			
		||||
        table = tables[statics.selected]
 | 
			
		||||
        imgui_md.render(f"# {statics.action} {table_labels[statics.selected]}")
 | 
			
		||||
        
 | 
			
		||||
        if imgui.begin_table("Editor Grid", 2, imgui.TableFlags_.row_bg.value):
 | 
			
		||||
            # Setup Header
 | 
			
		||||
            for header in ["Attribute", "Value"]:
 | 
			
		||||
                imgui.table_setup_column(header, imgui.TableColumnFlags_.width_stretch.value, 1/len(header))
 | 
			
		||||
            imgui.table_headers_row()
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
            student_editor(statics.action)
 | 
			
		||||
 | 
			
		||||
            imgui.end_table()
 | 
			
		||||
        
 | 
			
		||||
        
 | 
			
		||||
        imgui.end_popup()
 | 
			
		||||
 | 
			
		||||
@immapp.static(inited=False)
 | 
			
		||||
def student_editor(action: str) -> dict:
 | 
			
		||||
    '''
 | 
			
		||||
 | 
			
		||||
    '''
 | 
			
		||||
    statics = student_editor
 | 
			
		||||
    if not statics.inited:
 | 
			
		||||
        statics.classes = tuple(Class.select())
 | 
			
		||||
        statics.residences = tuple(Residence.select())
 | 
			
		||||
        statics.classes_labels = tuple(clas.name for clas in statics.classes)
 | 
			
		||||
        statics.graders = tuple(grader.alt_name for grader in get_gradings())
 | 
			
		||||
        statics.buffer = {
 | 
			
		||||
            'prename': "",
 | 
			
		||||
            'surname': "",
 | 
			
		||||
            'sex': 0,
 | 
			
		||||
            'class_id': 0,
 | 
			
		||||
            'group_id': 0,
 | 
			
		||||
            'grader': 0,
 | 
			
		||||
            'residence': 0,
 | 
			
		||||
 | 
			
		||||
        }
 | 
			
		||||
        statics.inited = True
 | 
			
		||||
    
 | 
			
		||||
    imgui.table_next_row()
 | 
			
		||||
    imgui.table_set_column_index(0)
 | 
			
		||||
    imgui.text("First Name")
 | 
			
		||||
    imgui.table_set_column_index(1)
 | 
			
		||||
    _, statics.buffer['prename'] = imgui.input_text("##prename", statics.buffer['prename'])
 | 
			
		||||
 | 
			
		||||
    imgui.table_next_row()
 | 
			
		||||
    imgui.table_set_column_index(0)
 | 
			
		||||
    imgui.text("Last Name")
 | 
			
		||||
    imgui.table_set_column_index(1)
 | 
			
		||||
    _, statics.buffer['surname'] = imgui.input_text("##surname", statics.buffer['surname'])
 | 
			
		||||
   
 | 
			
		||||
    imgui.table_next_row()
 | 
			
		||||
    imgui.table_set_column_index(0)
 | 
			
		||||
    imgui.text("Gender")
 | 
			
		||||
    imgui.table_set_column_index(1)
 | 
			
		||||
    _, statics.buffer['sex'] = imgui.combo("##sex", statics.buffer['sex'], ['Male', 'Female'])
 | 
			
		||||
 | 
			
		||||
    imgui.table_next_row()
 | 
			
		||||
    imgui.table_set_column_index(0)
 | 
			
		||||
    imgui.text("Class")
 | 
			
		||||
    imgui.table_set_column_index(1)
 | 
			
		||||
    _, statics.buffer['class_id'] = imgui.combo("##class_id", statics.buffer['class_id'], statics.classes_labels)
 | 
			
		||||
    
 | 
			
		||||
    imgui.table_next_row()
 | 
			
		||||
    imgui.table_set_column_index(0)
 | 
			
		||||
    imgui.text("Project Group")
 | 
			
		||||
    imgui.table_set_column_index(1)
 | 
			
		||||
    groups = tuple(Group.select().where(Group.class_id == statics.classes[statics.buffer['class_id']].id))
 | 
			
		||||
    _, statics.buffer['group_id'] = imgui.combo("##group_id", statics.buffer['group_id'], tuple(group.name for group in groups))
 | 
			
		||||
    
 | 
			
		||||
    imgui.table_next_row()
 | 
			
		||||
    imgui.table_set_column_index(0)
 | 
			
		||||
    imgui.text("Grader")
 | 
			
		||||
    imgui.table_set_column_index(1)
 | 
			
		||||
    _, statics.buffer['grader'] = imgui.combo("##grader", statics.buffer['grader'], statics.graders)
 | 
			
		||||
 | 
			
		||||
    imgui.table_next_row()
 | 
			
		||||
    imgui.table_set_column_index(0)
 | 
			
		||||
    imgui.text("Residence")
 | 
			
		||||
    imgui.table_set_column_index(1)
 | 
			
		||||
    _, statics.buffer['residence'] = imgui.combo("##residence", statics.buffer['residence'], [str(residence.id) for residence in statics.residences])
 | 
			
		||||
 | 
			
		||||
    if imgui.button(action):
 | 
			
		||||
        match action:
 | 
			
		||||
            case "Create":
 | 
			
		||||
                Student.create(
 | 
			
		||||
                    prename = statics.buffer['prename'],
 | 
			
		||||
                    surname = statics.buffer['surname'],
 | 
			
		||||
                    sex = 'Female' if statics.buffer['sex'] else 'Male',
 | 
			
		||||
                    class_id = statics.classes[statics.buffer['class_id']].id,
 | 
			
		||||
                    group_id = groups[statics.buffer['group_id']].id,
 | 
			
		||||
                    residence = statics.residences[statics.buffer['residence']] 
 | 
			
		||||
                )
 | 
			
		||||
            case "Update":
 | 
			
		||||
                pass
 | 
			
		||||
            case "Delete":
 | 
			
		||||
                pass
 | 
			
		||||
        statics.inited = False
 | 
			
		||||
        imgui.close_current_popup()
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										30
									
								
								grapher/gui/gui.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								grapher/gui/gui.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,30 @@
 | 
			
		||||
"""
 | 
			
		||||
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 dbmodel import *  # Importing database models like Class, Student, Lecture, and Submission
 | 
			
		||||
from .appstate import AppState 
 | 
			
		||||
 | 
			
		||||
# External
 | 
			
		||||
from imgui_bundle import imgui, hello_imgui  # ImGui-based UI framework
 | 
			
		||||
 | 
			
		||||
def menu_bar(runner_params: hello_imgui.RunnerParams) -> None:
 | 
			
		||||
    """Defines the application's menu bar."""
 | 
			
		||||
    hello_imgui.show_app_menu(runner_params)
 | 
			
		||||
    hello_imgui.show_view_menu(runner_params)
 | 
			
		||||
    
 | 
			
		||||
def status_bar(app_state: AppState) -> None:
 | 
			
		||||
    """Displays the status bar information."""
 | 
			
		||||
    imgui.text("Student Analyzer by @DerGrumpf")
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										24
									
								
								grapher/gui/logger.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								grapher/gui/logger.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,24 @@
 | 
			
		||||
'''
 | 
			
		||||
WIP
 | 
			
		||||
'''
 | 
			
		||||
 | 
			
		||||
from datetime import datetime 
 | 
			
		||||
from imgui_bundle import hello_imgui
 | 
			
		||||
import logging
 | 
			
		||||
 | 
			
		||||
FORMAT = '[%(asctime)s] '
 | 
			
		||||
 | 
			
		||||
def log(log_level: hello_imgui.LogLevel, msg: str) -> None:
 | 
			
		||||
    time = datetime.now().strftime("%X")
 | 
			
		||||
    hello_imgui.log(log_level, f"[{time}] {msg}")
 | 
			
		||||
 | 
			
		||||
LOG_DEBUG = lambda msg: log(hello_imgui.LogLevel.debug, msg)
 | 
			
		||||
LOG_INFO = lambda msg: log(hello_imgui.LogLevel.info, msg)
 | 
			
		||||
LOG_WARNING = lambda msg: log(hello_imgui.LogLevel.warning, msg)
 | 
			
		||||
LOG_ERROR = lambda msg: log(hello_imgui.LogLevel.error, msg)
 | 
			
		||||
 | 
			
		||||
logging.basicConfig()
 | 
			
		||||
logger = logging.getLogger('AppState')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
logger.info("Hello")
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								grapher/gui/state
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								grapher/gui/state
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							@@ -1,54 +1,21 @@
 | 
			
		||||
"""
 | 
			
		||||
Student Analyzer Application
 | 
			
		||||
import shelve
 | 
			
		||||
 | 
			
		||||
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.
 | 
			
		||||
from imgui_bundle import (
 | 
			
		||||
    hello_imgui,
 | 
			
		||||
    immapp
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
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.
 | 
			
		||||
"""
 | 
			
		||||
from gui.logger import LOG_ERROR
 | 
			
		||||
 | 
			
		||||
# Custom
 | 
			
		||||
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  # 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  # ImGui-based UI framework
 | 
			
		||||
 | 
			
		||||
# 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  # 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}")
 | 
			
		||||
from gui import (
 | 
			
		||||
    AppState,
 | 
			
		||||
    analyzer_layout,
 | 
			
		||||
    database_editor_layout,
 | 
			
		||||
    menu_bar,
 | 
			
		||||
    status_bar
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
from dbmodel import init_db
 | 
			
		||||
 | 
			
		||||
def main() -> None:
 | 
			
		||||
    """Main function to initialize and run the application."""
 | 
			
		||||
@@ -58,13 +25,12 @@ def main() -> None:
 | 
			
		||||
    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
 | 
			
		||||
            print(v)
 | 
			
		||||
            init_db(v)
 | 
			
		||||
            app_state.update()
 | 
			
		||||
    except Exception as e:
 | 
			
		||||
        LOG_ERROR(f"Database Initialization {e}")
 | 
			
		||||
        print(e)
 | 
			
		||||
 | 
			
		||||
    # Set Window Parameters
 | 
			
		||||
    runner_params = hello_imgui.RunnerParams()
 | 
			
		||||
@@ -110,3 +76,4 @@ def main() -> None:
 | 
			
		||||
 | 
			
		||||
if __name__ == "__main__":
 | 
			
		||||
    main()
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								grapher/state
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								grapher/state
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										21
									
								
								main.py
									
									
									
									
									
								
							
							
						
						
									
										21
									
								
								main.py
									
									
									
									
									
								
							@@ -1,21 +0,0 @@
 | 
			
		||||
import imgui
 | 
			
		||||
import numpy
 | 
			
		||||
 | 
			
		||||
phil = Student(
 | 
			
		||||
            "Phil Keier", "772fb04b24caa68fd38a05ec2a22e62b", "Geomapping",
 | 
			
		||||
            [Lecture("1. Tutorial 1", 28.5, 31), Lecture("2. Tutorial 2", 4.5, 15), Lecture("3. Extended Application", 18, 18)]
 | 
			
		||||
        )
 | 
			
		||||
nova = Student(
 | 
			
		||||
            "Nova Eib", "772fb04b24caa68fd38a05ec2a22e62b", "Mapping Maps",
 | 
			
		||||
            [Lecture("1. Tutorial 1", 28.5, 31), Lecture("2. Tutorial 2", 4.5, 15), Lecture("3. Extended Application", 18, 18)]
 | 
			
		||||
        )
 | 
			
		||||
kathi = Student(
 | 
			
		||||
            "Katharina Walz", "772fb04b24caa68fd38a05ec2a22e62b", "Geomapping",
 | 
			
		||||
            [Lecture("1. Tutorial 1", 28.5, 31), Lecture("2. Tutorial 2", 4.5, 15), Lecture("3. Extended Application", 18, 18), Lecture("4. Numpy & MatPlotLib", 3, 30)]
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
students = [phil, nova, kathi]
 | 
			
		||||
 | 
			
		||||
if __name__ == "__main__":
 | 
			
		||||
    gui = GUI()
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										176
									
								
								model.py
									
									
									
									
									
								
							
							
						
						
									
										176
									
								
								model.py
									
									
									
									
									
								
							@@ -1,176 +0,0 @@
 | 
			
		||||
from peewee import *
 | 
			
		||||
from datetime import datetime
 | 
			
		||||
 | 
			
		||||
import json
 | 
			
		||||
from typing import TextIO
 | 
			
		||||
from pathlib import Path
 | 
			
		||||
 | 
			
		||||
db = SqliteDatabase(None, autoconnect=False)
 | 
			
		||||
 | 
			
		||||
class BaseModel(Model):
 | 
			
		||||
    class Meta:
 | 
			
		||||
        database = db
 | 
			
		||||
 | 
			
		||||
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')
 | 
			
		||||
    grader = CharField()
 | 
			
		||||
    created_at = DateTimeField(default=datetime.now)
 | 
			
		||||
 | 
			
		||||
class Lecture(BaseModel):
 | 
			
		||||
    title = CharField()
 | 
			
		||||
    points = IntegerField()
 | 
			
		||||
    class_id = ForeignKeyField(Class, backref='class')
 | 
			
		||||
    created_at = DateTimeField(default=datetime.now)
 | 
			
		||||
 | 
			
		||||
class Submission(BaseModel):
 | 
			
		||||
    student_id = ForeignKeyField(Student, backref='student')
 | 
			
		||||
    lecture_id = ForeignKeyField(Lecture, backref='lecture')
 | 
			
		||||
    points = FloatField()
 | 
			
		||||
    created_at = DateTimeField(default=datetime.now)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def load_from_json(fp: Path) -> None:
 | 
			
		||||
    '''
 | 
			
		||||
    Rebuilding Database from a given json
 | 
			
		||||
    '''
 | 
			
		||||
    with open(fp, "r") as file:
 | 
			
		||||
        data = json.load(file)
 | 
			
		||||
    
 | 
			
		||||
    for c_k, c_v in data.items():
 | 
			
		||||
        Class.create(
 | 
			
		||||
            name=c_k,
 | 
			
		||||
            id=c_v["DB ID"],
 | 
			
		||||
            created_at=c_v["Date"]
 | 
			
		||||
        )
 | 
			
		||||
        #print(f"KLASSE = {c.id} {c.name} ({c.created_at})")
 | 
			
		||||
 | 
			
		||||
        for student in c_v["Students"]:
 | 
			
		||||
            Student.create(
 | 
			
		||||
                id=student["DB ID"],
 | 
			
		||||
                created_at=student["Date"],
 | 
			
		||||
                prename=student["First Name"],
 | 
			
		||||
                surname=student["Last Name"],
 | 
			
		||||
                sex=student["Sex"],
 | 
			
		||||
                class_id=c_v["DB ID"]
 | 
			
		||||
            )
 | 
			
		||||
            #print(f"STUDENT = {s.id}. {s.prename} {s.surname} {s.sex} ({s.created_at}) Klasse: {s.class_id}")
 | 
			
		||||
            
 | 
			
		||||
            for submission in student["Submissions"]:
 | 
			
		||||
                Submission.create(
 | 
			
		||||
                    id=submission["DB ID"],
 | 
			
		||||
                    created_at=submission["Date"],
 | 
			
		||||
                    points=submission["Points"],
 | 
			
		||||
                    lecture_id=submission["Lecture ID"],
 | 
			
		||||
                    student_id=student["DB ID"]
 | 
			
		||||
                )
 | 
			
		||||
                #print(f"SUBMISSION = {sub.id}. {sub.points} Lecture: {sub.lecture_id} Student: {sub.student_id} ({sub.created_at})")
 | 
			
		||||
 | 
			
		||||
        for lecture in c_v["Lectures"]:
 | 
			
		||||
            Lecture.create(
 | 
			
		||||
                id=lecture["DB ID"],
 | 
			
		||||
                created_at=lecture["Date"],
 | 
			
		||||
                title=lecture["Title"],
 | 
			
		||||
                points=lecture["Points"],
 | 
			
		||||
                class_id=c_v["DB ID"]
 | 
			
		||||
            )
 | 
			
		||||
            #print(f"LECTURE = {l.id}. {l.title} {l.points} ({l.created_at}) Klasse: {l.class_id}")
 | 
			
		||||
 | 
			
		||||
def dump_to_json(fp: Path, indent=None) -> None:
 | 
			
		||||
    '''
 | 
			
		||||
    Dump existing Database to Json
 | 
			
		||||
    '''
 | 
			
		||||
    classes = Class.select()
 | 
			
		||||
    d = {c.name: {
 | 
			
		||||
            "DB ID": int(c.id),
 | 
			
		||||
            "Date": c.created_at.isoformat(),
 | 
			
		||||
            "Students": [
 | 
			
		||||
                    {
 | 
			
		||||
                    "DB ID": s.id,
 | 
			
		||||
                    "Date": s.created_at.isoformat(),
 | 
			
		||||
                    "First Name": s.prename,
 | 
			
		||||
                    "Last Name": s.surname,
 | 
			
		||||
                    "Sex": s.sex,
 | 
			
		||||
                    "Submissions": [
 | 
			
		||||
                        {
 | 
			
		||||
                        "DB ID": sub.id,
 | 
			
		||||
                        "Date": sub.created_at.isoformat(),
 | 
			
		||||
                        "Points": sub.points,
 | 
			
		||||
                        "Lecture ID": sub.lecture_id.id
 | 
			
		||||
                        }
 | 
			
		||||
                        for sub in Submission.select().where(Submission.student_id == s.id)
 | 
			
		||||
                        ]
 | 
			
		||||
                    } 
 | 
			
		||||
                    for s in Student.select().where(Student.class_id == c.id)
 | 
			
		||||
                ],
 | 
			
		||||
            "Lectures": [
 | 
			
		||||
                    {
 | 
			
		||||
                    "DB ID": l.id,
 | 
			
		||||
                    "Date": l.created_at.isoformat(),
 | 
			
		||||
                    "Title": l.title,
 | 
			
		||||
                    "Points": l.points
 | 
			
		||||
                    }
 | 
			
		||||
                    for l in Lecture.select().where(Lecture.class_id == c.id)
 | 
			
		||||
                ],
 | 
			
		||||
            } 
 | 
			
		||||
        for c in classes
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    with open(fp, "w") as file:
 | 
			
		||||
        json.dump(d, file, indent=indent)
 | 
			
		||||
 | 
			
		||||
def main():
 | 
			
		||||
    import random
 | 
			
		||||
    # Generate Test Data 
 | 
			
		||||
    class1 = Class.create(name="WiSe 22/23")
 | 
			
		||||
    class2 = Class.create(name="WiSe 23/24")
 | 
			
		||||
    class3 = Class.create(name="WiSe 24/25")
 | 
			
		||||
 | 
			
		||||
    phil = Student.create(prename="Phil", surname="Keier", sex="Male", class_id=class1.id)
 | 
			
		||||
    calvin = Student.create(prename="Calvin", surname="Brandt", sex="Male", class_id=class2.id)
 | 
			
		||||
    nova = Student.create(prename="Nova", surname="Eib", sex="Female", class_id=class1.id)
 | 
			
		||||
    kathi = Student.create(prename="Katharina", surname="Walz", sex="Female", class_id=class3.id)
 | 
			
		||||
    victoria = Student.create(prename="Victoria", surname="Möller", sex="Female", class_id=class3.id)
 | 
			
		||||
 | 
			
		||||
    lec1 = Lecture.create(title="Tutorial 1", points=30, class_id=class1.id)
 | 
			
		||||
    lec2 = Lecture.create(title="Tutorial 1", points=30, class_id=class3.id)
 | 
			
		||||
    lec3 = Lecture.create(title="Tutorial 2", points=20, class_id=class1.id)
 | 
			
		||||
    lec4 = Lecture.create(title="Tutorial 2", points=20, class_id=class2.id)
 | 
			
		||||
    lec5 = Lecture.create(title="Extended Applications", points=44, class_id=class1.id)
 | 
			
		||||
 | 
			
		||||
    sub1_phil = Submission.create(student_id=phil.id, lecture_id=lec1.id, points=random.randint(0, lec1.points))
 | 
			
		||||
    sub2_phil = Submission.create(student_id=phil.id, lecture_id=lec3.id, points=random.randint(0, lec3.points))
 | 
			
		||||
    sub3_phil = Submission.create(student_id=phil.id, lecture_id=lec5.id, points=random.randint(0, lec5.points))
 | 
			
		||||
    sub1_nova = Submission.create(student_id=nova.id, lecture_id=lec1.id, points=random.randint(0, lec1.points))
 | 
			
		||||
    sub2_nova = Submission.create(student_id=nova.id, lecture_id=lec3.id, points=random.randint(0, lec3.points))
 | 
			
		||||
    sub1_kathi = Submission.create(student_id=kathi.id, lecture_id=lec3.id, points=random.randint(0, lec3.points))
 | 
			
		||||
    sub1_vici = Submission.create(student_id=victoria.id, lecture_id=lec2.id, points=random.randint(0, lec2.points))
 | 
			
		||||
    
 | 
			
		||||
    return
 | 
			
		||||
    fp = Path().cwd()/"Test.json"
 | 
			
		||||
    dump_to_json(fp)
 | 
			
		||||
    db.close()
 | 
			
		||||
    db.init("Test.db")
 | 
			
		||||
    db.connect()
 | 
			
		||||
    db.create_tables([Class, Student, Lecture, Submission])
 | 
			
		||||
    load_from_json(fp)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
if __name__ == "__main__":
 | 
			
		||||
    #    main()
 | 
			
		||||
    db.init('wise_24_25.db')
 | 
			
		||||
    db.connect()
 | 
			
		||||
    dump_to_json(Path().cwd()/"TEST.json")
 | 
			
		||||
							
								
								
									
										44
									
								
								pyproject.toml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								pyproject.toml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,44 @@
 | 
			
		||||
[tool.poetry]
 | 
			
		||||
name = "grapher"
 | 
			
		||||
version = "0.1.0"
 | 
			
		||||
description = "A Quick & Dirty Student Analyzer written in Python & DearImGUI"
 | 
			
		||||
authors = ["DerGrumpf (Phil Keier) <p.keier@beyerstedt-it.de>"]
 | 
			
		||||
readme = "README.md"
 | 
			
		||||
license = "MIT"
 | 
			
		||||
package-mode = false
 | 
			
		||||
 | 
			
		||||
[project]
 | 
			
		||||
dependencies = [
 | 
			
		||||
  "annotated-types==0.7.0",
 | 
			
		||||
  "glfw==2.8.0",
 | 
			
		||||
  "imgui-bundle==1.6.2",
 | 
			
		||||
  "munch==4.0.0",
 | 
			
		||||
  "numpy==2.2.1",
 | 
			
		||||
  "opencv-python==4.10.0.84",
 | 
			
		||||
  "pandas==2.2.3",
 | 
			
		||||
  "peewee==3.17.8",
 | 
			
		||||
  "pillow==11.1.0",
 | 
			
		||||
  "py-spy==0.4.0",
 | 
			
		||||
  "pydantic==2.10.4",
 | 
			
		||||
  "pydantic_core==2.27.2",
 | 
			
		||||
  "PyGLM==2.7.3",
 | 
			
		||||
  "PyOpenGL==3.1.7",
 | 
			
		||||
  "python-dateutil==2.9.0.post0",
 | 
			
		||||
  "pytz==2024.2",
 | 
			
		||||
  "six==1.17.0",
 | 
			
		||||
  "snakeviz==2.2.2",
 | 
			
		||||
  "tornado==6.4.2",
 | 
			
		||||
  "typing_extensions==4.12.2",
 | 
			
		||||
  "tzdata==2024.2",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[project.urls]
 | 
			
		||||
repository = "https://git.cyperpunk.de/DerGrumpf/grapher"
 | 
			
		||||
 | 
			
		||||
[tool.poetry.dependencies]
 | 
			
		||||
python = "^3.12"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
[build-system]
 | 
			
		||||
requires = ["poetry-core"]
 | 
			
		||||
build-backend = "poetry.core.masonry.api"
 | 
			
		||||
		Reference in New Issue
	
	Block a user