1
0
Fork 0
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:
Mo 2022-02-27 17:43:31 +01:00
parent 4e6c2ad708
commit de4ef9d6f4
6 changed files with 114 additions and 79 deletions

File diff suppressed because one or more lines are too long

View file

@ -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():

View file

@ -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

View file

@ -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
View file

@ -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"},

View file

@ -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)