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
 | 
					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
 | 
					Abdalaziz,Abunjaila,Male,DiKum,30%,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
 | 
					Marleen,Adolphi,Female,MeWi6,30%,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
 | 
					Sarina,Apel,Female,MeWi1,30%,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
 | 
					Skofiare,Berisha,Female,DiKum,30%,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
 | 
					Aurela,Brahimi,Female,MeWi2,30%,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
 | 
					Cam Thu,Do,Female,MeWi3,30%,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
 | 
					Nova,Eib,Female,MeWi4,30%,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
 | 
					Lena,Fricke,Female,MeWi4,30%,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
 | 
					Nele,Grundke,Female,MeWi6,30%,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
 | 
					Anna,Grünewald,Female,MeWi3,30%,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
 | 
					Yannik,Haupt,Male,NoGroup,30%,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
 | 
					Janna,Heiny,Female,MeWi1,30%,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
 | 
					Milena,Krieger,Female,MeWi1,30%,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
 | 
					Julia,Limbach,Female,MeWi6,30%,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
 | 
					Viktoria,Litza,Female,MeWi5,30%,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
 | 
					Leonie,Manthey,Female,MeWi1,30%,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
 | 
					Izabel,Mike,Female,MeWi2,30%,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
 | 
					Lea,Noglik,Female,MeWi5,30%,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
 | 
					Donika,Nuhiu,Female,MeWi5,30%,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
 | 
					Julia,Renner,Female,MeWi4,30%,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
 | 
					Fabian,Rothberger,Male,MeWi3,30%,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
 | 
					Natascha,Rott,Female,MeWi1,30%,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
 | 
					Isabel,Rudolf,Female,MeWi4,30%,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
 | 
					Melina,Sablotny,Female,MeWi6,30%,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
 | 
					Alea,Schleier,Female,DiKum,30%,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
 | 
					Flemming,Schur,Male,MeWi3,30%,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
 | 
					Marie,Seeger,Female,DiKum,30%,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
 | 
					Lucy,Thiele,Female,MeWi6,30%,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
 | 
					Lara,Troschke,Female,MeWi2,30%,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
 | 
					Inga-Brit,Turschner,Female,MeWi2,30%,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
 | 
					Alea,Unger,Female,MeWi5,30%,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
 | 
					Marie,Wallbaum,Female,MeWi5,30%,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
 | 
					Katharina,Walz,Female,MeWi4,30%,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
 | 
					Xiaowei,Wang,Male,NoGroup,30%,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
 | 
					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 pandas as pd
 | 
				
			||||||
import pprint
 | 
					import pprint
 | 
				
			||||||
import sys 
 | 
					import sys 
 | 
				
			||||||
sys.path.append('..')
 | 
					sys.path.append('../grapher/dbmodel')
 | 
				
			||||||
from model import *
 | 
					from model import *
 | 
				
			||||||
 | 
					from utils import *
 | 
				
			||||||
 | 
					
 | 
				
			||||||
df = pd.read_csv("Student_list.csv")
 | 
					df = pd.read_csv("Student_list.csv")
 | 
				
			||||||
df = df.dropna()
 | 
					df = df.dropna()
 | 
				
			||||||
@@ -31,10 +32,8 @@ groups = {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
print(df)
 | 
					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
 | 
					# Create Class
 | 
				
			||||||
clas = Class.create(name='WiSe 24/25')
 | 
					clas = Class.create(name='WiSe 24/25')
 | 
				
			||||||
@@ -55,7 +54,8 @@ for index, row in df.iterrows():
 | 
				
			|||||||
        sex=row["Sex"],
 | 
					        sex=row["Sex"],
 | 
				
			||||||
        class_id=clas.id,
 | 
					        class_id=clas.id,
 | 
				
			||||||
        group_id=Group.select().where(Group.name == row["Group"]),
 | 
					        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:]:
 | 
					    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({
 | 
					Std30PercentRule = StdPercentRule({
 | 
				
			||||||
    "pAssed": 0.3,
 | 
					    "pAssed": 0.3,
 | 
				
			||||||
    "Failed": 0.0
 | 
					    "Failed": 0.0
 | 
				
			||||||
}, "Std30PercentRule", "30 Percent")
 | 
					}, "Std30PercentRule", "30%")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Std50PercentRule = StdPercentRule({
 | 
					Std50PercentRule = StdPercentRule({
 | 
				
			||||||
    "Passed": 0.5,
 | 
					    "Passed": 0.5,
 | 
				
			||||||
    "Failed": 0.0
 | 
					    "Failed": 0.0
 | 
				
			||||||
}, "Std50PercentRule", "50 Percent")
 | 
					}, "Std50PercentRule", "50%")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
StdGermanGradingMiddleSchool = StdGermanGrading({
 | 
					StdGermanGradingMiddleSchool = StdGermanGrading({
 | 
				
			||||||
    1: 0.96,
 | 
					    1: 0.96,
 | 
				
			||||||
@@ -172,7 +172,7 @@ StdGermanGradingMiddleSchool = StdGermanGrading({
 | 
				
			|||||||
    4: 0.45,
 | 
					    4: 0.45,
 | 
				
			||||||
    5: 0.16,
 | 
					    5: 0.16,
 | 
				
			||||||
    6: 0.00
 | 
					    6: 0.00
 | 
				
			||||||
}, "StdGermanGradingMiddleSchool", "Secondary School")
 | 
					}, "StdGermanGradingMiddleSchool", "Mittelstufe")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
StdGermanGradingHighSchool = StdGermanGrading({
 | 
					StdGermanGradingHighSchool = StdGermanGrading({
 | 
				
			||||||
    15: 0.95,
 | 
					    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 
 | 
					# Custom 
 | 
				
			||||||
from model import *
 | 
					from dbmodel import *
 | 
				
			||||||
from appstate import AppState
 | 
					from gui import AppState
 | 
				
			||||||
from grader.valuation import *
 | 
					from grader.valuation import *
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# External
 | 
					# External
 | 
				
			||||||
@@ -140,8 +140,8 @@ def student_graph(app_state: AppState) -> None:
 | 
				
			|||||||
        statics.points = np.sum(statics.sub_points)
 | 
					        statics.points = np.sum(statics.sub_points)
 | 
				
			||||||
        if statics.points.is_integer():
 | 
					        if statics.points.is_integer():
 | 
				
			||||||
            statics.points = int(statics.points)
 | 
					            statics.points = int(statics.points)
 | 
				
			||||||
        statics.grader = get_grader("Oberstufe")
 | 
					        #statics.grader = get_grader("Oberstufe")
 | 
				
			||||||
        #statics.grader = get_grader(statics.student.grader)
 | 
					        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_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)]
 | 
					        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"):
 | 
					    if imgui.button("Change"):
 | 
				
			||||||
        statics.state = not statics.state
 | 
					        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 dbmodel import *
 | 
				
			||||||
from model import *
 | 
					 | 
				
			||||||
from datetime import datetime
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
class AppState:
 | 
					class AppState:
 | 
				
			||||||
    current_class_id: int 
 | 
					    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)
 | 
					        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
 | 
					        self.current_submission_id = submissions[0].id if submissions else None
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
        LOG_DEBUG(f"Updated App State {repr(self)}")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def __repr__(self):
 | 
					    def __repr__(self):
 | 
				
			||||||
        return f'''
 | 
					        return f'''
 | 
				
			||||||
        Class ID: {self.current_class_id}
 | 
					        Class ID: {self.current_class_id}
 | 
				
			||||||
@@ -38,12 +34,5 @@ class AppState:
 | 
				
			|||||||
        Submission ID: {self.current_submission_id}
 | 
					        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
 | 
					# Custom
 | 
				
			||||||
from model import *
 | 
					from dbmodel import *
 | 
				
			||||||
from appstate import * 
 | 
					from gui import * 
 | 
				
			||||||
 | 
					from grader import get_gradings
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# External
 | 
					# External
 | 
				
			||||||
from imgui_bundle import (
 | 
					from imgui_bundle import (
 | 
				
			||||||
@@ -20,11 +21,18 @@ from imgui_bundle import (
 | 
				
			|||||||
    hello_imgui
 | 
					    hello_imgui
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import peewee
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Built In 
 | 
					# Built In 
 | 
				
			||||||
from typing import List
 | 
					from typing import List
 | 
				
			||||||
import shelve
 | 
					import shelve
 | 
				
			||||||
from pathlib import Path
 | 
					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:
 | 
					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
 | 
					        # Retrieve the last used database file from persistent storage
 | 
				
			||||||
        with shelve.open("state") as state:
 | 
					        with shelve.open("state") as state:
 | 
				
			||||||
            statics.current = Path(state["DB"])
 | 
					            statics.current = Path(state.get("DB") or "")
 | 
				
			||||||
        statics.inited = True
 | 
					        statics.inited = True
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    # Render UI title and display file information
 | 
					    # 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
 | 
					            # Handle JSON files by converting them to SQLite databases
 | 
				
			||||||
            if statics.res.extension() == '.json':
 | 
					            if statics.res.extension() == '.json':
 | 
				
			||||||
                file = filename.removesuffix('.json') + '.db'  # Convert JSON filename to SQLite filename
 | 
					                file = filename.removesuffix('.json') + '.db'  # Convert JSON filename to SQLite filename
 | 
				
			||||||
                db.init(file)
 | 
					                init_db(file) 
 | 
				
			||||||
                db.connect(reuse_if_open=True)
 | 
					 | 
				
			||||||
                db.create_tables([Class, Student, Lecture, Submission, Group])
 | 
					 | 
				
			||||||
                load_from_json(str(info))  # Convert and load JSON data into the database
 | 
					                load_from_json(str(info))  # Convert and load JSON data into the database
 | 
				
			||||||
                LOG_INFO(f"Successfully created {file}")
 | 
					                LOG_INFO(f"Successfully created {file}")
 | 
				
			||||||
            
 | 
					            
 | 
				
			||||||
            # Handle SQLite database files directly
 | 
					            # Handle SQLite database files directly
 | 
				
			||||||
            if statics.res.extension() == '.db':
 | 
					            if statics.res.extension() == '.db':
 | 
				
			||||||
                file = str(statics.res.path())
 | 
					                file = str(statics.res.path())
 | 
				
			||||||
                db.init(file)
 | 
					                init_db(file)
 | 
				
			||||||
                db.connect(reuse_if_open=True)
 | 
					 | 
				
			||||||
                db.create_tables([Class, Student, Lecture, Submission, Group])
 | 
					 | 
				
			||||||
                LOG_INFO(f"Successfully loaded {filename}")
 | 
					                LOG_INFO(f"Successfully loaded {filename}")
 | 
				
			||||||
            
 | 
					            
 | 
				
			||||||
            # Save the selected database path to persistent storage
 | 
					            # Save the selected database path to persistent storage
 | 
				
			||||||
@@ -150,6 +154,11 @@ def select_file(app_state: AppState):
 | 
				
			|||||||
@immapp.static(inited=False)
 | 
					@immapp.static(inited=False)
 | 
				
			||||||
def table(app_state: AppState) -> None:
 | 
					def table(app_state: AppState) -> None:
 | 
				
			||||||
    statics = table
 | 
					    statics = table
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    if db.is_closed():
 | 
				
			||||||
 | 
					        imgui.text("DB")
 | 
				
			||||||
 | 
					        return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if not statics.inited:
 | 
					    if not statics.inited:
 | 
				
			||||||
        statics.table_flags = (
 | 
					        statics.table_flags = (
 | 
				
			||||||
            imgui.TableFlags_.row_bg.value
 | 
					            imgui.TableFlags_.row_bg.value
 | 
				
			||||||
@@ -215,7 +224,7 @@ def table(app_state: AppState) -> None:
 | 
				
			|||||||
        imgui.end_table()
 | 
					        imgui.end_table()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@immapp.static(inited=False)
 | 
					@immapp.static(inited=False)
 | 
				
			||||||
def class_editor() -> None:
 | 
					def editor() -> None:
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
    Class Editor UI Component.
 | 
					    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.
 | 
					    It maintains a static state to keep track of the selected class and fetches available classes.
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
    statics = class_editor
 | 
					    statics = class_editor
 | 
				
			||||||
 | 
					    if db.is_closed():
 | 
				
			||||||
 | 
					        imgui.text("DB")
 | 
				
			||||||
 | 
					        return
 | 
				
			||||||
    statics.classes = Class.select()
 | 
					    statics.classes = Class.select()
 | 
				
			||||||
    if not statics.inited:
 | 
					    if not statics.inited:
 | 
				
			||||||
        statics.selected = 0
 | 
					        statics.selected = 0
 | 
				
			||||||
@@ -260,86 +272,91 @@ def class_editor() -> None:
 | 
				
			|||||||
        LOG_INFO(f"Deleted: {clas.name}")
 | 
					        LOG_INFO(f"Deleted: {clas.name}")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def database_editor(app_state: AppState) -> None:
 | 
					def create_editor_popup(table, action: str, selectors: dict) -> None:
 | 
				
			||||||
    """
 | 
					    table_flags = (
 | 
				
			||||||
    Database Editor UI Function.
 | 
					        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.
 | 
					        id = 0
 | 
				
			||||||
    """
 | 
					        for k, v in table._meta.fields.items():
 | 
				
			||||||
    class_editor()
 | 
					            # Don't show Fields
 | 
				
			||||||
 | 
					            match type(v):
 | 
				
			||||||
 | 
					                case peewee.AutoField:
 | 
				
			||||||
 | 
					                    continue 
 | 
				
			||||||
 | 
					                case peewee.DateTimeField:
 | 
				
			||||||
 | 
					                    continue
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def database_docking_splits() -> List[hello_imgui.DockingSplit]:
 | 
					            imgui.table_next_row()
 | 
				
			||||||
    """
 | 
					            imgui.table_set_column_index(0)
 | 
				
			||||||
    Defines the docking layout for the database editor.
 | 
					            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.
 | 
					                    if k == 'grader':
 | 
				
			||||||
    """
 | 
					                        graders = [g.alt_name for g in get_gradings()]
 | 
				
			||||||
    split_main_command = hello_imgui.DockingSplit()
 | 
					                        _, selectors[id] = imgui.combo(f"##{id}", selectors[id], graders)
 | 
				
			||||||
    split_main_command.initial_dock = "MainDockSpace"
 | 
					                    else:
 | 
				
			||||||
    split_main_command.new_dock = "CommandSpace"
 | 
					                        _, selectors[id] = imgui.input_text(f"##{id}", selectors[id])
 | 
				
			||||||
    split_main_command.direction = imgui.Dir.down
 | 
					                case peewee.FloatField:
 | 
				
			||||||
    split_main_command.ratio = 0.3
 | 
					                    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()
 | 
					                    labels = list()
 | 
				
			||||||
    split_main_command2.initial_dock = "CommandSpace"
 | 
					                    match k:
 | 
				
			||||||
    split_main_command2.new_dock = "CommandSpace2"
 | 
					                        case 'class_id':
 | 
				
			||||||
    split_main_command2.direction = imgui.Dir.right
 | 
					                            labels = [clas.name for clas in Class.select()]
 | 
				
			||||||
    split_main_command2.ratio = 0.3
 | 
					                        case 'lecture_id':
 | 
				
			||||||
 | 
					                            labels = [lecture.title for lecture in Lecture.select()]
 | 
				
			||||||
                    
 | 
					                    
 | 
				
			||||||
    split_main_misc = hello_imgui.DockingSplit()
 | 
					                    if not labels:
 | 
				
			||||||
    split_main_misc.initial_dock = "MainDockSpace"
 | 
					                        imgui.text("No Element for this Attribute")
 | 
				
			||||||
    split_main_misc.new_dock = "MiscSpace"
 | 
					                    else:
 | 
				
			||||||
    split_main_misc.direction = imgui.Dir.left
 | 
					                        _, selectors[id] = imgui.combo(f"##{id}", selectors[id], labels)
 | 
				
			||||||
    split_main_misc.ratio = 0.2
 | 
					                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]:
 | 
					    if imgui.button(action):
 | 
				
			||||||
    """
 | 
					        match action:
 | 
				
			||||||
    Defines the dockable windows for the database editor.
 | 
					            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,
 | 
					        # Clear & Close Popup
 | 
				
			||||||
    table viewer, and editor.
 | 
					        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 @@
 | 
				
			|||||||
"""
 | 
					import shelve
 | 
				
			||||||
Student Analyzer Application
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
This script initializes and runs the Student Analyzer application, which provides an interface for
 | 
					from imgui_bundle import (
 | 
				
			||||||
managing student data, class records, and submissions. It uses the Hello ImGui framework for UI rendering
 | 
					    hello_imgui,
 | 
				
			||||||
and integrates a database to store and manipulate student information.
 | 
					    immapp
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Modules:
 | 
					from gui.logger import LOG_ERROR
 | 
				
			||||||
    - 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 gui import (
 | 
				
			||||||
from model import *  # Importing database models like Class, Student, Lecture, and Submission
 | 
					    AppState,
 | 
				
			||||||
from appstate import AppState, LOG_ERROR  # Application state management
 | 
					    analyzer_layout,
 | 
				
			||||||
 | 
					    database_editor_layout,
 | 
				
			||||||
# Layouts
 | 
					    menu_bar,
 | 
				
			||||||
from analyzer import analyzer_layout  # Main layout for the analyzer
 | 
					    status_bar
 | 
				
			||||||
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 dbmodel import init_db
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def main() -> None:
 | 
					def main() -> None:
 | 
				
			||||||
    """Main function to initialize and run the application."""
 | 
					    """Main function to initialize and run the application."""
 | 
				
			||||||
@@ -58,13 +25,12 @@ def main() -> None:
 | 
				
			|||||||
    try:
 | 
					    try:
 | 
				
			||||||
        with shelve.open("state") as state:
 | 
					        with shelve.open("state") as state:
 | 
				
			||||||
            v = state.get("DB")  # Retrieve stored database connection info
 | 
					            v = state.get("DB")  # Retrieve stored database connection info
 | 
				
			||||||
            if v:
 | 
					            print(v)
 | 
				
			||||||
                db.init(v)
 | 
					            init_db(v)
 | 
				
			||||||
                db.connect()
 | 
					            app_state.update()
 | 
				
			||||||
                db.create_tables([Class, Student, Lecture, Submission, Group])  # Ensure tables exist
 | 
					 | 
				
			||||||
                app_state.update()
 | 
					 | 
				
			||||||
    except Exception as e:
 | 
					    except Exception as e:
 | 
				
			||||||
        LOG_ERROR(f"Database Initialization {e}")
 | 
					        LOG_ERROR(f"Database Initialization {e}")
 | 
				
			||||||
 | 
					        print(e)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # Set Window Parameters
 | 
					    # Set Window Parameters
 | 
				
			||||||
    runner_params = hello_imgui.RunnerParams()
 | 
					    runner_params = hello_imgui.RunnerParams()
 | 
				
			||||||
@@ -110,3 +76,4 @@ def main() -> None:
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
if __name__ == "__main__":
 | 
					if __name__ == "__main__":
 | 
				
			||||||
    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