diff --git a/DB.drawio b/DB.drawio
index eaabc3a..4cfcca3 100644
--- a/DB.drawio
+++ b/DB.drawio
@@ -1 +1 @@
-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
\ No newline at end of file
+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
\ No newline at end of file
diff --git a/advlabdb/modelViews.py b/advlabdb/modelViews.py
index 97bc7a9..35acdd5 100644
--- a/advlabdb/modelViews.py
+++ b/advlabdb/modelViews.py
@@ -4,7 +4,7 @@ from flask_admin.menu import MenuLink
from flask_admin.model.template import EndpointLinkRowAction
from flask_security import current_user, hash_password
from sqlalchemy import func
-from wtforms import Form, BooleanField, SelectField, TextField, RadioField, FloatField
+from wtforms import Form, BooleanField, SelectField, TextField, RadioField, IntegerField
from wtforms.validators import DataRequired, Email, Optional
from flask_admin.contrib.sqla.fields import QuerySelectMultipleField, QuerySelectField
from flask_admin.helpers import get_form_data
@@ -37,7 +37,7 @@ from advlabdb.utils import (
class UserView(SecureModelView):
- column_list = ["email", "active", "roles", "assistant"]
+ column_list = ["email", "active", "roles", "assistant", "active_semester"]
column_searchable_list = ["email"]
column_filters = ["active"]
form_columns = ["email", "active", "roles"]
@@ -68,7 +68,7 @@ class UserView(SecureModelView):
self.on_model_change(form, model, True)
self.session.commit()
except Exception as ex:
- flash(ex, "error")
+ flash(str(ex), "error")
self.session.rollback()
else:
@@ -106,9 +106,10 @@ class RoleView(SecureModelView):
class SemesterView(SecureModelView):
can_edit = False
can_delete = False
- column_display_actions = False
+ can_view_details = True
column_list = ["label", "parts"]
+ column_details_list = column_list + ["active_users"]
form_columns = ["semester_label", "year", "transfer_parts", "transfer_assistants"]
semesterLabels = ["WS", "SS"]
@@ -137,7 +138,7 @@ class SemesterView(SecureModelView):
self.on_model_change(form, model, True)
self.session.commit()
except Exception as ex:
- flash(ex, "error")
+ flash(str(ex), "error")
self.session.rollback()
else:
@@ -177,7 +178,7 @@ class SemesterView(SecureModelView):
self.session.commit()
except Exception as ex:
- flash(ex, "error")
+ flash(str(ex), "error")
self.session.rollback()
@@ -248,7 +249,7 @@ class PartStudentView(SecureModelView):
class EditForm(CreateForm):
student = None
part = None
- final_part_mark = FloatField("Final Part Mark", validators=[Optional()])
+ final_part_mark = IntegerField("Final Part Mark", validators=[Optional()])
form = EditForm
@@ -316,7 +317,7 @@ class GroupView(SecureModelView):
self.on_model_change(form, model, True)
self.session.commit()
except Exception as ex:
- flash(ex, "error")
+ flash(str(ex), "error")
self.session.rollback()
else:
@@ -481,7 +482,7 @@ class GroupExperimentView(SecureModelView):
self.on_model_change(form, model, True)
self.session.commit()
except Exception as ex:
- flash(ex, "error")
+ flash(str(ex), "error")
self.session.rollback()
else:
diff --git a/advlabdb/models.py b/advlabdb/models.py
index f5e78c3..f141ff0 100644
--- a/advlabdb/models.py
+++ b/advlabdb/models.py
@@ -17,7 +17,7 @@ from advlabdb.configUtils import getConfig
class Student(db.Model):
id = db.Column(db.Integer, primary_key=True)
- student_number = db.Column(db.Integer, nullable=False, unique=True)
+ student_number = db.Column(db.Integer, db.CheckConstraint("student_number > -1"), nullable=False, unique=True)
first_name = db.Column(db.String(100), nullable=False)
last_name = db.Column(db.String(100), nullable=False)
uni_email = db.Column(db.String(200), nullable=False, unique=True)
@@ -37,7 +37,12 @@ class Student(db.Model):
class PartStudent(db.Model):
# A student doing a specific part
id = db.Column(db.Integer, primary_key=True)
- final_part_mark = db.Column(db.Integer, nullable=True)
+ final_part_mark = db.Column(
+ db.Integer,
+ db.CheckConstraint("final_part_mark > -1"),
+ db.CheckConstraint("final_part_mark < 16"),
+ nullable=True,
+ )
student_id = db.Column(db.Integer, db.ForeignKey("student.id"), nullable=False)
part_id = db.Column(db.Integer, db.ForeignKey("part.id"), nullable=False)
group_id = db.Column(db.Integer, db.ForeignKey("group.id"), nullable=True)
@@ -57,7 +62,7 @@ class PartStudent(db.Model):
class Group(db.Model):
id = db.Column(db.Integer, primary_key=True)
- number = db.Column(db.Integer, nullable=False)
+ number = db.Column(db.Integer, db.CheckConstraint("number > 0"), nullable=False)
part_id = db.Column(db.Integer, db.ForeignKey("part.id"), nullable=False)
part_students = db.relationship("PartStudent", backref="group", lazy=True)
group_experiments = db.relationship("GroupExperiment", backref="group", lazy=True)
@@ -109,7 +114,7 @@ class Experiment(db.Model):
room = db.Column(db.String(100), nullable=False)
building = db.Column(db.String(100), nullable=False)
responsibility = db.Column(db.String(200), nullable=True)
- duration_in_days = db.Column(db.Integer, nullable=False)
+ duration_in_days = db.Column(db.Integer, db.CheckConstraint("duration_in_days > -1"), nullable=False)
active = db.Column(db.Boolean, nullable=False, default=True)
oral_weighting = db.Column(db.Float, nullable=False)
protocol_weighting = db.Column(db.Float, nullable=False)
@@ -204,6 +209,7 @@ class Semester(db.Model):
label = db.Column(db.String(100), nullable=False, unique=True) # WS2122 for example
parts = db.relationship("Part", backref="semester", lazy=True)
semester_experiments = db.relationship("SemesterExperiment", backref="semester", lazy=True)
+ active_users = db.relationship("User", backref="active_semester", lazy=True)
def repr(self):
return f"{self.label}"
@@ -224,8 +230,12 @@ class Semester(db.Model):
class ExperimentMark(db.Model):
# A mark for a student after a specific experiment
id = db.Column(db.Integer, primary_key=True)
- oral_mark = db.Column(db.Integer, nullable=True)
- protocol_mark = db.Column(db.Integer, nullable=True)
+ oral_mark = db.Column(
+ db.Integer, db.CheckConstraint("oral_mark > -1"), db.CheckConstraint("oral_mark < 16"), nullable=True
+ )
+ protocol_mark = db.Column(
+ db.Integer, db.CheckConstraint("protocol_mark > -1"), db.CheckConstraint("protocol_mark < 16"), nullable=True
+ )
part_student_id = db.Column(db.Integer, db.ForeignKey("part_student.id"), nullable=False)
group_experiment_id = db.Column(db.Integer, db.ForeignKey("group_experiment.id"), nullable=False)
assistant_id = db.Column(
@@ -243,7 +253,7 @@ class ExperimentMark(db.Model):
class User(db.Model, FsUserMixin):
assistant = db.relationship("Assistant", backref="user", lazy=True, uselist=False)
- active_semester_id = db.Column(db.Integer, nullable=True)
+ active_semester_id = db.Column(db.Integer, db.ForeignKey("semester.id"), nullable=True)
def repr(self):
return f"{self.email}"