mirror of
https://codeberg.org/Mo8it/AdvLabDB.git
synced 2024-12-02 22:33:05 +00:00
Compare commits
8 commits
26a8785d7a
...
7a6b4dcf58
Author | SHA1 | Date | |
---|---|---|---|
7a6b4dcf58 | |||
927ebbaefc | |||
4cfc616a34 | |||
99cc109cde | |||
7bbf0b861b | |||
ce98747548 | |||
b197dc8f05 | |||
8514bd6668 |
12 changed files with 205 additions and 39 deletions
3
.ignore
Normal file
3
.ignore
Normal file
|
@ -0,0 +1,3 @@
|
|||
/advlabdb/static/
|
||||
/LICENSE.txt
|
||||
/poetry.lock
|
|
@ -31,7 +31,7 @@ def backup(backup_prefix):
|
|||
flash(f"Created a database backup at the path {dest}", "success")
|
||||
else:
|
||||
flash("Failed to create a database backup!", "danger")
|
||||
except:
|
||||
except Exception:
|
||||
flash("Failed to create a database backup! Make sure that `sqlite3` is installed on your system!", "danger")
|
||||
|
||||
|
||||
|
@ -43,9 +43,9 @@ def deactivate_assistants():
|
|||
user_ids_to_deactivate = db.session.scalars(
|
||||
select(Assistant.user_id)
|
||||
.join(User)
|
||||
.where(User.active is True)
|
||||
.where(User.active == True)
|
||||
.except_(
|
||||
select(Assistant.user_id).join(Assistant.semester_experiments).join(Semester).where(Semester.done is False)
|
||||
select(Assistant.user_id).join(Assistant.semester_experiments).join(Semester).where(Semester.done == False)
|
||||
)
|
||||
)
|
||||
|
||||
|
|
|
@ -397,7 +397,7 @@ class SemesterView(SecureAdminModelView):
|
|||
|
||||
form_args = {
|
||||
"done": {
|
||||
"description": "Setting a semester as done prevents assistants from changing or even seeing marks in this semester. Setting a semester as done sets older semesters as done, too. Only set the semester as done if all marks in the semester and all previous semesters are already set."
|
||||
"description": "Setting a semester as done prevents assistants from changing or even seeing marks in this semester. Setting a semester as done sets older semesters as done, too. Only set a semester as done if all marks in that semester and all previous semesters are already set."
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -636,14 +636,14 @@ class GroupView(SecureAdminModelView):
|
|||
if is_created:
|
||||
|
||||
def query_factory():
|
||||
return partStudentQueryFactory().where(PartStudent.group is None)
|
||||
return partStudentQueryFactory().where(PartStudent.group == None)
|
||||
|
||||
else:
|
||||
|
||||
def query_factory():
|
||||
return partStudentQueryFactory().where(
|
||||
or_(
|
||||
and_(PartStudent.group is None, Part.program == group.program),
|
||||
and_(PartStudent.group == None, Part.program == group.program),
|
||||
PartStudent.group == group,
|
||||
)
|
||||
)
|
||||
|
@ -754,7 +754,7 @@ class ExperimentView(SecureAdminModelView):
|
|||
|
||||
|
||||
def assistantQueryFactory():
|
||||
return Assistant.query.join(User).where(User.active is True)
|
||||
return Assistant.query.join(User).where(User.active == True)
|
||||
|
||||
|
||||
def weighting_field(label: str, default: float):
|
||||
|
@ -776,7 +776,7 @@ class SemesterExperimentView(SecureAdminModelView):
|
|||
class CreateForm(FlaskForm):
|
||||
@staticmethod
|
||||
def experimentQueryFactory():
|
||||
return Experiment.query.where(Experiment.active is True)
|
||||
return Experiment.query.where(Experiment.active == True)
|
||||
|
||||
experiment = QuerySelectField(
|
||||
"Experiment",
|
||||
|
@ -937,7 +937,7 @@ class ExperimentRowFilter(FilterEqual):
|
|||
if not has_request_context():
|
||||
return ()
|
||||
|
||||
activeExperiments = db.session.scalars(select(Experiment).where(Experiment.active is True))
|
||||
activeExperiments = db.session.scalars(select(Experiment).where(Experiment.active == True))
|
||||
return tuple(
|
||||
(
|
||||
f"{activeExperiment.number},{activeExperiment.program_id}",
|
||||
|
@ -1193,7 +1193,7 @@ class ExperimentMarkView(SecureAdminModelView):
|
|||
if not has_request_context():
|
||||
return ()
|
||||
|
||||
admins = db.session.scalars(select(Admin).join(User).where(User.active is True))
|
||||
admins = db.session.scalars(select(Admin).join(User).where(User.active == True))
|
||||
return tuple((admin.id, str(admin)) for admin in admins)
|
||||
|
||||
def apply(self, query, value, alias=None):
|
||||
|
|
|
@ -88,7 +88,7 @@ def email_formatter(view, context, model, name: str):
|
|||
|
||||
def experiment_marks_missing_formatter(view, context, model, name: str):
|
||||
experiment_marks_missing = getattr(model, name)
|
||||
if experiment_marks_missing is True:
|
||||
if experiment_marks_missing == True:
|
||||
return Markup("<span style='color: red;'>Yes</span>")
|
||||
|
||||
return "No"
|
||||
|
|
|
@ -77,7 +77,7 @@ def mark_hists(markType, active_assistants):
|
|||
|
||||
|
||||
def assistant_marks_analysis(cls):
|
||||
active_assistants = db.session.scalars(select(Assistant).join(User).where(User.active is True)).all()
|
||||
active_assistants = db.session.scalars(select(Assistant).join(User).where(User.active == True)).all()
|
||||
|
||||
if not active_assistants:
|
||||
flash("No active assistants!", "warning")
|
||||
|
|
|
@ -211,7 +211,7 @@ class AssistantUserView(SecureAssistantModelView):
|
|||
@staticmethod
|
||||
def semesterQueryFactory():
|
||||
# Show only last two semesters to assistants
|
||||
return Semester.query.order_by(Semester.id.desc()).where(Semester.done is False).limit(2)
|
||||
return Semester.query.order_by(Semester.id.desc()).where(Semester.done == False).limit(2)
|
||||
|
||||
active_semester = QuerySelectField(
|
||||
"Active Semester",
|
||||
|
|
|
@ -44,9 +44,9 @@ class SecureAdminIndexView(CustomIndexView):
|
|||
.join(Assistant.semester_experiments)
|
||||
.where(SemesterExperiment.semester == current_user.active_semester)
|
||||
.join(SemesterExperiment.group_experiments)
|
||||
.where(GroupExperiment.experiment_marks_missing is True)
|
||||
.where(GroupExperiment.experiment_marks_missing == True)
|
||||
.join(GroupExperiment.experiment_marks)
|
||||
.where(ExperimentMark.final_experiment_mark is None)
|
||||
.where(ExperimentMark.final_experiment_mark == None)
|
||||
.group_by(Assistant.id)
|
||||
.order_by(func.count().desc())
|
||||
)
|
||||
|
@ -71,7 +71,7 @@ class SecureAssistantIndexView(CustomIndexView):
|
|||
.where(SemesterExperiment.semester == current_user.active_semester)
|
||||
.join(SemesterExperiment.assistants)
|
||||
.where(Assistant.user == current_user)
|
||||
.where(ExperimentMark.final_experiment_mark is None)
|
||||
.where(ExperimentMark.final_experiment_mark == None)
|
||||
)
|
||||
|
||||
return self.render(
|
||||
|
@ -158,7 +158,7 @@ class CustomModelView(ModelView):
|
|||
|
||||
def update_model(self, form, model):
|
||||
try:
|
||||
if self.customUpdateModel(form, model) is False:
|
||||
if self.customUpdateModel(form, model) == False:
|
||||
# Nothing changed
|
||||
return True
|
||||
|
||||
|
|
|
@ -537,7 +537,7 @@ class Semester(db.Model):
|
|||
if transferParts:
|
||||
semester.transferPartsFrom(oldSemester)
|
||||
|
||||
for experiment in db.session.scalars(select(Experiment).where(Experiment.active is True)):
|
||||
for experiment in db.session.scalars(select(Experiment).where(Experiment.active == True)):
|
||||
newSemesterExperiment = SemesterExperiment(experiment=experiment, semester=semester)
|
||||
|
||||
if transferAssistants:
|
||||
|
@ -575,7 +575,7 @@ class Semester(db.Model):
|
|||
.join(GroupExperiment)
|
||||
.join(SemesterExperiment)
|
||||
.where(SemesterExperiment.semester == self)
|
||||
.where(ExperimentMark.final_experiment_mark is None)
|
||||
.where(ExperimentMark.final_experiment_mark == None)
|
||||
)
|
||||
|
||||
def set_done(self, next_semester=None):
|
||||
|
|
|
@ -82,7 +82,7 @@
|
|||
|
||||
<p>
|
||||
Every part student has a final part mark shown in this table.
|
||||
The final part mark is calculated as explained in <a href="#experiment">Experiment</a>.
|
||||
The final part mark is calculated as explained in <a href="#semester_experiment">Semester Experiment</a>.
|
||||
Normally, this final part mark is the one relevant for the transcript of records of students.
|
||||
</p>
|
||||
|
||||
|
@ -193,40 +193,161 @@
|
|||
This mark is calculated with respect to the weights of the related <a href="#semester_experiment">semester experiment</a>.
|
||||
</p>
|
||||
|
||||
<!-- TODO
|
||||
<h4 id="experiment">Experiment</h4>
|
||||
<p></p>
|
||||
<p>This table shows all experiments, not only in your active semester.</p>
|
||||
|
||||
<p>
|
||||
Experiments that are marked as "active" will be present in new semesters.
|
||||
Make sure to active/deactivate experiments before creating a new semester.
|
||||
</p>
|
||||
|
||||
<h4 id="semester_experiment">Semester Experiment</h4>
|
||||
<p></p>
|
||||
<p>This table shows the semester experiments in your active semester.</p>
|
||||
|
||||
<p>
|
||||
A semester experiment is an <a href="#experiment">experiment</a> that takes place in a specific <a href="#semester">semester</a>.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Assistants and mark weightings are assigned to these semester experiments.
|
||||
These assignments are transferred to the next semester if the related <a href="#experiment">experiment</a> was active before creating the new semester.
|
||||
</p>
|
||||
|
||||
<p>Mark weightings consist of oral, protocol and final weightings.</p>
|
||||
|
||||
<p>
|
||||
The oral weighting and protocol weightings are between 0 and 1 and have to add up to 1.
|
||||
By default, these weightings should both be 0.5, but you can for example have an oral weighting of 0.25 and a protocol weighting of 0.75.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
The final weighting has to be a value between 0 and 1.
|
||||
By default, its value should be 1.
|
||||
But you can choose a value lower than 1 in case an experiment is easier than others.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
The final part mark for a student is calculated by summing the following expression over <code>i</code> and then dividing through the sum of <code>f_i</code>:
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<code>f_i * (ow_i * o_i + pw_i * p_i)</code>
|
||||
</p>
|
||||
|
||||
<ul>
|
||||
<li>
|
||||
<code>i</code> is the index of a semester experiment.
|
||||
The sum over <code>i</code> is done only over the semester experiments that a student has in that part.
|
||||
</li>
|
||||
<li>
|
||||
<code>f_i</code> is the final part mark for the semester experiment <code>i</code>.
|
||||
</li>
|
||||
<li>
|
||||
<code>ow_i</code> is the oral weighting.
|
||||
</li>
|
||||
<li>
|
||||
<code>o_i</code> is the oral mark.
|
||||
</li>
|
||||
<li>
|
||||
<code>pw_i</code> is the protocol weighting.
|
||||
</li>
|
||||
<li>
|
||||
<code>p_i</code> is the protocol mark.
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<p>
|
||||
The result after the division is rounded to an integer using the "round half up" strategy.
|
||||
This means that <code>x.5</code> is always rounded up to <code>x+1</code>.
|
||||
Values between <code>x.5</code> and <code>x+1</code> (closed interval) are rounded up to <code>x+1</code>.
|
||||
Values higher than <code>x</code> and lower than <code>x.5</code> are rounded down to <code>x</code>.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
In case you change the weightings during a semester (some marks are already set), all part and experiment marks will be automatically updated.
|
||||
</p>
|
||||
|
||||
<h4 id="semester">Semester</h4>
|
||||
<p></p>
|
||||
<p>This table shows all semesters.</p>
|
||||
|
||||
<p>
|
||||
Before creating a new semester, make sure to read all notes in the form that shows up after pressing the "Create" button.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
You can and should set a semester as "done" by clicking on the edit button near that semester, then clicking the "done" checkbox and then "Save".
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Setting a semester as done prevents assistants from changing or even seeing marks in this semester.
|
||||
Setting a semester as done sets older semesters as done, too.
|
||||
Only set a semester as done if all marks in that semester and all previous semesters are already set.
|
||||
</p>
|
||||
|
||||
<h4 id="part">Part</h4>
|
||||
<p></p>
|
||||
<p>This table shows the parts in your active semester.</p>
|
||||
|
||||
<p>
|
||||
A part consists of a <a href="#program">program</a>, a <a href="#semester">semester</a> and a number.
|
||||
For example, if students in the bachelor of science program (BS) must absolve two lab courses, then you would have the parts BS1 and BS2.
|
||||
But maybe the university only offers part BS2 every second semester.
|
||||
Therefore, a part is coupled to a semester.
|
||||
</p>
|
||||
|
||||
<h4 id="assistant">Assistant</h4>
|
||||
<p></p>
|
||||
<p>This table shows all assistants.</p>
|
||||
|
||||
<p>
|
||||
To deactivate an assistant, you have to deactivate their <a href="#user">user</a> account.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
You can assign <a href="#semester_experiment">semester experiments</a> to assistants either in this table or in the semester experiments table.
|
||||
</p>
|
||||
|
||||
<h4 id="admin">Admin</h4>
|
||||
<p></p>
|
||||
<p>This is a read only table that shows current admins.</p>
|
||||
|
||||
<h4 id="user">User</h4>
|
||||
<p></p>
|
||||
<p>This table shows all users.</p>
|
||||
|
||||
<p>A user can have either an admin or assistant role (or both).</p>
|
||||
|
||||
<p>
|
||||
Make sure to deactivate users if they don't require access to the database anymore.
|
||||
Deactivated users can't login anymore.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Further details can be read under <a href="#user_settings">User settings</a>.
|
||||
</p>
|
||||
|
||||
<h4 id="program">Program</h4>
|
||||
<p></p>
|
||||
<p>This table shows all programs.</p>
|
||||
|
||||
<p>
|
||||
A program is a university program.
|
||||
You can for example have the programs BS for bachelor of science, BE for bachelor of education, MS for master of science and so on.
|
||||
It is recommended to choose short labels for programs.
|
||||
</p>
|
||||
|
||||
<h3 id="import">Import</h3>
|
||||
<p></p>
|
||||
<p>
|
||||
Allows you to import data for your active semester from a file.
|
||||
Visit the importing page for more information.
|
||||
</p>
|
||||
|
||||
<h3 id="actions">Actions</h3>
|
||||
<p></p>
|
||||
<p>
|
||||
Run one or multiple actions like creating a database backup.
|
||||
The available actions should be explained on the page.
|
||||
</p>
|
||||
|
||||
<h3 id="analysis">Analysis</h3>
|
||||
<p></p>
|
||||
-->
|
||||
<p>
|
||||
Run an analysis and plot the results.
|
||||
More information on the page itself.
|
||||
</p>
|
||||
|
||||
<h3 id="docs">Docs</h3>
|
||||
<p>This is a link which leads you to this page.</p>
|
||||
|
@ -263,11 +384,51 @@
|
|||
</p>
|
||||
<p>If you forget your password, you can reset it using the CLI on the server.</p>
|
||||
|
||||
<!-- TODO
|
||||
<h2>How-Tos</h2>
|
||||
<h3>Start a new semester</h3>
|
||||
<p></p>
|
||||
-->
|
||||
<p>Here is a list of tables that one has to visit when creating a new semester:</p>
|
||||
|
||||
<ol>
|
||||
<li>
|
||||
Program: Make sure that the programs are up to date. Usually, you will only create new programs when first creating the database for the first time.
|
||||
</li>
|
||||
<li>
|
||||
User: Create a user for every new assistants.
|
||||
Old assistants should already be in the database, just make sure that their users are activated.
|
||||
</li>
|
||||
<li>
|
||||
Experiment: Create possibly new experiments.
|
||||
Old experiments should already be in the database, just make sure that they are activated.
|
||||
</li>
|
||||
<li>
|
||||
Semester: Create a new semester.
|
||||
Read the notes in the form for creating a semester.
|
||||
Possibly set old semester as done.
|
||||
</li>
|
||||
<li>
|
||||
Part: The parts from the last semester should be duplicated in the new semester (if you don't uncheck the checkbox "Transfer parts" on creating a new semester).
|
||||
You can add or remove a part from the new semester if required.
|
||||
</li>
|
||||
<li>
|
||||
Semester Experiment: Semester experiments should be duplicated in the new semester (if you don't uncheck the checkbox "Transfer experiments" on creating a new semester).
|
||||
Make sure that all are present.
|
||||
Possibly create new ones.
|
||||
Check the assigned assistants and possibly assign new ones or reassign old ones.
|
||||
</li>
|
||||
<li>
|
||||
Import: Skip the next steps if you want to use the import functionality.
|
||||
In that case, create an import file and import it.
|
||||
Check that everything is imported correctly afterwards.
|
||||
</li>
|
||||
<li>Student: Add students that are new to the database.</li>
|
||||
<li>Part Student: Add part students for all students that are doing a part in this semester.</li>
|
||||
<li>Group: Create groups for part students.</li>
|
||||
<li>
|
||||
Group Experiment: Create new group experiments to assign semester experiments to groups.
|
||||
The form allows you to directly add up to two appointments.
|
||||
</li>
|
||||
<li>Appointment: Check appointments and possibly add missing ones.</li>
|
||||
</ol>
|
||||
|
||||
<h3>Tables</h3>
|
||||
|
||||
|
|
|
@ -13,7 +13,7 @@ def _reset_admin_password(manage):
|
|||
app = create_app(create_for_server=False)
|
||||
|
||||
with app.app_context(), db.session.begin():
|
||||
admins = db.session.scalars(select(Admin).join(User).where(User.active is True)).all()
|
||||
admins = db.session.scalars(select(Admin).join(User).where(User.active == True)).all()
|
||||
activate_user = False
|
||||
|
||||
if len(admins) == 0:
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[tool.poetry]
|
||||
name = "advlabdb"
|
||||
version = "0.9.0"
|
||||
version = "1.0.0"
|
||||
description = "Database with a web interface for labs."
|
||||
authors = ["Mo Bitar <mo8it@proton.me>"]
|
||||
readme = "README.md"
|
||||
|
@ -48,7 +48,9 @@ select = [
|
|||
"RUF",
|
||||
]
|
||||
ignore = [
|
||||
"E5",
|
||||
"E501",
|
||||
"E711",
|
||||
"E712",
|
||||
"S3",
|
||||
"S6",
|
||||
"RET504",
|
||||
|
|
Loading…
Reference in a new issue