mirror of
https://codeberg.org/Mo8it/AdvLabDB.git
synced 2024-12-20 23:41:20 +00:00
Move weightings from Experiment to SemesterExperiment
This commit is contained in:
parent
4e6c2ad708
commit
de4ef9d6f4
6 changed files with 114 additions and 79 deletions
File diff suppressed because one or more lines are too long
|
@ -12,8 +12,16 @@ from flask_wtf import FlaskForm
|
||||||
from flask_wtf.file import FileAllowed, FileField, FileRequired
|
from flask_wtf.file import FileAllowed, FileField, FileRequired
|
||||||
from sqlalchemy import and_, func, or_
|
from sqlalchemy import and_, func, or_
|
||||||
from werkzeug.utils import secure_filename
|
from werkzeug.utils import secure_filename
|
||||||
from wtforms import BooleanField, Form, RadioField, SelectField, StringField
|
from wtforms import Form
|
||||||
from wtforms.fields import DateField, IntegerField
|
from wtforms.fields import (
|
||||||
|
BooleanField,
|
||||||
|
DateField,
|
||||||
|
DecimalField,
|
||||||
|
IntegerField,
|
||||||
|
RadioField,
|
||||||
|
SelectField,
|
||||||
|
StringField,
|
||||||
|
)
|
||||||
from wtforms.validators import URL, DataRequired, Email, NumberRange, Optional
|
from wtforms.validators import URL, DataRequired, Email, NumberRange, Optional
|
||||||
|
|
||||||
from advlabdb import adminSpace, app, assistantSpace, db, user_datastore
|
from advlabdb import adminSpace, app, assistantSpace, db, user_datastore
|
||||||
|
@ -523,9 +531,6 @@ class ExperimentView(SecureAdminModelView):
|
||||||
"room",
|
"room",
|
||||||
"responsibility",
|
"responsibility",
|
||||||
"duration_in_days",
|
"duration_in_days",
|
||||||
"oral_weighting",
|
|
||||||
"protocol_weighting",
|
|
||||||
"final_weighting",
|
|
||||||
"semester_experiments",
|
"semester_experiments",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -538,9 +543,6 @@ class ExperimentView(SecureAdminModelView):
|
||||||
|
|
||||||
form_args = {
|
form_args = {
|
||||||
"wiki_link": {"validators": [URL()]},
|
"wiki_link": {"validators": [URL()]},
|
||||||
"oral_weighting": {
|
|
||||||
"description": "Oral and protocol weighting have to add to 1! Both are rounded to 2 decimal digits."
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
form_extra_fields = {
|
form_extra_fields = {
|
||||||
"program": QuerySelectField(
|
"program": QuerySelectField(
|
||||||
|
@ -548,9 +550,6 @@ class ExperimentView(SecureAdminModelView):
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
def on_model_change(self, form, model, is_created):
|
|
||||||
model.checkWeightings(roundWeightings=True)
|
|
||||||
|
|
||||||
|
|
||||||
def assistantQueryFactory():
|
def assistantQueryFactory():
|
||||||
return Assistant.query.filter(Assistant.user.has(User.active == True))
|
return Assistant.query.filter(Assistant.user.has(User.active == True))
|
||||||
|
@ -569,6 +568,28 @@ class SemesterExperimentView(SecureAdminModelView):
|
||||||
blank_text="-",
|
blank_text="-",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
oral_weighting = DecimalField(
|
||||||
|
"Oral weighting",
|
||||||
|
validators=[DataRequired(), NumberRange(min=0, max=1)],
|
||||||
|
default=0.5,
|
||||||
|
description="Between 0 and 1",
|
||||||
|
places=2,
|
||||||
|
)
|
||||||
|
protocol_weighting = DecimalField(
|
||||||
|
"Protocol weighting",
|
||||||
|
validators=[DataRequired(), NumberRange(min=0, max=1)],
|
||||||
|
default=0.5,
|
||||||
|
description="Between 0 and 1. Oral and protocol weightings have to add to 1! Both are rounded to 2 decimal digits.",
|
||||||
|
places=2,
|
||||||
|
)
|
||||||
|
final_weighting = DecimalField(
|
||||||
|
"Final weighting",
|
||||||
|
validators=[DataRequired(), NumberRange(min=0, max=1)],
|
||||||
|
default=1.0,
|
||||||
|
description="Between 0 and 1",
|
||||||
|
places=2,
|
||||||
|
)
|
||||||
|
|
||||||
assistants = QuerySelectMultipleField("Assistants", query_factory=assistantQueryFactory)
|
assistants = QuerySelectMultipleField("Assistants", query_factory=assistantQueryFactory)
|
||||||
|
|
||||||
form = CreateForm
|
form = CreateForm
|
||||||
|
@ -581,6 +602,9 @@ class SemesterExperimentView(SecureAdminModelView):
|
||||||
"semester",
|
"semester",
|
||||||
]
|
]
|
||||||
column_details_list = column_list + [
|
column_details_list = column_list + [
|
||||||
|
"oral_weighting",
|
||||||
|
"protocol_weighting",
|
||||||
|
"final_weighting",
|
||||||
"group_experiments",
|
"group_experiments",
|
||||||
]
|
]
|
||||||
column_filters = [
|
column_filters = [
|
||||||
|
@ -592,9 +616,17 @@ class SemesterExperimentView(SecureAdminModelView):
|
||||||
|
|
||||||
def customCreateModel(self, form):
|
def customCreateModel(self, form):
|
||||||
return SemesterExperiment(
|
return SemesterExperiment(
|
||||||
semester=userActiveSemester(), experiment=form.experiment.data, assistants=form.assistants.data
|
semester=userActiveSemester(),
|
||||||
|
oral_weighting=form.oral_weighting.data,
|
||||||
|
protocol_weighting=form.protocol_weighting.data,
|
||||||
|
final_weighting=form.final_weighting.data,
|
||||||
|
experiment=form.experiment.data,
|
||||||
|
assistants=form.assistants.data,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def on_model_change(self, form, model, is_created):
|
||||||
|
model.checkAndRoundWeightings()
|
||||||
|
|
||||||
|
|
||||||
class AssistantView(SecureAdminModelView):
|
class AssistantView(SecureAdminModelView):
|
||||||
def assistantUserQueryFactory():
|
def assistantUserQueryFactory():
|
||||||
|
|
|
@ -5,7 +5,14 @@ from flask_security import current_user
|
||||||
from sqlalchemy import and_
|
from sqlalchemy import and_
|
||||||
|
|
||||||
from advlabdb.exceptions import DataBaseException, ModelViewException
|
from advlabdb.exceptions import DataBaseException, ModelViewException
|
||||||
from advlabdb.models import ExperimentMark, Part, PartStudent, GroupExperiment, SemesterExperiment, Assistant
|
from advlabdb.models import (
|
||||||
|
Assistant,
|
||||||
|
ExperimentMark,
|
||||||
|
GroupExperiment,
|
||||||
|
Part,
|
||||||
|
PartStudent,
|
||||||
|
SemesterExperiment,
|
||||||
|
)
|
||||||
from advlabdb.utils import reportBadAttempt, userActiveSemester
|
from advlabdb.utils import reportBadAttempt, userActiveSemester
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -85,21 +85,19 @@ class PartStudent(db.Model):
|
||||||
groupExperiments = []
|
groupExperiments = []
|
||||||
|
|
||||||
for experimentMark in self.experiment_marks:
|
for experimentMark in self.experiment_marks:
|
||||||
if not (experimentMark.oral_mark and experimentMark.protocol_mark):
|
if None in (experimentMark.oral_mark, experimentMark.protocol_mark):
|
||||||
|
# Not all marks are set!
|
||||||
return
|
return
|
||||||
|
|
||||||
groupExperiment = experimentMark.group_experiment
|
groupExperiment = experimentMark.group_experiment
|
||||||
groupExperiments.append(groupExperiment)
|
groupExperiments.append(groupExperiment)
|
||||||
|
|
||||||
experiment = groupExperiment.semester_experiment.experiment
|
semesterExperiment = groupExperiment.semester_experiment
|
||||||
|
|
||||||
finalWeighting = experiment.final_weighting
|
finalWeighting = semesterExperiment.final_weighting
|
||||||
finalWeightingSum += finalWeighting
|
finalWeightingSum += finalWeighting
|
||||||
|
|
||||||
finalMark += finalWeighting * (
|
finalMark += finalWeighting * experimentMark.final_experiment_mark
|
||||||
experiment.oral_weighting * experimentMark.oral_mark
|
|
||||||
+ experiment.protocol_weighting * experimentMark.protocol_mark
|
|
||||||
)
|
|
||||||
|
|
||||||
if set(groupExperiments) != set(self.group.group_experiments):
|
if set(groupExperiments) != set(self.group.group_experiments):
|
||||||
flash(f"{self} does not have an experiment mark for every group experiment in his group!", "warning")
|
flash(f"{self} does not have an experiment mark for every group experiment in his group!", "warning")
|
||||||
|
@ -114,7 +112,7 @@ class PartStudent(db.Model):
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
flash(str(ex), "error")
|
flash(str(ex), "error")
|
||||||
|
|
||||||
self.session.rollback()
|
db.session.rollback()
|
||||||
else:
|
else:
|
||||||
if current_user.has_role("admin"):
|
if current_user.has_role("admin"):
|
||||||
if oldFinalPartMark != self.final_part_mark:
|
if oldFinalPartMark != self.final_part_mark:
|
||||||
|
@ -205,11 +203,7 @@ class GroupExperiment(db.Model):
|
||||||
student = partStudent.student
|
student = partStudent.student
|
||||||
for partStudent in student.part_students:
|
for partStudent in student.part_students:
|
||||||
for experimentMark in partStudent.experiment_marks:
|
for experimentMark in partStudent.experiment_marks:
|
||||||
if (
|
if experimentMark.group_experiment.semester_experiment.experiment == semester_experiment.experiment:
|
||||||
(experimentMark.oral_mark or experimentMark.protocol_mark)
|
|
||||||
and experimentMark.group_experiment.semester_experiment.experiment
|
|
||||||
== semester_experiment.experiment
|
|
||||||
):
|
|
||||||
raise Exception(
|
raise Exception(
|
||||||
f"{student} has already done {semester_experiment.experiment} in {partStudent.part} and had {experimentMark}!"
|
f"{student} has already done {semester_experiment.experiment} in {partStudent.part} and had {experimentMark}!"
|
||||||
)
|
)
|
||||||
|
@ -240,45 +234,12 @@ class Experiment(db.Model):
|
||||||
duration_in_days = db.Column(db.Integer, db.CheckConstraint("duration_in_days > 0"), nullable=False)
|
duration_in_days = db.Column(db.Integer, db.CheckConstraint("duration_in_days > 0"), nullable=False)
|
||||||
active = db.Column(db.Boolean, default=True, nullable=False)
|
active = db.Column(db.Boolean, default=True, nullable=False)
|
||||||
|
|
||||||
oral_weighting = db.Column(
|
|
||||||
db.Float,
|
|
||||||
db.CheckConstraint("oral_weighting >= 0"),
|
|
||||||
db.CheckConstraint("oral_weighting <= 1"),
|
|
||||||
default=0.5,
|
|
||||||
nullable=False,
|
|
||||||
)
|
|
||||||
protocol_weighting = db.Column(
|
|
||||||
db.Float,
|
|
||||||
db.CheckConstraint("protocol_weighting >= 0"),
|
|
||||||
db.CheckConstraint("protocol_weighting <= 1"),
|
|
||||||
default=0.5,
|
|
||||||
nullable=False,
|
|
||||||
)
|
|
||||||
final_weighting = db.Column(
|
|
||||||
db.Float, db.CheckConstraint("final_weighting >= 0"), db.CheckConstraint("final_weighting <= 1"), nullable=False
|
|
||||||
)
|
|
||||||
|
|
||||||
program_id = db.Column(db.Integer, db.ForeignKey("program.id"), nullable=False)
|
program_id = db.Column(db.Integer, db.ForeignKey("program.id"), nullable=False)
|
||||||
|
|
||||||
semester_experiments = db.relationship("SemesterExperiment", backref="experiment", lazy=True)
|
semester_experiments = db.relationship("SemesterExperiment", backref="experiment", lazy=True)
|
||||||
|
|
||||||
__table_args__ = (db.UniqueConstraint(number, program_id),)
|
__table_args__ = (db.UniqueConstraint(number, program_id),)
|
||||||
|
|
||||||
def checkWeightings(self, roundWeightings=False):
|
|
||||||
roundedOralWeighting = round(self.oral_weighting, 2)
|
|
||||||
roundedProtocolWeighting = round(self.protocol_weighting, 2)
|
|
||||||
|
|
||||||
weightingSum = round(roundedOralWeighting + roundedProtocolWeighting, 2)
|
|
||||||
|
|
||||||
if weightingSum != 1:
|
|
||||||
raise DataBaseException(
|
|
||||||
f"Oral and protocol weighting (rounded to 2 decimal digits) sum to {weightingSum} and not 1.00!"
|
|
||||||
)
|
|
||||||
|
|
||||||
if roundWeightings:
|
|
||||||
self.oral_weighting = roundedOralWeighting
|
|
||||||
self.protocol_weighting = roundedProtocolWeighting
|
|
||||||
|
|
||||||
def repr(self):
|
def repr(self):
|
||||||
return f"{self.number} {self.program.repr()}"
|
return f"{self.number} {self.program.repr()}"
|
||||||
|
|
||||||
|
@ -297,6 +258,27 @@ experiment_assistant = db.Table(
|
||||||
class SemesterExperiment(db.Model):
|
class SemesterExperiment(db.Model):
|
||||||
# An experiment in a specific semester
|
# An experiment in a specific semester
|
||||||
id = db.Column(db.Integer, primary_key=True)
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
|
oral_weighting = db.Column(
|
||||||
|
db.Float,
|
||||||
|
db.CheckConstraint("oral_weighting >= 0"),
|
||||||
|
db.CheckConstraint("oral_weighting <= 1"),
|
||||||
|
default=0.5,
|
||||||
|
nullable=False,
|
||||||
|
)
|
||||||
|
protocol_weighting = db.Column(
|
||||||
|
db.Float,
|
||||||
|
db.CheckConstraint("protocol_weighting >= 0"),
|
||||||
|
db.CheckConstraint("protocol_weighting <= 1"),
|
||||||
|
default=0.5,
|
||||||
|
nullable=False,
|
||||||
|
)
|
||||||
|
final_weighting = db.Column(
|
||||||
|
db.Float,
|
||||||
|
db.CheckConstraint("final_weighting >= 0"),
|
||||||
|
db.CheckConstraint("final_weighting <= 1"),
|
||||||
|
default=1.0,
|
||||||
|
nullable=False,
|
||||||
|
)
|
||||||
|
|
||||||
experiment_id = db.Column(db.Integer, db.ForeignKey("experiment.id"), nullable=False)
|
experiment_id = db.Column(db.Integer, db.ForeignKey("experiment.id"), nullable=False)
|
||||||
semester_id = db.Column(db.Integer, db.ForeignKey("semester.id"), nullable=False)
|
semester_id = db.Column(db.Integer, db.ForeignKey("semester.id"), nullable=False)
|
||||||
|
@ -308,6 +290,20 @@ class SemesterExperiment(db.Model):
|
||||||
|
|
||||||
__table_args__ = (db.UniqueConstraint(experiment_id, semester_id),)
|
__table_args__ = (db.UniqueConstraint(experiment_id, semester_id),)
|
||||||
|
|
||||||
|
def checkAndRoundWeightings(self):
|
||||||
|
roundedOralWeighting = round(self.oral_weighting, 2)
|
||||||
|
roundedProtocolWeighting = round(self.protocol_weighting, 2)
|
||||||
|
|
||||||
|
weightingSum = round(roundedOralWeighting + roundedProtocolWeighting, 2)
|
||||||
|
|
||||||
|
if weightingSum != 1:
|
||||||
|
raise DataBaseException(
|
||||||
|
f"Oral and protocol weightings (rounded to 2 decimal digits) sum to {weightingSum} and not 1.00!"
|
||||||
|
)
|
||||||
|
|
||||||
|
self.oral_weighting = roundedOralWeighting
|
||||||
|
self.protocol_weighting = roundedProtocolWeighting
|
||||||
|
|
||||||
def repr(self):
|
def repr(self):
|
||||||
return f"{self.experiment.repr()} {self.semester.repr()}"
|
return f"{self.experiment.repr()} {self.semester.repr()}"
|
||||||
|
|
||||||
|
@ -456,16 +452,22 @@ class ExperimentMark(db.Model):
|
||||||
|
|
||||||
experimentMark = ExperimentMark.query.get(params["experiment_mark_id"])
|
experimentMark = ExperimentMark.query.get(params["experiment_mark_id"])
|
||||||
|
|
||||||
experiment = experimentMark.group_experiment.semester_experiment.experiment
|
oral_mark = params.get("oral_mark")
|
||||||
|
if oral_mark is None:
|
||||||
oral_mark = params.get("oral_mark") or experimentMark.oral_mark
|
oral_mark = experimentMark.oral_mark
|
||||||
protocol_mark = params.get("protocol_mark") or experimentMark.protocol_mark
|
if oral_mark is None:
|
||||||
|
|
||||||
if not (oral_mark and protocol_mark):
|
|
||||||
return
|
return
|
||||||
else:
|
|
||||||
|
protocol_mark = params.get("protocol_mark")
|
||||||
|
if protocol_mark is None:
|
||||||
|
protocol_mark = experimentMark.protocol_mark
|
||||||
|
if protocol_mark is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
semesterExperiment = experimentMark.group_experiment.semester_experiment
|
||||||
|
|
||||||
return roundHalfUpToInt(
|
return roundHalfUpToInt(
|
||||||
experiment.oral_weighting * oral_mark + experiment.protocol_weighting * protocol_mark
|
semesterExperiment.oral_weighting * oral_mark + semesterExperiment.protocol_weighting * protocol_mark
|
||||||
)
|
)
|
||||||
|
|
||||||
id = db.Column(db.Integer, primary_key=True)
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
|
@ -491,7 +493,7 @@ class ExperimentMark(db.Model):
|
||||||
nullable=True,
|
nullable=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
edited_by_admin = db.Column(db.Boolean, default=False, nullable=False)
|
edited_by_admin = db.Column(db.Boolean, default=False, nullable=False) # TODO: Admin id
|
||||||
|
|
||||||
part_student_id = db.Column(db.Integer, db.ForeignKey("part_student.id"), nullable=False)
|
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)
|
group_experiment_id = db.Column(db.Integer, db.ForeignKey("group_experiment.id"), nullable=False)
|
||||||
|
|
6
poetry.lock
generated
6
poetry.lock
generated
|
@ -327,7 +327,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rope"
|
name = "rope"
|
||||||
version = "0.22.0"
|
version = "0.23.0"
|
||||||
description = "a python refactoring library..."
|
description = "a python refactoring library..."
|
||||||
category = "dev"
|
category = "dev"
|
||||||
optional = false
|
optional = false
|
||||||
|
@ -625,8 +625,8 @@ pyflakes = [
|
||||||
{file = "pyflakes-2.4.0.tar.gz", hash = "sha256:05a85c2872edf37a4ed30b0cce2f6093e1d0581f8c19d7393122da7e25b2b24c"},
|
{file = "pyflakes-2.4.0.tar.gz", hash = "sha256:05a85c2872edf37a4ed30b0cce2f6093e1d0581f8c19d7393122da7e25b2b24c"},
|
||||||
]
|
]
|
||||||
rope = [
|
rope = [
|
||||||
{file = "rope-0.22.0-py3-none-any.whl", hash = "sha256:2847220bf72ead09b5abe72b1edc9cacff90ab93663ece06913fc97324167870"},
|
{file = "rope-0.23.0-py3-none-any.whl", hash = "sha256:edf2ed3c9b35a8814752ffd3ea55b293c791e5087e252461de898e953cf9c146"},
|
||||||
{file = "rope-0.22.0.tar.gz", hash = "sha256:b00fbc064a26fc62d7220578a27fd639b2fad57213663cc396c137e92d73f10f"},
|
{file = "rope-0.23.0.tar.gz", hash = "sha256:f87662c565086d660fc855cc07f37820267876634c3e9e51bddb32ff51547268"},
|
||||||
]
|
]
|
||||||
sqlalchemy = [
|
sqlalchemy = [
|
||||||
{file = "SQLAlchemy-1.4.31-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:c3abc34fed19fdeaead0ced8cf56dd121f08198008c033596aa6aae7cc58f59f"},
|
{file = "SQLAlchemy-1.4.31-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:c3abc34fed19fdeaead0ced8cf56dd121f08198008c033596aa6aae7cc58f59f"},
|
||||||
|
|
|
@ -68,9 +68,6 @@ with app.app_context():
|
||||||
building="phy",
|
building="phy",
|
||||||
responsibility="none",
|
responsibility="none",
|
||||||
duration_in_days=2,
|
duration_in_days=2,
|
||||||
oral_weighting=0.5,
|
|
||||||
protocol_weighting=0.5,
|
|
||||||
final_weighting=1,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
ex2 = Experiment(
|
ex2 = Experiment(
|
||||||
|
@ -81,9 +78,6 @@ with app.app_context():
|
||||||
building="phy",
|
building="phy",
|
||||||
responsibility="none",
|
responsibility="none",
|
||||||
duration_in_days=2,
|
duration_in_days=2,
|
||||||
oral_weighting=0.5,
|
|
||||||
protocol_weighting=0.5,
|
|
||||||
final_weighting=1,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
db.session.add(ex1)
|
db.session.add(ex1)
|
||||||
|
|
Loading…
Reference in a new issue