diff --git a/advlabdb/modelViews.py b/advlabdb/modelViews.py index 309fc5b..114d814 100644 --- a/advlabdb/modelViews.py +++ b/advlabdb/modelViews.py @@ -3,8 +3,10 @@ from flask_admin.contrib.sqla.filters import BaseSQLAFilter from flask_admin.menu import MenuLink from flask_security import current_user, hash_password from sqlalchemy import func -from wtforms import BooleanField, SelectField, TextField, RadioField -from wtforms.validators import DataRequired, Email +from wtforms import Form, BooleanField, SelectField, TextField, RadioField, FloatField +from wtforms.validators import DataRequired, Email, Optional +from flask_admin.contrib.sqla.fields import QuerySelectMultipleField, QuerySelectField +from flask_admin.helpers import get_form_data from advlabdb import admin, app, db, user_datastore from advlabdb.configUtils import getConfig @@ -59,6 +61,8 @@ class UserModelView(SecureModelView): try: model = user_datastore.create_user(email=email, password=passwordHash, roles=roles) + + self.on_model_change(form, model, True) self.session.commit() except Exception as ex: flash(ex, "error") @@ -120,7 +124,9 @@ class SemesterModelView(SecureModelView): def create_model(self, form): try: model = Semester(label=form.semester_label.data + form.year.data) + self.session.add(model) + self.on_model_change(form, model, True) self.session.commit() except Exception as ex: flash(ex, "error") @@ -183,108 +189,65 @@ class PartModelView(SecureModelView): class StudentModelView(SecureModelView): can_view_details = True + column_list = ["student_number", "first_name", "last_name", "uni_email", "contact_email", "part_students"] column_details_list = column_list + ["bachelor_thesis", "bachelor_thesis_work_group", "note"] - column_searchable_list = ["student_number", "uni_email", "contact_email", "first_name", "last_name"] - form_columns = column_details_list + ["new_part_student_part", "new_part_student_group_number"] + + column_sortable_list = ["student_number", "first_name", "last_name"] + column_searchable_list = column_sortable_list + ["uni_email", "contact_email"] + + form_excluded_columns = ["part_students"] form_args = { "uni_email": {"validators": [Email()]}, "contact_email": {"validators": [Email()]}, } - partChoices = ["-"] + getConfig("partLabels") - form_extra_fields = { - "new_part_student_part": SelectField( - "Part", choices=list(zip(partChoices, partChoices)), default=partChoices[0] - ), - "new_part_student_group_number": TextField("Group number"), - } - def validate_form(self, form): - if request.method == "POST": - partLabel = form.new_part_student_part.data - groupNumber = form.new_part_student_group_number.data - - if (partLabel != self.partChoices[0] and groupNumber == "") or ( - partLabel == self.partChoices[0] and groupNumber != "" - ): - flash("You have to assign both part and group if you want to add a part student!", "danger") - return False - - if partLabel != self.partChoices[0] and not partFromLabelInUserActiveSemester(partLabel): - flash(f"Part {partLabel} is not created in {str(userActiveSemester())} yet!", "danger") - return False - - if groupNumber != "": - message = "The group number has to be an integer > 0 !" - - try: - groupNumber = int(groupNumber) - except Exception: - flash(message, "danger") - return False - - if groupNumber < 1: - flash(message, "danger") - return False - - return super().validate_form(form) - - def after_model_change(self, form, model, is_created): - partLabel = form.new_part_student_part.data - - if partLabel != self.partChoices[0]: - groupNumber = int(form.new_part_student_group_number.data) - - part = partFromLabelInUserActiveSemester(partLabel) - group = Group.query.filter(Group.number == groupNumber, Group.part == part).first() - - if group is None: - try: - group = Group(number=groupNumber, part=part) - self.session.add(group) - self.session.commit() - except Exception as ex: - flash(ex, "error") - - self.session.rollback() - return - else: - flash(f"Added the new group with number {str(groupNumber)} in part {str(part)}.", "success") - - try: - partStudent = PartStudent(student=model, part=part, group=group) - self.session.add(partStudent) - self.session.commit() - except Exception as ex: - flash(ex, "error") - - self.session.rollback() - else: - flash("Added part student.", "success") - - -class PartFilter(BaseSQLAFilter): - def apply(self, query, value, alias=None): - return query.filter(self.column == partFromLabelInUserActiveSemester(value).id) - - def operation(self): - return "equals" - - def validate(self, value): - if partFromLabelInUserActiveSemester(value): - return True - else: - flash(f"Part {value} not found in your active semester {userActiveSemester()}!", "danger") - return False +def partQueryFactory(): + return Part.query.filter(Part.id.in_([part.id for part in userActiveSemester().parts])) class PartStudentModelView(SecureModelView): - partLabels = getConfig("partLabels") - column_filters = [PartFilter(PartStudent.part_id, "Part", options=list(zip(partLabels, partLabels)))] + class CreateForm(Form): + def studentQueryFactory(): + return Student.query - form_excluded_columns = ["experiment_marks"] + def groupQueryFactory(): + return Group.query.filter(Group.part_id.in_([part.id for part in userActiveSemester().parts])) + + student = QuerySelectField( + "Student", query_factory=studentQueryFactory, validators=[DataRequired()], allow_blank=True, blank_text="-" + ) + part = QuerySelectField( + "Part", query_factory=partQueryFactory, validators=[DataRequired()], allow_blank=True, blank_text="-" + ) + group = QuerySelectField("Group", query_factory=groupQueryFactory, allow_blank=True, blank_text="-") + + class EditForm(CreateForm): + student = None + part = None + final_part_mark = FloatField("Final Part Mark", validators=[Optional()]) + + form = EditForm + + column_filters = ["part", "student", "group"] + + partGroupPartMismatchException = "Part and groups part don't match!" + + def create_form(self, obj=None): + form = self.CreateForm + return form(get_form_data(), obj=obj) + + def on_model_change(self, form, model, is_created): + if model.group and model.part != model.group.part: + raise Exception(self.partGroupPartMismatchException) + + def handle_view_exception(self, exc): + if exc.args[0] in (self.partGroupPartMismatchException): + pass + else: + return super().handle_view_exception(exc) def get_query(self): return super().get_query().filter(PartStudent.part_id.in_([part.id for part in userActiveSemester().parts])) @@ -298,21 +261,62 @@ class PartStudentModelView(SecureModelView): class GroupModelView(SecureModelView): - partLabels = getConfig("partLabels") - column_filters = [PartFilter(Group.part_id, "Part", options=list(zip(partLabels, partLabels)))] + class CreateForm(Form): + def partStudentsQueryFactory(): + return PartStudent.query.filter(PartStudent.part_id.in_([part.id for part in userActiveSemester().parts])) - def validate_form(self, form): - if request.method == "POST": - if Group.query.filter(Group.number == form.number.data, Group.part == form.part.data).first(): - lastTakenGroupNumber = ( - Group.query.filter(Group.part == form.part.data).order_by(Group.number)[-1].number - ) - flash( - f"Group number taken in this part! Last group number taken in this part is {lastTakenGroupNumber}.", - "warning", - ) - return False - return super().validate_form(form) + part = QuerySelectField( + label="Part", query_factory=partQueryFactory, validators=[DataRequired()], allow_blank=True, blank_text="-" + ) + part_students = QuerySelectMultipleField(label="Part Students", query_factory=partStudentsQueryFactory) + + class EditForm(CreateForm): + part = None + + form = EditForm + + column_list = ["number", "part", "part_students"] + column_filters = ["part"] + column_searchable_list = ["number"] + + partStudentPartPartMismatchException = "Part and StudentParts part don't match!" + + def create_model(self, form): + try: + orderedPartGroups = Group.query.filter(Group.part == form.part.data).order_by(Group.number) + lastTakenGroupNumber = orderedPartGroups[-1].number if orderedPartGroups.count() > 0 else 0 + + model = Group( + number=lastTakenGroupNumber + 1, + part_students=form.part_students.data, + part=form.part.data, + ) + + self.session.add(model) + self.on_model_change(form, model, True) + self.session.commit() + except Exception as ex: + flash(ex, "error") + + self.session.rollback() + else: + self.after_model_change(form, model, True) + return model + + def on_model_change(self, form, model, is_created): + for partStudent in model.part_students: + if model.part != partStudent.part: + raise Exception(self.partStudentPartPartMismatchException) + + def handle_view_exception(self, exc): + if exc.args[0] in (self.partStudentPartPartMismatchException): + pass + else: + return super().handle_view_exception(exc) + + def create_form(self, obj=None): + form = self.CreateForm + return form(get_form_data(), obj=obj) def get_query(self): return super().get_query().filter(Group.part_id.in_([part.id for part in userActiveSemester().parts])) @@ -335,7 +339,7 @@ class PartExperimentModelView(SecureModelView): column_list = ["experiment", "part", "assistants"] partLabels = getConfig("partLabels") - column_filters = [PartFilter(PartExperiment.part_id, "Part", options=list(zip(partLabels, partLabels)))] + column_filters = ["part"] def get_query(self): return super().get_query().filter(PartExperiment.part_id.in_([part.id for part in userActiveSemester().parts]))