Added: Postgres Support
This commit is contained in:
parent
2d25d5105a
commit
4b82b11072
3
.gitignore
vendored
3
.gitignore
vendored
@ -1,5 +1,6 @@
|
|||||||
# ---> Custom
|
# ---> Custom
|
||||||
state
|
pickles/*
|
||||||
|
assets/documents/*
|
||||||
|
|
||||||
# ---> Python
|
# ---> Python
|
||||||
# Byte-compiled / optimized / DLL files
|
# Byte-compiled / optimized / DLL files
|
||||||
|
@ -9,7 +9,7 @@ Nova,Eib,Female,MeWi4,30%,Medienwissenschaften,31,15,15,34,20,20,21,27,19,21
|
|||||||
Lena,Fricke,Female,MeWi4,30%,Medienwissenschaften,13,14,15,21,15,17,17,18,19,11
|
Lena,Fricke,Female,MeWi4,30%,Medienwissenschaften,13,14,15,21,15,17,17,18,19,11
|
||||||
Nele,Grundke,Female,MeWi6,30%,Medienwissenschaften,23.5,13,16,28,20,17,21,18,22,11
|
Nele,Grundke,Female,MeWi6,30%,Medienwissenschaften,23.5,13,16,28,20,17,21,18,22,11
|
||||||
Anna,Grünewald,Female,MeWi3,30%,Medienwissenschaften,12,14,16,29,16,15,19,9,16,12
|
Anna,Grünewald,Female,MeWi3,30%,Medienwissenschaften,12,14,16,29,16,15,19,9,16,12
|
||||||
Yannik,Haupt,Male,NoGroup,30%,Unknown,18,6,14,21,13,2,9,0,0,0
|
Yannik,Haupt,Male,NoGroup,30%,Student,18,6,14,21,13,2,9,0,0,0
|
||||||
Janna,Heiny,Female,MeWi1,30%,Technologie Orientiertes Managment,30,15,18,33,18,20,22,25,24,30
|
Janna,Heiny,Female,MeWi1,30%,Technologie Orientiertes Managment,30,15,18,33,18,20,22,25,24,30
|
||||||
Milena,Krieger,Female,MeWi1,30%,Technologie Orientiertes Managment,30,15,18,33,20,20,21.5,26,20,22
|
Milena,Krieger,Female,MeWi1,30%,Technologie Orientiertes Managment,30,15,18,33,20,20,21.5,26,20,22
|
||||||
Julia,Limbach,Female,MeWi6,30%,Medienwissenschaften,27.5,12,18,29,11,19,17.5,26,24,28
|
Julia,Limbach,Female,MeWi6,30%,Medienwissenschaften,27.5,12,18,29,11,19,17.5,26,24,28
|
||||||
@ -32,7 +32,5 @@ Inga-Brit,Turschner,Female,MeWi2,30%,Medienwissenschaften,25.5,14,18,34,20,16,19
|
|||||||
Alea,Unger,Female,MeWi5,30%,Medienwissenschaften,30,12,18,31,20,20,21,22,15,21.5
|
Alea,Unger,Female,MeWi5,30%,Medienwissenschaften,30,12,18,31,20,20,21,22,15,21.5
|
||||||
Marie,Wallbaum,Female,MeWi5,30%,Medienwissenschaften,28.5,14,18,34,17,20,19,24,12,22
|
Marie,Wallbaum,Female,MeWi5,30%,Medienwissenschaften,28.5,14,18,34,17,20,19,24,12,22
|
||||||
Katharina,Walz,Female,MeWi4,30%,Medienwissenschaften,31,15,18,31,19,19,17,24,17,14.5
|
Katharina,Walz,Female,MeWi4,30%,Medienwissenschaften,31,15,18,31,19,19,17,24,17,14.5
|
||||||
Xiaowei,Wang,Male,NoGroup,30%,Unknown,30.5,14,18,26,19,17,0,0,0,0
|
Xiaowei,Wang,Male,NoGroup,30%,Student,30.5,14,18,26,19,17,0,0,0,0
|
||||||
Lilly-Lu,Warnken,Female,DiKum,30%,Medienwissenschaften,30,15,18,30,14,17,19,14,16,24
|
Lilly-Lu,Warnken,Female,DiKum,30%,Medienwissenschaften,30,15,18,30,14,17,19,14,16,24
|
||||||
,,,,,,,,,,,,,,,
|
|
||||||
|
|
||||||
|
|
Binary file not shown.
@ -31,12 +31,14 @@ groups = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
print(df)
|
print(df)
|
||||||
init_db('WiSe_24_25.db')
|
#init_db('WiSe_24_25.db')
|
||||||
|
init_postgres('postgresql://admin:admin@127.0.0.1/learnlytics')
|
||||||
|
db.drop_tables(tables)
|
||||||
|
db.create_tables(tables)
|
||||||
# Create Class
|
# Create Class
|
||||||
clas = Class.create(name='WiSe 24/25')
|
clas = Class.create(name='WiSe 24/25')
|
||||||
#print(clas.id)
|
|
||||||
|
Study.create(name='Student')
|
||||||
|
|
||||||
# Create Courses
|
# Create Courses
|
||||||
for k, v in courses.items():
|
for k, v in courses.items():
|
||||||
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -1,7 +1,8 @@
|
|||||||
from .utils import (
|
from .utils import (
|
||||||
tables,
|
tables,
|
||||||
table_labels,
|
table_labels,
|
||||||
init_db,
|
init_local,
|
||||||
|
init_postgres,
|
||||||
save_as_json,
|
save_as_json,
|
||||||
create_from_json
|
create_from_json
|
||||||
)
|
)
|
||||||
|
@ -9,17 +9,18 @@ Online Viewer: https://dbdiagram.io
|
|||||||
|
|
||||||
|
|
||||||
from peewee import *
|
from peewee import *
|
||||||
|
from playhouse.db_url import connect
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
db = DatabaseProxy()
|
db = DatabaseProxy()
|
||||||
|
|
||||||
# WIP: Add Switch Function
|
# WIP: Add Switch Function
|
||||||
if True:
|
#if False:
|
||||||
database = SqliteDatabase(None, autoconnect=False)
|
# database = SqliteDatabase(None, autoconnect=False)
|
||||||
else:
|
#else:
|
||||||
database = PostgresqlDatabase(None, autoconnect=False)
|
# database = connect('postgresql://admin:admin@127.0.0.1:5432/learnlytics')
|
||||||
|
|
||||||
db.initialize(database)
|
#db.initialize(database)
|
||||||
|
|
||||||
class BaseModel(Model):
|
class BaseModel(Model):
|
||||||
'''
|
'''
|
||||||
@ -94,4 +95,3 @@ class Submission(BaseModel):
|
|||||||
points = FloatField()
|
points = FloatField()
|
||||||
created_at = DateTimeField(default=datetime.now)
|
created_at = DateTimeField(default=datetime.now)
|
||||||
|
|
||||||
|
|
||||||
|
@ -12,6 +12,7 @@ import sys, inspect, json
|
|||||||
from datetime import datetime, date
|
from datetime import datetime, date
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from playhouse.shortcuts import model_to_dict, dict_to_model
|
from playhouse.shortcuts import model_to_dict, dict_to_model
|
||||||
|
from playhouse.db_url import connect
|
||||||
from .model import *
|
from .model import *
|
||||||
|
|
||||||
class DateTimeEncoder(json.JSONEncoder):
|
class DateTimeEncoder(json.JSONEncoder):
|
||||||
@ -59,7 +60,7 @@ def get_module_cls(module: str) -> tuple[tuple[str, object]]:
|
|||||||
tables = tuple(table[1] for table in get_module_cls('dbmodel.model') if not table[1] is BaseModel)
|
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)
|
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:
|
def init_local(name: Path | str) -> None:
|
||||||
'''
|
'''
|
||||||
(Creates,) Connects and Initializes the db descriptor from a given *.db file
|
(Creates,) Connects and Initializes the db descriptor from a given *.db file
|
||||||
'''
|
'''
|
||||||
@ -71,14 +72,23 @@ def init_db(name: Path | str) -> None:
|
|||||||
if type(name) is Path:
|
if type(name) is Path:
|
||||||
name = str(name)
|
name = str(name)
|
||||||
|
|
||||||
# Switch Database; Initialize if needed
|
database = SqliteDatabase(None, autoconnect=False)
|
||||||
if not db.is_closed():
|
db.initialize(database)
|
||||||
db.close()
|
|
||||||
db.init(name)
|
db.init(name)
|
||||||
db.connect()
|
db.connect()
|
||||||
db.create_tables(tables) # Ensure tables exist
|
db.create_tables(tables) # Ensure tables exist
|
||||||
|
|
||||||
|
|
||||||
|
def init_postgres(url: str) -> None:
|
||||||
|
|
||||||
|
assert isinstance(url, str), "Provided url isnt a String"
|
||||||
|
|
||||||
|
database = connect(url)
|
||||||
|
db.initialize(database)
|
||||||
|
db.connect()
|
||||||
|
db.create_tables(tables)
|
||||||
|
|
||||||
|
|
||||||
def save_as_json(filename: str, path: Path | str = Path('.')) -> Path:
|
def save_as_json(filename: str, path: Path | str = Path('.')) -> Path:
|
||||||
'''
|
'''
|
||||||
Saves the current loaded Database as <filename>.json to given <path>
|
Saves the current loaded Database as <filename>.json to given <path>
|
||||||
|
@ -7,9 +7,14 @@ class AnalyzerState:
|
|||||||
return cls._instance
|
return cls._instance
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.class_id = 1
|
self.class_id = -1
|
||||||
self.group_id = 1
|
self.group_id = -1
|
||||||
self.student_id = 1
|
self.student_id = -1
|
||||||
|
|
||||||
|
def reset(self):
|
||||||
|
self.class_id = -1
|
||||||
|
self.group_id = -1
|
||||||
|
self.student_id = -1
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f'''
|
return f'''
|
||||||
|
@ -167,7 +167,6 @@ def add_lecture(class_id: int) -> bool:
|
|||||||
@immapp.static(inited=False)
|
@immapp.static(inited=False)
|
||||||
def group_list(class_id: int) -> None:
|
def group_list(class_id: int) -> None:
|
||||||
imgui_md.render_unindented('# Groups')
|
imgui_md.render_unindented('# Groups')
|
||||||
|
|
||||||
if class_id < 1:
|
if class_id < 1:
|
||||||
return
|
return
|
||||||
|
|
||||||
@ -340,7 +339,7 @@ def add_student(class_id: int) -> bool:
|
|||||||
statics.prename = str()
|
statics.prename = str()
|
||||||
statics.surname = str()
|
statics.surname = str()
|
||||||
|
|
||||||
statics.genders = ["Male", "Female"]
|
statics.genders = ["Male", "Female", "Diverse"]
|
||||||
statics.gender_select = 0
|
statics.gender_select = 0
|
||||||
|
|
||||||
statics.studys = list(Study.select())
|
statics.studys = list(Study.select())
|
||||||
@ -367,14 +366,22 @@ def add_student(class_id: int) -> bool:
|
|||||||
|
|
||||||
_, statics.prename = imgui.input_text("First Name", statics.prename)
|
_, statics.prename = imgui.input_text("First Name", statics.prename)
|
||||||
_, statics.surname = imgui.input_text("Last Name", statics.surname)
|
_, statics.surname = imgui.input_text("Last Name", statics.surname)
|
||||||
|
|
||||||
_, statics.gender_select = imgui.combo("Gender", statics.gender_select, statics.genders)
|
imgui.text("Gender")
|
||||||
|
for n, gender in enumerate(statics.genders):
|
||||||
|
changed = imgui.radio_button(gender, statics.gender_select == n)
|
||||||
|
if changed:
|
||||||
|
statics.gender_select = n
|
||||||
|
|
||||||
_, statics.study_select = imgui.combo("Study Progamm", statics.study_select, statics.study_labels)
|
_, statics.study_select = imgui.combo("Study Progamm", statics.study_select, statics.study_labels)
|
||||||
|
|
||||||
_, statics.group_select = imgui.combo("Group", statics.group_select, statics.group_labels)
|
_, statics.group_select = imgui.combo("Group", statics.group_select, statics.group_labels)
|
||||||
|
|
||||||
_, statics.grader_select = imgui.combo("Grader", statics.grader_select, statics.graders)
|
imgui.text("Grader")
|
||||||
|
for n, grader in enumerate(statics.graders):
|
||||||
|
changed = imgui.radio_button(grader, statics.grader_select == n)
|
||||||
|
if changed:
|
||||||
|
statics.grader_select = n
|
||||||
|
|
||||||
if imgui.button("Add"):
|
if imgui.button("Add"):
|
||||||
s = Student.create(
|
s = Student.create(
|
||||||
@ -408,6 +415,49 @@ def add_student(class_id: int) -> bool:
|
|||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
@immapp.static(inited=False)
|
||||||
|
def study_list() -> None:
|
||||||
|
statics = study_list
|
||||||
|
|
||||||
|
if not statics.inited:
|
||||||
|
statics.studys = Study.select().order_by(Study.id)
|
||||||
|
statics.new_study = str()
|
||||||
|
statics.inited = True
|
||||||
|
|
||||||
|
imgui_md.render_unindented("# Study Programms")
|
||||||
|
|
||||||
|
if imgui.button("Add Study"):
|
||||||
|
Study.create(name=statics.new_study)
|
||||||
|
statics.inited = False
|
||||||
|
|
||||||
|
imgui.same_line()
|
||||||
|
_, statics.new_study = imgui.input_text("##study", statics.new_study)
|
||||||
|
|
||||||
|
if imgui.begin_table("Studys", 2, imgui.TableFlags_.sizing_fixed_fit.value):
|
||||||
|
for study in statics.studys:
|
||||||
|
if study.name == 'Student':
|
||||||
|
continue
|
||||||
|
imgui.table_next_row()
|
||||||
|
imgui.table_next_column()
|
||||||
|
|
||||||
|
if imgui.button(f"X##{study.id}"):
|
||||||
|
students = Student.select().where(Student.study_id == study.id)
|
||||||
|
nostudy = Study.select().where(Study.name == "Student").get()
|
||||||
|
for student in students:
|
||||||
|
student.study_id = nostudy.id
|
||||||
|
with db.atomic():
|
||||||
|
Student.bulk_update(students, fields=[Student.study_id], batch_size=50)
|
||||||
|
study.delete_instance()
|
||||||
|
statics.inited = False
|
||||||
|
imgui.end_table()
|
||||||
|
return
|
||||||
|
|
||||||
|
imgui.same_line()
|
||||||
|
imgui.text(study.name)
|
||||||
|
#imgui.table_next_column()
|
||||||
|
#imgui.text(f"{group.project}")
|
||||||
|
imgui.end_table()
|
||||||
|
|
||||||
|
|
||||||
def class_graph() -> None:
|
def class_graph() -> None:
|
||||||
if db.is_closed():
|
if db.is_closed():
|
||||||
@ -440,4 +490,8 @@ def class_graph() -> None:
|
|||||||
if imgui.begin_child("Ranking", ImVec2(w1/3, h1), imgui.ChildFlags_.borders.value):
|
if imgui.begin_child("Ranking", ImVec2(w1/3, h1), imgui.ChildFlags_.borders.value):
|
||||||
ranking(state.class_id)
|
ranking(state.class_id)
|
||||||
imgui.end_child()
|
imgui.end_child()
|
||||||
|
imgui.same_line()
|
||||||
|
if imgui.begin_child("Studys", ImVec2(w1/3, h1), imgui.ChildFlags_.borders.value):
|
||||||
|
study_list()
|
||||||
|
imgui.end_child()
|
||||||
imgui.end_child()
|
imgui.end_child()
|
||||||
|
@ -52,6 +52,19 @@ def load_properties() -> DocumentProperties:
|
|||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
file = Path('./pickles/document_properties.pkl')
|
||||||
|
try:
|
||||||
|
file.resolve(strict=True)
|
||||||
|
|
||||||
|
except FileNotFoundError:
|
||||||
|
dump_properties(DocumentProperties(
|
||||||
|
logo = Path("./assets/learnlytics.svg"),
|
||||||
|
save_dir = Path.home(),
|
||||||
|
file_name = "document",
|
||||||
|
author = "Learnlytics"
|
||||||
|
))
|
||||||
|
|
||||||
|
|
||||||
@immapp.static(inited=False)
|
@immapp.static(inited=False)
|
||||||
def document_properties(class_id: int) -> None:
|
def document_properties(class_id: int) -> None:
|
||||||
# TO DO: Store Properties persistent
|
# TO DO: Store Properties persistent
|
||||||
@ -137,14 +150,21 @@ def document_properties(class_id: int) -> None:
|
|||||||
return statics.properties
|
return statics.properties
|
||||||
|
|
||||||
@immapp.static(inited=False)
|
@immapp.static(inited=False)
|
||||||
def filters() -> list[Study]:
|
def filters(class_id: int) -> list[Study]:
|
||||||
statics = filters
|
statics = filters
|
||||||
|
|
||||||
if not statics.inited:
|
if not statics.inited:
|
||||||
|
statics.class_id = class_id
|
||||||
statics.studys = list(Study.select())
|
statics.studys = list(Study.select())
|
||||||
statics.checked = [True] * len(statics.studys)
|
statics.checked = [True] * len(statics.studys)
|
||||||
statics.inited = True
|
statics.inited = True
|
||||||
|
|
||||||
|
if statics.class_id != class_id:
|
||||||
|
statics.inited = False
|
||||||
|
|
||||||
|
if Study.select().count() != len(statics.studys):
|
||||||
|
statics.inited = False
|
||||||
|
|
||||||
imgui_md.render_unindented("### Study Filters")
|
imgui_md.render_unindented("### Study Filters")
|
||||||
imgui.text("")
|
imgui.text("")
|
||||||
|
|
||||||
@ -219,7 +239,7 @@ def create_document(class_id: int) -> None:
|
|||||||
if imgui.begin_child("Filters", ImVec2(w*0.54, h), imgui.ChildFlags_.borders.value):
|
if imgui.begin_child("Filters", ImVec2(w*0.54, h), imgui.ChildFlags_.borders.value):
|
||||||
w1, h1 = imgui.get_content_region_avail()
|
w1, h1 = imgui.get_content_region_avail()
|
||||||
if imgui.begin_child("Study Filters", ImVec2(w1, h1*0.2)):
|
if imgui.begin_child("Study Filters", ImVec2(w1, h1*0.2)):
|
||||||
study_filters = filters()
|
study_filters = filters(statics.class_id)
|
||||||
imgui.end_child()
|
imgui.end_child()
|
||||||
|
|
||||||
if imgui.begin_child("Order By", ImVec2(w1, h1*0.2)):
|
if imgui.begin_child("Order By", ImVec2(w1, h1*0.2)):
|
||||||
@ -230,17 +250,17 @@ def create_document(class_id: int) -> None:
|
|||||||
properties = document_properties(statics.class_id)
|
properties = document_properties(statics.class_id)
|
||||||
if imgui.button("Create"):
|
if imgui.button("Create"):
|
||||||
pdf, css = get_pdf(statics.class_id)
|
pdf, css = get_pdf(statics.class_id)
|
||||||
header = get_pdf_header(state.class_id, [study.name for study in study_filters], properties.author, properties.logo)
|
header = get_pdf_header_creator(state.class_id, [study.name for study in study_filters], properties.author, properties.logo)
|
||||||
|
|
||||||
match statics.order_by:
|
match statics.order_by:
|
||||||
case "Group":
|
case "Group":
|
||||||
sections = create_group_pdf(study_filters)
|
sections = create_group_pdf(statics.class_id, study_filters)
|
||||||
|
|
||||||
case "Study Program":
|
case "Study Program":
|
||||||
sections = create_study_pdf(study_filters)
|
sections = create_study_pdf(statics.class_id, study_filters)
|
||||||
|
|
||||||
case 'Ranking':
|
case 'Ranking':
|
||||||
sections = create_ranking_pdf(study_filters)
|
sections = create_ranking_pdf(statics.class_id, study_filters)
|
||||||
|
|
||||||
pdf.add_section(header, user_css=css)
|
pdf.add_section(header, user_css=css)
|
||||||
for section in sections:
|
for section in sections:
|
||||||
|
@ -2,18 +2,21 @@ from imgui_bundle import (
|
|||||||
imgui,
|
imgui,
|
||||||
imgui_md,
|
imgui_md,
|
||||||
immapp,
|
immapp,
|
||||||
|
im_file_dialog,
|
||||||
ImVec2,
|
ImVec2,
|
||||||
ImVec4
|
ImVec4
|
||||||
)
|
)
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
from peewee import fn
|
from peewee import fn
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
from dbmodel import *
|
from dbmodel import *
|
||||||
from grader import get_gradings, get_grader
|
from grader import get_gradings, get_grader
|
||||||
|
from pdf import *
|
||||||
from .analyzer_state import AnalyzerState
|
from .analyzer_state import AnalyzerState
|
||||||
from .plotter import plot_bar_line_percentage
|
from .plotter import plot_bar_line_percentage
|
||||||
|
from .document_creator import load_properties
|
||||||
|
|
||||||
state = AnalyzerState()
|
state = AnalyzerState()
|
||||||
PROGRESS_BAR_COLOR = ImVec4(190, 190, 40, 255)/255
|
PROGRESS_BAR_COLOR = ImVec4(190, 190, 40, 255)/255
|
||||||
@ -43,7 +46,15 @@ def header(student_id: int, reset: bool) -> None:
|
|||||||
|
|
||||||
imgui_md.render_unindented(f"# {statics.student.prename} {statics.student.surname} - {statics.has_passed}")
|
imgui_md.render_unindented(f"# {statics.student.prename} {statics.student.surname} - {statics.has_passed}")
|
||||||
imgui_md.render_unindented(f"Degree Program: **{statics.study.name}**")
|
imgui_md.render_unindented(f"Degree Program: **{statics.study.name}**")
|
||||||
|
imgui.same_line()
|
||||||
|
if imgui.button("Create Summary"):
|
||||||
|
properties = load_properties()
|
||||||
|
pdf, css = get_pdf(statics.student.class_id)
|
||||||
|
sections = get_pdf_student(statics.student.id, properties.author, properties.logo)
|
||||||
|
for section in sections:
|
||||||
|
pdf.add_section(section, user_css = css)
|
||||||
|
save = properties.save_dir / (properties.file_name + '.pdf')
|
||||||
|
pdf.save(save)
|
||||||
|
|
||||||
@immapp.static(inited=False)
|
@immapp.static(inited=False)
|
||||||
def plot(student_id: int, reset: bool) -> None:
|
def plot(student_id: int, reset: bool) -> None:
|
||||||
@ -96,7 +107,9 @@ def dialog(student_id: int) -> None:
|
|||||||
|
|
||||||
statics.prename = statics.student.prename
|
statics.prename = statics.student.prename
|
||||||
statics.surname = statics.student.surname
|
statics.surname = statics.student.surname
|
||||||
statics.sex = 0 if statics.student.sex == "Male" else 1
|
|
||||||
|
statics.genders = ["Male", "Female", "Diverse"]
|
||||||
|
statics.sex = statics.genders.index(statics.student.sex)
|
||||||
|
|
||||||
statics.studys = list(Study.select())
|
statics.studys = list(Study.select())
|
||||||
s = Study.get_by_id(statics.student.study_id)
|
s = Study.get_by_id(statics.student.study_id)
|
||||||
@ -131,7 +144,7 @@ def dialog(student_id: int) -> None:
|
|||||||
imgui.table_next_column()
|
imgui.table_next_column()
|
||||||
imgui.text("Sex")
|
imgui.text("Sex")
|
||||||
imgui.table_next_column()
|
imgui.table_next_column()
|
||||||
_, statics.sex = imgui.combo("##Sex", statics.sex, ["Male", "Female"])
|
_, statics.sex = imgui.combo("##Sex", statics.sex, statics.genders)
|
||||||
|
|
||||||
imgui.table_next_row()
|
imgui.table_next_row()
|
||||||
imgui.table_next_column()
|
imgui.table_next_column()
|
||||||
@ -156,7 +169,7 @@ def dialog(student_id: int) -> None:
|
|||||||
if imgui.button("Save"):
|
if imgui.button("Save"):
|
||||||
statics.student.prename = statics.prename
|
statics.student.prename = statics.prename
|
||||||
statics.student.surname = statics.surname
|
statics.student.surname = statics.surname
|
||||||
statics.student.sex = "Male" if statics.sex == 0 else "Female"
|
statics.student.sex = statics.genders[statics.sex]
|
||||||
statics.student.study_id = statics.studys[statics.study].id
|
statics.student.study_id = statics.studys[statics.study].id
|
||||||
statics.student.group_id = statics.groups[statics.group].id
|
statics.student.group_id = statics.groups[statics.group].id
|
||||||
statics.student.grader = get_grader(statics.graders[statics.grader]).alt_name
|
statics.student.grader = get_grader(statics.graders[statics.grader]).alt_name
|
||||||
|
@ -18,15 +18,18 @@ def class_selector() -> int:
|
|||||||
if not statics.inited:
|
if not statics.inited:
|
||||||
statics.selector = 0
|
statics.selector = 0
|
||||||
statics.inited = True
|
statics.inited = True
|
||||||
|
|
||||||
labels = (c.name for c in Class.select())
|
|
||||||
_, statics.selector = imgui.combo("##Classes", statics.selector, list(labels))
|
|
||||||
|
|
||||||
current = Class.select()[statics.selector].id
|
|
||||||
|
|
||||||
if imgui.button("New Class"):
|
if imgui.button("New Class"):
|
||||||
imgui.open_popup("NewC")
|
imgui.open_popup("NewC")
|
||||||
new_class()
|
new_class()
|
||||||
|
|
||||||
|
classes = Class.select()
|
||||||
|
if classes.count() < 1:
|
||||||
|
return -1
|
||||||
|
|
||||||
|
labels = (c.name for c in classes)
|
||||||
|
_, statics.selector = imgui.combo("##Classes", statics.selector, list(labels))
|
||||||
|
|
||||||
|
current = classes[statics.selector].id
|
||||||
|
|
||||||
imgui.same_line()
|
imgui.same_line()
|
||||||
|
|
||||||
@ -56,6 +59,7 @@ def new_class() -> None:
|
|||||||
|
|
||||||
if imgui.button("Add"):
|
if imgui.button("Add"):
|
||||||
clas = Class.create(name=statics.name)
|
clas = Class.create(name=statics.name)
|
||||||
|
Study.get_or_create(name='Student')
|
||||||
Group.create(
|
Group.create(
|
||||||
name="NoGroup",
|
name="NoGroup",
|
||||||
project="NoProject",
|
project="NoProject",
|
||||||
|
@ -4,8 +4,6 @@ from typing import List
|
|||||||
|
|
||||||
from .database import *
|
from .database import *
|
||||||
|
|
||||||
from .editor import editor
|
|
||||||
|
|
||||||
def database_docking_splits() -> List[hello_imgui.DockingSplit]:
|
def database_docking_splits() -> List[hello_imgui.DockingSplit]:
|
||||||
"""
|
"""
|
||||||
Defines the docking layout for the database editor.
|
Defines the docking layout for the database editor.
|
||||||
@ -54,17 +52,7 @@ def set_database_editor_layout() -> List[hello_imgui.DockableWindow]:
|
|||||||
log.dock_space_name = "CommandSpace2"
|
log.dock_space_name = "CommandSpace2"
|
||||||
log.gui_function = hello_imgui.log_gui
|
log.gui_function = hello_imgui.log_gui
|
||||||
|
|
||||||
table_view = hello_imgui.DockableWindow()
|
return [file_dialog, log]
|
||||||
table_view.label = "Table"
|
|
||||||
table_view.dock_space_name = "MainDockSpace"
|
|
||||||
table_view.gui_function = lambda: table()
|
|
||||||
|
|
||||||
eeditor = hello_imgui.DockableWindow()
|
|
||||||
eeditor.label = "Editor"
|
|
||||||
eeditor.dock_space_name = "CommandSpace"
|
|
||||||
eeditor.gui_function = lambda: editor()
|
|
||||||
|
|
||||||
return [file_dialog, log, table_view, eeditor]
|
|
||||||
|
|
||||||
def database_editor_layout() -> hello_imgui.DockingParams:
|
def database_editor_layout() -> hello_imgui.DockingParams:
|
||||||
"""
|
"""
|
||||||
|
@ -9,6 +9,7 @@ to set up the database editing environment.
|
|||||||
# Custom
|
# Custom
|
||||||
from dbmodel import *
|
from dbmodel import *
|
||||||
from gui import *
|
from gui import *
|
||||||
|
from gui.analyzer.analyzer_state import AnalyzerState
|
||||||
from grader import get_gradings
|
from grader import get_gradings
|
||||||
|
|
||||||
# External
|
# External
|
||||||
@ -57,7 +58,7 @@ def file_info(path: Path) -> None:
|
|||||||
}
|
}
|
||||||
|
|
||||||
# Create ImGui table to display file information
|
# Create ImGui table to display file information
|
||||||
if imgui.begin_table("File Info", 2):
|
if imgui.begin_table("File Info", 2, imgui.TableFlags_.sizing_fixed_fit.value):
|
||||||
imgui.table_setup_column(" ", 0)
|
imgui.table_setup_column(" ", 0)
|
||||||
imgui.table_setup_column(" ")
|
imgui.table_setup_column(" ")
|
||||||
|
|
||||||
@ -73,207 +74,90 @@ def file_info(path: Path) -> None:
|
|||||||
|
|
||||||
imgui.end_table()
|
imgui.end_table()
|
||||||
|
|
||||||
@immapp.static(inited=False, res=False)
|
|
||||||
|
def connect_postgres() -> None:
|
||||||
|
pass
|
||||||
|
|
||||||
|
@immapp.static(inited=False)
|
||||||
def select_file() -> None:
|
def select_file() -> None:
|
||||||
"""
|
statics = select_file
|
||||||
Handles the selection and loading of a database file (JSON or SQLite).
|
|
||||||
It initializes necessary state, allows users to open a file dialog,
|
|
||||||
and processes the selected file by either loading or converting it.
|
|
||||||
"""
|
|
||||||
statics = select_file # Access static variables within the function
|
|
||||||
|
|
||||||
# Initialize static variables on the first function call
|
|
||||||
if not statics.inited:
|
if not statics.inited:
|
||||||
statics.res = None # Stores the selected file result
|
try:
|
||||||
statics.current = None # Stores the currently loaded database file path
|
with open("./pickles/database_location.txt", "r") as f:
|
||||||
|
statics.file = Path(f.read())
|
||||||
# Retrieve the last used database file from persistent storage
|
except FileNotFoundError:
|
||||||
with shelve.open("state") as state:
|
statics.file = None
|
||||||
statics.current = Path(state.get("DB") or "")
|
|
||||||
statics.inited = True
|
statics.inited = True
|
||||||
|
|
||||||
|
imgui_md.render_unindented('# Database Manager')
|
||||||
|
|
||||||
# Render UI title and display file information
|
if imgui.button("Create DB"):
|
||||||
imgui_md.render("# Database Manager")
|
im_file_dialog.FileDialog.instance().save(
|
||||||
file_info(statics.current)
|
"CreateDatabase", "Create Database", "Database File (*.db){.db}", str(Path.home())
|
||||||
|
)
|
||||||
|
|
||||||
# Button to open the file selection dialog
|
# Handle the file dialog result
|
||||||
if imgui.button("Open File"):
|
if im_file_dialog.FileDialog.instance().is_done("CreateDatabase"):
|
||||||
|
if im_file_dialog.FileDialog.instance().has_result():
|
||||||
|
file = im_file_dialog.FileDialog.instance().get_result()
|
||||||
|
init_db(str(file.path()))
|
||||||
|
with open("./pickles/database_location.txt", "w") as f:
|
||||||
|
f.write(str(file.path()))
|
||||||
|
statics.file = Path(str(file))
|
||||||
|
im_file_dialog.FileDialog.instance().close()
|
||||||
|
|
||||||
|
imgui.same_line()
|
||||||
|
if imgui.button("Load DB File"):
|
||||||
im_file_dialog.FileDialog.instance().open(
|
im_file_dialog.FileDialog.instance().open(
|
||||||
"SelectDatabase", "Open Database", "Database File (*.json;*.db){.json,.db}"
|
"SelectDatabase", "Open Database", "Database File (*.db){.db}", False, str(Path.home())
|
||||||
)
|
)
|
||||||
|
|
||||||
# Handle the file dialog result
|
# Handle the file dialog result
|
||||||
if im_file_dialog.FileDialog.instance().is_done("SelectDatabase"):
|
if im_file_dialog.FileDialog.instance().is_done("SelectDatabase"):
|
||||||
if im_file_dialog.FileDialog.instance().has_result():
|
if im_file_dialog.FileDialog.instance().has_result():
|
||||||
statics.res = im_file_dialog.FileDialog.instance().get_result()
|
file = im_file_dialog.FileDialog.instance().get_result()
|
||||||
|
init_db(str(file.path()))
|
||||||
|
with open("./pickles/database_location.txt", "w") as f:
|
||||||
|
f.write(str(file.path()))
|
||||||
|
statics.file = Path(str(file))
|
||||||
|
AnalyzerState().reset()
|
||||||
im_file_dialog.FileDialog.instance().close()
|
im_file_dialog.FileDialog.instance().close()
|
||||||
|
|
||||||
# Process the selected file if available
|
|
||||||
if statics.res:
|
|
||||||
filename = statics.res.filename()
|
|
||||||
info = Path(statics.res.path())
|
|
||||||
|
|
||||||
imgui.separator() # UI separator for clarity
|
|
||||||
file_info(info) # Display information about the selected file
|
|
||||||
|
|
||||||
file = None
|
|
||||||
|
|
||||||
# Load the selected database file
|
|
||||||
if imgui.button("Load"):
|
|
||||||
# Ensure any currently open database is closed before loading a new one
|
|
||||||
if not db.is_closed():
|
|
||||||
db.close()
|
|
||||||
|
|
||||||
# Handle JSON files by converting them to SQLite databases
|
|
||||||
if statics.res.extension() == '.json':
|
|
||||||
file = filename.removesuffix('.json') + '.db' # Convert JSON filename to SQLite filename
|
|
||||||
init_db(file)
|
|
||||||
load_from_json(str(info)) # Convert and load JSON data into the database
|
|
||||||
|
|
||||||
# Handle SQLite database files directly
|
|
||||||
if statics.res.extension() == '.db':
|
|
||||||
file = str(statics.res.path())
|
|
||||||
init_db(file)
|
|
||||||
|
|
||||||
# Save the selected database path to persistent storage
|
|
||||||
with shelve.open("state") as state:
|
|
||||||
state["DB"] = file
|
|
||||||
|
|
||||||
# Update application state and reset selection result
|
|
||||||
statics.res = None
|
|
||||||
|
|
||||||
@immapp.static(inited=False)
|
if imgui.button("Export DB"):
|
||||||
def editor() -> None:
|
im_file_dialog.FileDialog.instance().save(
|
||||||
"""
|
"ExportJSON", "Export JSON", "JSON Export (*.json){.json}", str(Path.home())
|
||||||
Class Editor UI Component.
|
|
||||||
|
|
||||||
This function initializes and renders the class selection interface within the database editor.
|
|
||||||
It maintains a static state to keep track of the selected class and fetches available classes.
|
|
||||||
"""
|
|
||||||
statics = class_editor
|
|
||||||
if db.is_closed():
|
|
||||||
imgui.text("DB")
|
|
||||||
return
|
|
||||||
statics.classes = Class.select()
|
|
||||||
if not statics.inited:
|
|
||||||
statics.selected = 0
|
|
||||||
statics.value = statics.classes[statics.selected].name if statics.classes else str()
|
|
||||||
statics.inited = True
|
|
||||||
|
|
||||||
# Fetch available classes from the database
|
|
||||||
|
|
||||||
# Render the UI for class selection
|
|
||||||
imgui_md.render("# Edit Classes")
|
|
||||||
changed, statics.selected = imgui.combo("##Classes", statics.selected, [c.name for c in statics.classes])
|
|
||||||
if changed:
|
|
||||||
statics.value = statics.classes[statics.selected].name
|
|
||||||
_, statics.value = imgui.input_text("##input_class", statics.value)
|
|
||||||
|
|
||||||
if imgui.button("Update"):
|
|
||||||
clas = statics.classes[statics.selected]
|
|
||||||
clas.name, old_name = statics.value, clas.name
|
|
||||||
clas.save()
|
|
||||||
|
|
||||||
imgui.same_line()
|
|
||||||
|
|
||||||
if imgui.button("New"):
|
|
||||||
Class.create(name=statics.value)
|
|
||||||
|
|
||||||
imgui.same_line()
|
|
||||||
|
|
||||||
if imgui.button("Delete"):
|
|
||||||
clas = statics.classes[statics.selected]
|
|
||||||
clas.delete_instance()
|
|
||||||
statics.selected -= 1
|
|
||||||
statics.value = statics.classes[statics.selected].name
|
|
||||||
|
|
||||||
|
|
||||||
def create_editor_popup(table, action: str, selectors: dict) -> None:
|
|
||||||
table_flags = (
|
|
||||||
imgui.TableFlags_.row_bg.value
|
|
||||||
)
|
)
|
||||||
cols = 2
|
|
||||||
rows = len(table._meta.fields)
|
|
||||||
|
|
||||||
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()
|
|
||||||
|
|
||||||
id = 0
|
# Handle the file dialog result
|
||||||
for k, v in table._meta.fields.items():
|
if im_file_dialog.FileDialog.instance().is_done("ExportJSON"):
|
||||||
# Don't show Fields
|
if im_file_dialog.FileDialog.instance().has_result():
|
||||||
match type(v):
|
file = im_file_dialog.FileDialog.instance().get_result()
|
||||||
case peewee.AutoField:
|
filename = str(file).removesuffix('.json') + '.db'
|
||||||
continue
|
save_as_json(filename, Path(str(file.path())))
|
||||||
case peewee.DateTimeField:
|
im_file_dialog.FileDialog.instance().close()
|
||||||
continue
|
|
||||||
|
|
||||||
imgui.table_next_row()
|
|
||||||
imgui.table_set_column_index(0)
|
|
||||||
label = str(k).removesuffix('_id')
|
|
||||||
imgui.text(label.title())
|
|
||||||
imgui.table_set_column_index(1)
|
|
||||||
|
|
||||||
# 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()
|
|
||||||
|
|
||||||
if k == 'grader':
|
|
||||||
graders = [g.alt_name for g in get_gradings()]
|
|
||||||
_, selectors[id] = imgui.combo(f"##{id}", selectors[id], graders)
|
|
||||||
else:
|
|
||||||
_, selectors[id] = imgui.input_text(f"##{id}", selectors[id])
|
|
||||||
case peewee.FloatField:
|
|
||||||
if id not in selectors:
|
|
||||||
selectors[id] = float()
|
|
||||||
_, selectors[id] = imgui.input_float(f"##{id}", selectors[id])
|
|
||||||
case peewee.ForeignKeyField:
|
|
||||||
if id not in selectors:
|
|
||||||
selectors[id] = int()
|
|
||||||
|
|
||||||
labels = list()
|
|
||||||
match k:
|
|
||||||
case 'class_id':
|
|
||||||
labels = [clas.name for clas in Class.select()]
|
|
||||||
case 'lecture_id':
|
|
||||||
labels = [lecture.title for lecture in Lecture.select()]
|
|
||||||
|
|
||||||
if not labels:
|
|
||||||
imgui.text("No Element for this Attribute")
|
|
||||||
else:
|
|
||||||
_, selectors[id] = imgui.combo(f"##{id}", selectors[id], labels)
|
|
||||||
case _:
|
|
||||||
imgui.text(f"Unknown Field {k}")
|
|
||||||
|
|
||||||
id += 1
|
|
||||||
imgui.end_table()
|
|
||||||
|
|
||||||
if imgui.button(action):
|
|
||||||
match action:
|
|
||||||
case "Create":
|
|
||||||
print("Create")
|
|
||||||
case "Update":
|
|
||||||
print("Update")
|
|
||||||
case "Delete":
|
|
||||||
print("Delete")
|
|
||||||
case _:
|
|
||||||
print("Unknown Case")
|
|
||||||
|
|
||||||
# Clear & Close Popup
|
|
||||||
selectors.clear()
|
|
||||||
imgui.close_current_popup()
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
imgui.same_line()
|
||||||
|
if imgui.button("Load JSON"):
|
||||||
|
im_file_dialog.FileDialog.instance().open(
|
||||||
|
"SelectJSON", "Open JSON Export", "JSON Export (*.json){.json}", False, str(Path.home())
|
||||||
|
)
|
||||||
|
|
||||||
|
if im_file_dialog.FileDialog.instance().is_done("SelectJSON"):
|
||||||
|
if im_file_dialog.FileDialog.instance().has_result():
|
||||||
|
file = im_file_dialog.FileDialog.instance().get_result()
|
||||||
|
filename = str(file).removesuffix('.json') + '.db'
|
||||||
|
create_from_json(filename, Path(str(file.path())))
|
||||||
|
with open("./pickles/database_location.txt", "w") as f:
|
||||||
|
f.write(str(file.path()))
|
||||||
|
statics.file = Path(filename)
|
||||||
|
AnalyzerState().reset()
|
||||||
|
im_file_dialog.FileDialog.instance().close()
|
||||||
|
|
||||||
|
if imgui.button("Connect Postgres (WIP)"):
|
||||||
|
connect_postgres()
|
||||||
|
|
||||||
|
imgui.separator()
|
||||||
|
if statics.file:
|
||||||
|
file_info(statics.file)
|
||||||
|
@ -1,141 +0,0 @@
|
|||||||
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() -> None:
|
|
||||||
"""
|
|
||||||
Database Editor UI Function.
|
|
||||||
|
|
||||||
Calls the class editor function to render its UI component.
|
|
||||||
"""
|
|
||||||
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()
|
|
||||||
|
|
||||||
|
|
@ -1,5 +1,3 @@
|
|||||||
import shelve
|
|
||||||
|
|
||||||
from imgui_bundle import (
|
from imgui_bundle import (
|
||||||
hello_imgui,
|
hello_imgui,
|
||||||
immapp
|
immapp
|
||||||
@ -12,18 +10,22 @@ from gui import (
|
|||||||
status_bar
|
status_bar
|
||||||
)
|
)
|
||||||
|
|
||||||
from dbmodel import init_db
|
from dbmodel import init_postgres
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
def main() -> None:
|
def main() -> None:
|
||||||
"""Main function to initialize and run the application."""
|
"""Main function to initialize and run the application."""
|
||||||
|
|
||||||
# Load Database
|
# Load Database
|
||||||
try:
|
try:
|
||||||
with shelve.open("state") as state:
|
with open("./pickles/database_location.txt", "r") as f:
|
||||||
v = state.get("DB") # Retrieve stored database connection info
|
file = f.read()
|
||||||
init_db(v)
|
except FileNotFoundError:
|
||||||
except Exception as e:
|
file = str(Path.home() / "learnlytics.db")
|
||||||
print(e)
|
with open("./pickles/database_location.txt", "w") as f:
|
||||||
|
f.write(file)
|
||||||
|
|
||||||
|
init_postgres('postgres://admin:admin@127.0.0.1:5432/learnlytics')
|
||||||
|
|
||||||
# Set Window Parameters
|
# Set Window Parameters
|
||||||
runner_params = hello_imgui.RunnerParams()
|
runner_params = hello_imgui.RunnerParams()
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
from .utils import get_pdf, get_pdf_header
|
from .utils import get_pdf, get_pdf_header_creator, get_pdf_student
|
||||||
from .ranking_pdf import create_ranking_pdf
|
from .ranking_pdf import create_ranking_pdf
|
||||||
from .study_pdf import create_study_pdf
|
from .study_pdf import create_study_pdf
|
||||||
from .group_pdf import create_group_pdf
|
from .group_pdf import create_group_pdf
|
||||||
|
@ -70,6 +70,11 @@ hr {
|
|||||||
border: 3px solid #be1e3c;
|
border: 3px solid #be1e3c;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
strong {
|
||||||
|
font-size: 14px;
|
||||||
|
font-family: 'NexusSansPro-Bold';
|
||||||
|
}
|
||||||
|
|
||||||
/* Table */
|
/* Table */
|
||||||
table {
|
table {
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
|
@ -1,27 +1,34 @@
|
|||||||
from dbmodel import *
|
from dbmodel import *
|
||||||
from grader import get_grader
|
from grader import get_grader
|
||||||
|
from .utils import error
|
||||||
|
|
||||||
from markdown_pdf import Section
|
from markdown_pdf import Section
|
||||||
|
|
||||||
from functools import reduce
|
from functools import reduce
|
||||||
import operator
|
import operator
|
||||||
|
|
||||||
def create_group_pdf(filter: list[Study]) -> list[Section]:
|
def create_group_pdf(class_id: int, filter: list[Study]) -> list[Section]:
|
||||||
|
if not filter:
|
||||||
|
return error()
|
||||||
|
|
||||||
sections = list()
|
sections = list()
|
||||||
text = ''
|
text = ''
|
||||||
|
|
||||||
filter = [(Student.study_id == study.id) for study in filter]
|
filter = [(Student.study_id == study.id) for study in filter]
|
||||||
expr = reduce(operator.or_, filter)
|
expr = reduce(operator.or_, filter)
|
||||||
students = list(Student.select().where(expr))
|
students = list(Student.select().where(expr).where(Student.class_id == class_id))
|
||||||
|
|
||||||
group_ids = set([student.group_id for student in students])
|
group_ids = set([student.group_id for student in students])
|
||||||
group_filter = [(Group.id == id) for id in group_ids]
|
group_filter = [(Group.id == id) for id in group_ids]
|
||||||
|
if not group_filter:
|
||||||
|
return [Section('# No Student found')]
|
||||||
group_expr = reduce(operator.or_, group_filter)
|
group_expr = reduce(operator.or_, group_filter)
|
||||||
|
|
||||||
data = {
|
data = {
|
||||||
group: [student for student in Student.select().where(Student.group_id == group.id) if student.study in filter]
|
group: [student for student in Student.select().where(Student.group_id == group.id).where(expr)]
|
||||||
for group in Group.select().where(group_expr)
|
for group in Group.select().where(group_expr)
|
||||||
}
|
}
|
||||||
|
|
||||||
max_points = [lecture.points for lecture in Lecture.select().where(Lecture.class_id == next(iter(data.keys())).class_id)]
|
max_points = [lecture.points for lecture in Lecture.select().where(Lecture.class_id == next(iter(data.keys())).class_id)]
|
||||||
|
|
||||||
n = 0
|
n = 0
|
||||||
|
@ -6,7 +6,7 @@ from itertools import compress
|
|||||||
import operator
|
import operator
|
||||||
from grader import get_grader
|
from grader import get_grader
|
||||||
from collections import Counter
|
from collections import Counter
|
||||||
|
from .utils import error
|
||||||
|
|
||||||
def create_header(number_of_students: int) -> str:
|
def create_header(number_of_students: int) -> str:
|
||||||
text = '## Ranking\n'
|
text = '## Ranking\n'
|
||||||
@ -16,12 +16,15 @@ def create_header(number_of_students: int) -> str:
|
|||||||
return text
|
return text
|
||||||
|
|
||||||
|
|
||||||
def create_ranking_pdf(filter: list[int]) -> list[Section]:
|
def create_ranking_pdf(class_id: int, filter: list[Study]) -> list[Section]:
|
||||||
filter = [(Student.study_id == id) for id in filter]
|
if not filter:
|
||||||
|
return error()
|
||||||
|
|
||||||
|
filter = [(Student.study_id == study.id) for study in filter]
|
||||||
expr = reduce(operator.or_, filter)
|
expr = reduce(operator.or_, filter)
|
||||||
students = [
|
students = [
|
||||||
(student, Submission.select(fn.SUM(Submission.points)).where(Submission.student_id == student.id).scalar())
|
(student, Submission.select(fn.SUM(Submission.points)).where(Submission.student_id == student.id).scalar())
|
||||||
for student in Student.select().where(expr)
|
for student in Student.select().where(expr).where(Student.class_id == class_id)
|
||||||
]
|
]
|
||||||
students.sort(key = lambda tup: tup[1], reverse=True)
|
students.sort(key = lambda tup: tup[1], reverse=True)
|
||||||
|
|
||||||
|
@ -1,14 +1,17 @@
|
|||||||
from dbmodel import *
|
from dbmodel import *
|
||||||
from grader import get_grader
|
from grader import get_grader
|
||||||
from markdown_pdf import Section
|
from markdown_pdf import Section
|
||||||
|
from .utils import error
|
||||||
|
|
||||||
|
def create_study_pdf(class_id: int, filter: list[Study]) -> list[Section]:
|
||||||
|
if not filter:
|
||||||
|
return error()
|
||||||
|
|
||||||
def create_study_pdf(filter: list[int]) -> list[Section]:
|
|
||||||
sections = list()
|
sections = list()
|
||||||
text = ''
|
text = ''
|
||||||
|
|
||||||
data = {
|
data = {
|
||||||
study: list(Student.select().where(Student.study_id == study.id))
|
study: list(Student.select().where(Student.study_id == study.id).where(Student.class_id == class_id))
|
||||||
for study in filter
|
for study in filter
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,8 +1,13 @@
|
|||||||
from dbmodel import Class
|
from dbmodel import *
|
||||||
from markdown_pdf import MarkdownPdf, Section
|
from markdown_pdf import MarkdownPdf, Section
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from collections.abc import Sequence
|
from collections.abc import Sequence
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
from grader import get_grader
|
||||||
|
|
||||||
|
def error() -> list[Section]:
|
||||||
|
return [Section('# No Filter selected')]
|
||||||
|
|
||||||
|
|
||||||
def get_pdf(class_id: int) -> tuple[MarkdownPdf, str]:
|
def get_pdf(class_id: int) -> tuple[MarkdownPdf, str]:
|
||||||
clas = Class.get_by_id(class_id)
|
clas = Class.get_by_id(class_id)
|
||||||
@ -21,7 +26,73 @@ def get_pdf(class_id: int) -> tuple[MarkdownPdf, str]:
|
|||||||
|
|
||||||
return (pdf, css)
|
return (pdf, css)
|
||||||
|
|
||||||
def get_pdf_header(class_id: int, filter: Sequence[str], author: str, logo_file: Path = None) -> Section:
|
|
||||||
|
def get_metadata(author: str) -> str:
|
||||||
|
text = f'Author: {author}\n\n'
|
||||||
|
|
||||||
|
date = datetime.now()
|
||||||
|
text += f'Created: {date.strftime("%A %d.%m.%Y %H:%M")}\n\n'
|
||||||
|
|
||||||
|
text += 'This document is system-generated and may not require manual signatures.\n'
|
||||||
|
return text
|
||||||
|
|
||||||
|
def get_pdf_student(student_id: int, author: str, logo_file: Path = None) -> list[Section]:
|
||||||
|
if student_id < 1:
|
||||||
|
return Section('# No Student selected')
|
||||||
|
sections = list()
|
||||||
|
student = Student.get_by_id(student_id)
|
||||||
|
study = Study.get_by_id(student.study_id)
|
||||||
|
subs = list(Submission.select().where(Submission.student_id == student.id))
|
||||||
|
|
||||||
|
grader = get_grader(student.grader)
|
||||||
|
points = [sub.points for sub in subs]
|
||||||
|
maxs = [sub.lecture_id.points for sub in subs]
|
||||||
|
|
||||||
|
name = f'{student.prename} {student.surname}'
|
||||||
|
passed = f'{name} has passed the course successfully.' if grader.has_passed_course(points, maxs) else f'{name} failed the course.'
|
||||||
|
|
||||||
|
text = ''
|
||||||
|
|
||||||
|
# Append Logo
|
||||||
|
if logo_file:
|
||||||
|
text += f'\n'
|
||||||
|
|
||||||
|
text += f'# {student.prename} {student.surname} - {student.class_id.name}\n'
|
||||||
|
text += f'#### {study.name}\n\n'
|
||||||
|
text += f'Gender: **{student.sex}**\n\n'
|
||||||
|
s = sum(points)
|
||||||
|
text += f'Score: **{int(s) if s.is_integer() else s}/{sum(maxs)}**\n\n'
|
||||||
|
text += f'Percent: **{s/sum(maxs):.1%}**\n\n'
|
||||||
|
text += f'Grade: {grader.get_final_grade(points, maxs)}\n\n'
|
||||||
|
text += passed + '\n\n'
|
||||||
|
|
||||||
|
text += '---\n\n'
|
||||||
|
text += get_metadata(author)
|
||||||
|
sections.append(Section(text))
|
||||||
|
|
||||||
|
text = '# Lectures\n'
|
||||||
|
text += '|Lecture|Submission|Passed?|\n'
|
||||||
|
text += '|-|-|-|\n'
|
||||||
|
n = 0
|
||||||
|
for sub in subs:
|
||||||
|
text += f'|{sub.lecture_id.title}|{int(sub.points) if sub.points.is_integer() else sub.points}/{sub.lecture_id.points}|{grader.get_grade(sub.points, sub.lecture_id.points)}|\n'
|
||||||
|
|
||||||
|
n += 1
|
||||||
|
if n == 30:
|
||||||
|
text += '---\n\n'
|
||||||
|
sections.append(Section(text))
|
||||||
|
n = 0
|
||||||
|
text = '# Lectures\n'
|
||||||
|
text += '|Lecture|Submission|Passed?|\n'
|
||||||
|
text += '|-|-|-|\n'
|
||||||
|
|
||||||
|
if text and n != 0:
|
||||||
|
text += '---\n\n'
|
||||||
|
sections.append(Section(text))
|
||||||
|
|
||||||
|
return sections
|
||||||
|
|
||||||
|
def get_pdf_header_creator(class_id: int, filter: Sequence[str], author: str, logo_file: Path = None) -> Section:
|
||||||
text = ''
|
text = ''
|
||||||
# Append Logo
|
# Append Logo
|
||||||
if logo_file:
|
if logo_file:
|
||||||
@ -37,11 +108,5 @@ def get_pdf_header(class_id: int, filter: Sequence[str], author: str, logo_file:
|
|||||||
text += '---\n\n'
|
text += '---\n\n'
|
||||||
|
|
||||||
# Metadata
|
# Metadata
|
||||||
text += f'Author: {author}\n\n'
|
text += get_metadata(author)
|
||||||
|
|
||||||
date = datetime.now()
|
|
||||||
text += f'Created: {date.strftime("%A %d.%m.%Y %H:%M")}\n\n'
|
|
||||||
|
|
||||||
text += 'This document is system-generated and may not require manual signatures.'
|
|
||||||
|
|
||||||
return Section(text)
|
return Section(text)
|
||||||
|
Binary file not shown.
@ -1 +1 @@
|
|||||||
/storage/programming/Learnlytics/assets/documents/wise-23-24-fri-07-03-2025-01-15a.pdf
|
/home/phil/wise-24-25-sat-08-03-2025-01-53.pdf
|
79
poetry.lock
generated
79
poetry.lock
generated
@ -710,6 +710,83 @@ files = [
|
|||||||
greenlet = ">=3.1.1,<4.0.0"
|
greenlet = ">=3.1.1,<4.0.0"
|
||||||
pyee = ">=12,<13"
|
pyee = ">=12,<13"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "psycopg2-binary"
|
||||||
|
version = "2.9.10"
|
||||||
|
description = "psycopg2 - Python-PostgreSQL Database Adapter"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.8"
|
||||||
|
files = [
|
||||||
|
{file = "psycopg2-binary-2.9.10.tar.gz", hash = "sha256:4b3df0e6990aa98acda57d983942eff13d824135fe2250e6522edaa782a06de2"},
|
||||||
|
{file = "psycopg2_binary-2.9.10-cp310-cp310-macosx_12_0_x86_64.whl", hash = "sha256:0ea8e3d0ae83564f2fc554955d327fa081d065c8ca5cc6d2abb643e2c9c1200f"},
|
||||||
|
{file = "psycopg2_binary-2.9.10-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:3e9c76f0ac6f92ecfc79516a8034a544926430f7b080ec5a0537bca389ee0906"},
|
||||||
|
{file = "psycopg2_binary-2.9.10-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2ad26b467a405c798aaa1458ba09d7e2b6e5f96b1ce0ac15d82fd9f95dc38a92"},
|
||||||
|
{file = "psycopg2_binary-2.9.10-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:270934a475a0e4b6925b5f804e3809dd5f90f8613621d062848dd82f9cd62007"},
|
||||||
|
{file = "psycopg2_binary-2.9.10-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:48b338f08d93e7be4ab2b5f1dbe69dc5e9ef07170fe1f86514422076d9c010d0"},
|
||||||
|
{file = "psycopg2_binary-2.9.10-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f4152f8f76d2023aac16285576a9ecd2b11a9895373a1f10fd9db54b3ff06b4"},
|
||||||
|
{file = "psycopg2_binary-2.9.10-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:32581b3020c72d7a421009ee1c6bf4a131ef5f0a968fab2e2de0c9d2bb4577f1"},
|
||||||
|
{file = "psycopg2_binary-2.9.10-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:2ce3e21dc3437b1d960521eca599d57408a695a0d3c26797ea0f72e834c7ffe5"},
|
||||||
|
{file = "psycopg2_binary-2.9.10-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:e984839e75e0b60cfe75e351db53d6db750b00de45644c5d1f7ee5d1f34a1ce5"},
|
||||||
|
{file = "psycopg2_binary-2.9.10-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3c4745a90b78e51d9ba06e2088a2fe0c693ae19cc8cb051ccda44e8df8a6eb53"},
|
||||||
|
{file = "psycopg2_binary-2.9.10-cp310-cp310-win32.whl", hash = "sha256:e5720a5d25e3b99cd0dc5c8a440570469ff82659bb09431c1439b92caf184d3b"},
|
||||||
|
{file = "psycopg2_binary-2.9.10-cp310-cp310-win_amd64.whl", hash = "sha256:3c18f74eb4386bf35e92ab2354a12c17e5eb4d9798e4c0ad3a00783eae7cd9f1"},
|
||||||
|
{file = "psycopg2_binary-2.9.10-cp311-cp311-macosx_12_0_x86_64.whl", hash = "sha256:04392983d0bb89a8717772a193cfaac58871321e3ec69514e1c4e0d4957b5aff"},
|
||||||
|
{file = "psycopg2_binary-2.9.10-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:1a6784f0ce3fec4edc64e985865c17778514325074adf5ad8f80636cd029ef7c"},
|
||||||
|
{file = "psycopg2_binary-2.9.10-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b5f86c56eeb91dc3135b3fd8a95dc7ae14c538a2f3ad77a19645cf55bab1799c"},
|
||||||
|
{file = "psycopg2_binary-2.9.10-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2b3d2491d4d78b6b14f76881905c7a8a8abcf974aad4a8a0b065273a0ed7a2cb"},
|
||||||
|
{file = "psycopg2_binary-2.9.10-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2286791ececda3a723d1910441c793be44625d86d1a4e79942751197f4d30341"},
|
||||||
|
{file = "psycopg2_binary-2.9.10-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:512d29bb12608891e349af6a0cccedce51677725a921c07dba6342beaf576f9a"},
|
||||||
|
{file = "psycopg2_binary-2.9.10-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:5a507320c58903967ef7384355a4da7ff3f28132d679aeb23572753cbf2ec10b"},
|
||||||
|
{file = "psycopg2_binary-2.9.10-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:6d4fa1079cab9018f4d0bd2db307beaa612b0d13ba73b5c6304b9fe2fb441ff7"},
|
||||||
|
{file = "psycopg2_binary-2.9.10-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:851485a42dbb0bdc1edcdabdb8557c09c9655dfa2ca0460ff210522e073e319e"},
|
||||||
|
{file = "psycopg2_binary-2.9.10-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:35958ec9e46432d9076286dda67942ed6d968b9c3a6a2fd62b48939d1d78bf68"},
|
||||||
|
{file = "psycopg2_binary-2.9.10-cp311-cp311-win32.whl", hash = "sha256:ecced182e935529727401b24d76634a357c71c9275b356efafd8a2a91ec07392"},
|
||||||
|
{file = "psycopg2_binary-2.9.10-cp311-cp311-win_amd64.whl", hash = "sha256:ee0e8c683a7ff25d23b55b11161c2663d4b099770f6085ff0a20d4505778d6b4"},
|
||||||
|
{file = "psycopg2_binary-2.9.10-cp312-cp312-macosx_12_0_x86_64.whl", hash = "sha256:880845dfe1f85d9d5f7c412efea7a08946a46894537e4e5d091732eb1d34d9a0"},
|
||||||
|
{file = "psycopg2_binary-2.9.10-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:9440fa522a79356aaa482aa4ba500b65f28e5d0e63b801abf6aa152a29bd842a"},
|
||||||
|
{file = "psycopg2_binary-2.9.10-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e3923c1d9870c49a2d44f795df0c889a22380d36ef92440ff618ec315757e539"},
|
||||||
|
{file = "psycopg2_binary-2.9.10-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7b2c956c028ea5de47ff3a8d6b3cc3330ab45cf0b7c3da35a2d6ff8420896526"},
|
||||||
|
{file = "psycopg2_binary-2.9.10-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f758ed67cab30b9a8d2833609513ce4d3bd027641673d4ebc9c067e4d208eec1"},
|
||||||
|
{file = "psycopg2_binary-2.9.10-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8cd9b4f2cfab88ed4a9106192de509464b75a906462fb846b936eabe45c2063e"},
|
||||||
|
{file = "psycopg2_binary-2.9.10-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6dc08420625b5a20b53551c50deae6e231e6371194fa0651dbe0fb206452ae1f"},
|
||||||
|
{file = "psycopg2_binary-2.9.10-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:d7cd730dfa7c36dbe8724426bf5612798734bff2d3c3857f36f2733f5bfc7c00"},
|
||||||
|
{file = "psycopg2_binary-2.9.10-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:155e69561d54d02b3c3209545fb08938e27889ff5a10c19de8d23eb5a41be8a5"},
|
||||||
|
{file = "psycopg2_binary-2.9.10-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c3cc28a6fd5a4a26224007712e79b81dbaee2ffb90ff406256158ec4d7b52b47"},
|
||||||
|
{file = "psycopg2_binary-2.9.10-cp312-cp312-win32.whl", hash = "sha256:ec8a77f521a17506a24a5f626cb2aee7850f9b69a0afe704586f63a464f3cd64"},
|
||||||
|
{file = "psycopg2_binary-2.9.10-cp312-cp312-win_amd64.whl", hash = "sha256:18c5ee682b9c6dd3696dad6e54cc7ff3a1a9020df6a5c0f861ef8bfd338c3ca0"},
|
||||||
|
{file = "psycopg2_binary-2.9.10-cp313-cp313-macosx_12_0_x86_64.whl", hash = "sha256:26540d4a9a4e2b096f1ff9cce51253d0504dca5a85872c7f7be23be5a53eb18d"},
|
||||||
|
{file = "psycopg2_binary-2.9.10-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:e217ce4d37667df0bc1c397fdcd8de5e81018ef305aed9415c3b093faaeb10fb"},
|
||||||
|
{file = "psycopg2_binary-2.9.10-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:245159e7ab20a71d989da00f280ca57da7641fa2cdcf71749c193cea540a74f7"},
|
||||||
|
{file = "psycopg2_binary-2.9.10-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3c4ded1a24b20021ebe677b7b08ad10bf09aac197d6943bfe6fec70ac4e4690d"},
|
||||||
|
{file = "psycopg2_binary-2.9.10-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3abb691ff9e57d4a93355f60d4f4c1dd2d68326c968e7db17ea96df3c023ef73"},
|
||||||
|
{file = "psycopg2_binary-2.9.10-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8608c078134f0b3cbd9f89b34bd60a943b23fd33cc5f065e8d5f840061bd0673"},
|
||||||
|
{file = "psycopg2_binary-2.9.10-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:230eeae2d71594103cd5b93fd29d1ace6420d0b86f4778739cb1a5a32f607d1f"},
|
||||||
|
{file = "psycopg2_binary-2.9.10-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:bb89f0a835bcfc1d42ccd5f41f04870c1b936d8507c6df12b7737febc40f0909"},
|
||||||
|
{file = "psycopg2_binary-2.9.10-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f0c2d907a1e102526dd2986df638343388b94c33860ff3bbe1384130828714b1"},
|
||||||
|
{file = "psycopg2_binary-2.9.10-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f8157bed2f51db683f31306aa497311b560f2265998122abe1dce6428bd86567"},
|
||||||
|
{file = "psycopg2_binary-2.9.10-cp313-cp313-win_amd64.whl", hash = "sha256:27422aa5f11fbcd9b18da48373eb67081243662f9b46e6fd07c3eb46e4535142"},
|
||||||
|
{file = "psycopg2_binary-2.9.10-cp38-cp38-macosx_12_0_x86_64.whl", hash = "sha256:eb09aa7f9cecb45027683bb55aebaaf45a0df8bf6de68801a6afdc7947bb09d4"},
|
||||||
|
{file = "psycopg2_binary-2.9.10-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b73d6d7f0ccdad7bc43e6d34273f70d587ef62f824d7261c4ae9b8b1b6af90e8"},
|
||||||
|
{file = "psycopg2_binary-2.9.10-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ce5ab4bf46a211a8e924d307c1b1fcda82368586a19d0a24f8ae166f5c784864"},
|
||||||
|
{file = "psycopg2_binary-2.9.10-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:056470c3dc57904bbf63d6f534988bafc4e970ffd50f6271fc4ee7daad9498a5"},
|
||||||
|
{file = "psycopg2_binary-2.9.10-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:73aa0e31fa4bb82578f3a6c74a73c273367727de397a7a0f07bd83cbea696baa"},
|
||||||
|
{file = "psycopg2_binary-2.9.10-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:8de718c0e1c4b982a54b41779667242bc630b2197948405b7bd8ce16bcecac92"},
|
||||||
|
{file = "psycopg2_binary-2.9.10-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:5c370b1e4975df846b0277b4deba86419ca77dbc25047f535b0bb03d1a544d44"},
|
||||||
|
{file = "psycopg2_binary-2.9.10-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:ffe8ed017e4ed70f68b7b371d84b7d4a790368db9203dfc2d222febd3a9c8863"},
|
||||||
|
{file = "psycopg2_binary-2.9.10-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:8aecc5e80c63f7459a1a2ab2c64df952051df196294d9f739933a9f6687e86b3"},
|
||||||
|
{file = "psycopg2_binary-2.9.10-cp39-cp39-macosx_12_0_x86_64.whl", hash = "sha256:7a813c8bdbaaaab1f078014b9b0b13f5de757e2b5d9be6403639b298a04d218b"},
|
||||||
|
{file = "psycopg2_binary-2.9.10-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d00924255d7fc916ef66e4bf22f354a940c67179ad3fd7067d7a0a9c84d2fbfc"},
|
||||||
|
{file = "psycopg2_binary-2.9.10-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7559bce4b505762d737172556a4e6ea8a9998ecac1e39b5233465093e8cee697"},
|
||||||
|
{file = "psycopg2_binary-2.9.10-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e8b58f0a96e7a1e341fc894f62c1177a7c83febebb5ff9123b579418fdc8a481"},
|
||||||
|
{file = "psycopg2_binary-2.9.10-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b269105e59ac96aba877c1707c600ae55711d9dcd3fc4b5012e4af68e30c648"},
|
||||||
|
{file = "psycopg2_binary-2.9.10-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:79625966e176dc97ddabc142351e0409e28acf4660b88d1cf6adb876d20c490d"},
|
||||||
|
{file = "psycopg2_binary-2.9.10-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:8aabf1c1a04584c168984ac678a668094d831f152859d06e055288fa515e4d30"},
|
||||||
|
{file = "psycopg2_binary-2.9.10-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:19721ac03892001ee8fdd11507e6a2e01f4e37014def96379411ca99d78aeb2c"},
|
||||||
|
{file = "psycopg2_binary-2.9.10-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:7f5d859928e635fa3ce3477704acee0f667b3a3d3e4bb109f2b18d4005f38287"},
|
||||||
|
{file = "psycopg2_binary-2.9.10-cp39-cp39-win32.whl", hash = "sha256:3216ccf953b3f267691c90c6fe742e45d890d8272326b4a8b20850a03d05b7b8"},
|
||||||
|
{file = "psycopg2_binary-2.9.10-cp39-cp39-win_amd64.whl", hash = "sha256:30e34c4e97964805f715206c7b789d54a78b70f3ff19fbe590104b71c45600e5"},
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pycparser"
|
name = "pycparser"
|
||||||
version = "2.22"
|
version = "2.22"
|
||||||
@ -1178,4 +1255,4 @@ files = [
|
|||||||
[metadata]
|
[metadata]
|
||||||
lock-version = "2.0"
|
lock-version = "2.0"
|
||||||
python-versions = "^3.12"
|
python-versions = "^3.12"
|
||||||
content-hash = "0f368a19ff46c53164f16b88dee00580f42b9559212a4fa7fa71b9aad0721824"
|
content-hash = "799a180a55fe1eac994203a47c03b390b1abbd444d0fd7d5d92045b310947319"
|
||||||
|
@ -29,6 +29,7 @@ markdown-pdf = "^1.3.3"
|
|||||||
linkify-it-py = "^2.0.3"
|
linkify-it-py = "^2.0.3"
|
||||||
python-slugify = "^8.0.4"
|
python-slugify = "^8.0.4"
|
||||||
cairosvg = "^2.7.1"
|
cairosvg = "^2.7.1"
|
||||||
|
psycopg2-binary = "^2.9.10"
|
||||||
|
|
||||||
|
|
||||||
[build-system]
|
[build-system]
|
||||||
|
29
tests/postgres/compose.yml
Normal file
29
tests/postgres/compose.yml
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
services:
|
||||||
|
postgres:
|
||||||
|
container_name: learnlytics-pg
|
||||||
|
image: postgres
|
||||||
|
hostname: localhost
|
||||||
|
ports:
|
||||||
|
- "5432:5432"
|
||||||
|
environment:
|
||||||
|
POSTGRES_USER: admin
|
||||||
|
POSTGRES_PASSWORD: admin
|
||||||
|
POSTGRES_DB: learnlytics
|
||||||
|
volumes:
|
||||||
|
- postgres-data:/var/lib/postgresql/data
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
pgadmin:
|
||||||
|
container_name: learnlytics-pgadmin
|
||||||
|
image: dpage/pgadmin4
|
||||||
|
depends_on:
|
||||||
|
- postgres
|
||||||
|
ports:
|
||||||
|
- "80:80"
|
||||||
|
environment:
|
||||||
|
PGADMIN_DEFAULT_EMAIL: admin@learnlytics.de
|
||||||
|
PGADMIN_DEFAULT_PASSWORD: admin
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
postgres-data:
|
Loading…
Reference in New Issue
Block a user