1
0
Fork 0
mirror of https://codeberg.org/Mo8it/AdvLabDB.git synced 2024-11-12 21:40:41 +00:00
AdvLabDB/advlabdb/adminModelViews.py

1432 lines
44 KiB
Python
Raw Normal View History

2022-02-13 18:58:05 +00:00
from pathlib import Path
2022-09-24 14:18:12 +00:00
from flask import flash, has_request_context, redirect
2022-08-09 12:46:48 +00:00
from flask_admin import Admin as FlaskAdmin
2022-02-13 18:58:05 +00:00
from flask_admin import expose
2021-07-30 12:20:54 +00:00
from flask_admin.contrib.sqla.fields import QuerySelectField, QuerySelectMultipleField
2022-05-09 00:13:18 +00:00
from flask_admin.contrib.sqla.filters import BooleanEqualFilter, FilterEqual
2021-07-30 12:20:54 +00:00
from flask_admin.helpers import get_form_data
2021-06-02 21:43:41 +00:00
from flask_admin.menu import MenuLink
2022-08-15 20:22:36 +00:00
from flask_login import current_user
from flask_security.changeable import admin_change_password
from flask_security.utils import hash_password
2022-02-13 18:58:05 +00:00
from flask_wtf import FlaskForm
from flask_wtf.file import FileAllowed, FileField, FileRequired
2022-05-21 19:14:10 +00:00
from sqlalchemy import and_, not_, or_, select
2022-02-13 18:58:05 +00:00
from werkzeug.utils import secure_filename
from wtforms.fields import (
BooleanField,
DateField,
2022-04-04 16:56:16 +00:00
DecimalField,
IntegerField,
RadioField,
StringField,
2022-04-10 19:01:42 +00:00
SubmitField,
2022-06-17 17:15:38 +00:00
TextAreaField,
)
from wtforms.validators import URL, DataRequired, Email, NumberRange, Optional
2022-04-04 16:56:16 +00:00
from wtforms.widgets import NumberInput
2022-09-07 22:33:00 +00:00
from . import data_dir, user_datastore
2023-11-02 17:47:52 +00:00
from .actions import (
deactivate_assistants,
manual_backup,
update_final_experiment_and_part_marks,
)
2022-06-30 02:08:21 +00:00
from .admin_link_formatters import (
admin_formatter,
appointment_date_formatter,
appointment_formatter,
assistant_formatter,
experiment_formatter,
experiment_mark_formatter,
group_experiment_formatter,
group_formatter,
part_formatter,
part_student_formatter,
part_student_part_formatter,
2022-06-30 02:08:21 +00:00
part_with_semester_formatter,
program_formatter,
semester_experiment_formatter,
semester_experiment_with_semester_formatter,
semester_formatter,
student_formatter,
user_formatter,
2022-03-04 02:49:02 +00:00
)
from .advlabdb_independent_funs import (
2022-07-01 16:50:39 +00:00
deep_getattr,
email_formatter,
experiment_marks_missing_formatter,
flashRandomPassword,
2022-07-01 16:50:39 +00:00
str_without_semester_formatter,
)
2022-09-24 17:29:56 +00:00
from .analysis import assistant_marks_analysis, final_part_marks_analysis
2022-08-09 12:46:48 +00:00
from .custom_classes import (
SecureAdminBaseView,
SecureAdminIndexView,
SecureAdminModelView,
)
2022-05-08 19:26:25 +00:00
from .database_import import importFromFile
2022-07-03 15:11:33 +00:00
from .exceptions import ModelViewException
2022-06-17 18:56:14 +00:00
from .model_dependent_funs import (
generate_new_password_field,
mark_field,
user_info_fields,
)
2022-05-29 17:03:54 +00:00
from .model_independent_funs import randomPassword
2022-05-08 19:26:25 +00:00
from .models import (
2022-04-11 23:52:01 +00:00
MAX_MARK,
2022-04-18 16:37:21 +00:00
MAX_YEAR,
2022-04-11 23:52:01 +00:00
MIN_MARK,
2022-04-18 16:37:21 +00:00
MIN_YEAR,
2022-03-04 02:49:02 +00:00
Admin,
2021-06-02 21:43:41 +00:00
Appointment,
Assistant,
Experiment,
ExperimentMark,
Group,
GroupExperiment,
Part,
PartStudent,
2021-07-30 12:20:54 +00:00
Program,
2021-06-02 21:43:41 +00:00
Role,
Semester,
2021-07-30 12:20:54 +00:00
SemesterExperiment,
2021-06-02 21:43:41 +00:00
Student,
User,
2022-08-09 12:46:48 +00:00
db,
)
adminSpace = FlaskAdmin(
name="Admin@AdvLabDB",
url="/admin",
template_mode="bootstrap4",
index_view=SecureAdminIndexView(name="Home", url="/admin", endpoint="admin"),
2021-06-02 21:43:41 +00:00
)
2021-08-15 23:15:19 +00:00
def semesterExperimentQueryFactory():
2022-05-21 19:04:06 +00:00
return SemesterExperiment.query.where(SemesterExperiment.semester == current_user.active_semester)
2021-08-15 23:15:19 +00:00
2022-05-07 00:09:37 +00:00
class SemesterRowFilter(FilterEqual):
def get_options(self, view):
2022-05-07 21:53:47 +00:00
if not has_request_context():
2023-11-02 18:01:07 +00:00
return ()
2022-05-07 21:53:47 +00:00
2022-05-29 19:47:23 +00:00
semesters = Semester.sortedSemestersStartingWithNewest()
2022-06-01 21:02:17 +00:00
return tuple((semester.id, str(semester)) for semester in semesters)
2022-03-03 03:26:05 +00:00
2021-07-30 00:03:44 +00:00
class UserView(SecureAdminModelView):
2022-05-07 00:09:37 +00:00
class SemesterFilter(SemesterRowFilter):
2022-03-03 03:26:05 +00:00
def apply(self, query, value, alias=None):
2022-05-21 19:19:26 +00:00
return query.where(User.active_semester_id == int(value))
2022-03-03 03:26:05 +00:00
2022-06-19 22:39:31 +00:00
class CreateForm(FlaskForm):
2022-07-02 22:48:05 +00:00
@staticmethod
2021-07-29 18:55:22 +00:00
def roleQueryFactory():
return Role.query
2022-07-02 22:48:05 +00:00
@staticmethod
2021-07-29 18:55:22 +00:00
def semesterQueryFactory():
return Semester.query.order_by(Semester.id.desc())
2021-07-29 18:55:22 +00:00
2022-07-02 22:48:05 +00:00
@staticmethod
2022-05-07 19:05:16 +00:00
def default_roles():
return [user_datastore.find_role("assistant")]
2022-09-11 12:55:53 +00:00
active_semester = QuerySelectField(
"Active Semester",
query_factory=semesterQueryFactory,
validators=[DataRequired()],
default=Semester.lastSemester,
description="Not fixed and users (including assistants) can change it.",
)
2022-05-07 19:05:16 +00:00
email = StringField(
"Email",
validators=[DataRequired(), Email()],
)
2021-07-29 18:55:22 +00:00
roles = QuerySelectMultipleField(
"Roles",
query_factory=roleQueryFactory,
validators=[DataRequired()],
2022-05-07 19:05:16 +00:00
default=default_roles,
2021-07-29 18:55:22 +00:00
)
2021-08-15 23:15:19 +00:00
2022-05-07 19:05:16 +00:00
first_name = StringField(
"First Name",
validators=[DataRequired()],
)
last_name = StringField(
"Last Name",
validators=[DataRequired()],
)
2022-06-17 18:56:14 +00:00
phone_number, mobile_phone_number, building, room = user_info_fields()
2021-08-15 23:15:19 +00:00
semester_experiments = QuerySelectMultipleField(
"Semester Experiments",
query_factory=semesterExperimentQueryFactory,
allow_blank=True,
blank_text="-",
2022-06-17 17:15:38 +00:00
description="Only needed if the user has the assistant role.",
2021-08-15 23:15:19 +00:00
)
2022-05-07 19:05:16 +00:00
active = BooleanField(
"Active",
default=True,
)
2021-07-29 18:55:22 +00:00
class EditForm(CreateForm):
2021-08-15 23:15:19 +00:00
semester_experiments = None
2022-06-17 18:56:14 +00:00
generate_new_password = generate_new_password_field()
2021-07-29 18:55:22 +00:00
2021-11-30 00:41:18 +00:00
column_list = [
"first_name",
"last_name",
"email",
"active",
"roles",
"active_semester",
2022-05-29 17:42:36 +00:00
"current_login_at",
2021-11-30 00:41:18 +00:00
]
2023-11-02 19:10:06 +00:00
column_details_list = [
*column_list,
2021-08-15 23:15:19 +00:00
"phone_number",
"mobile_phone_number",
"building",
2022-02-23 19:36:29 +00:00
"room",
2021-08-15 23:15:19 +00:00
"create_datetime",
"update_datetime",
]
2021-11-30 00:41:18 +00:00
column_searchable_list = [
"first_name",
"last_name",
"email",
]
2022-05-06 23:44:39 +00:00
2022-03-02 00:56:42 +00:00
column_filters = (
2022-03-03 03:26:05 +00:00
SemesterFilter(User, "Active Semester"),
2021-11-30 00:41:18 +00:00
"active",
2022-03-02 00:56:42 +00:00
)
2022-05-06 23:44:39 +00:00
refreshFiltersCache = True
2021-11-30 00:41:18 +00:00
column_editable_list = [
"active",
]
2021-04-27 21:28:47 +00:00
2022-06-30 02:08:21 +00:00
column_formatters = {
"email": email_formatter,
"active_semester": semester_formatter,
}
2022-05-17 00:31:09 +00:00
_skip_session_addition_on_model_creation = True
2022-05-17 00:31:09 +00:00
def customCreateModel(self, form):
password = randomPassword()
hashedPassword = hash_password(password)
email = form.email.data.lower()
roles = [role.name for role in form.roles.data]
model = user_datastore.create_user(
email=email,
password=hashedPassword,
roles=roles,
first_name=form.first_name.data,
last_name=form.last_name.data,
phone_number=form.phone_number.data,
mobile_phone_number=form.mobile_phone_number.data,
building=form.building.data,
room=form.room.data,
active=form.active.data,
active_semester=form.active_semester.data,
)
2021-07-29 18:55:22 +00:00
2022-06-28 15:01:42 +00:00
flashRandomPassword(model.email, password)
2021-07-29 18:55:22 +00:00
2022-05-17 00:31:09 +00:00
return model
2021-06-09 00:22:37 +00:00
def on_model_delete(self, model):
if model == current_user:
raise ModelViewException("Tried to delete yourself as user!")
2021-06-09 00:22:37 +00:00
def on_model_change(self, form, model, is_created):
2021-07-29 22:24:10 +00:00
if not is_created:
2021-11-30 00:41:18 +00:00
if model == current_user:
# Prevent locking out
2021-11-30 00:41:18 +00:00
if not form.active.data:
2022-02-13 18:58:05 +00:00
raise ModelViewException("Tried to deactivate yourself as user!")
2021-11-30 00:41:18 +00:00
if not model.has_role("admin"):
raise ModelViewException("Tried to remove your admin role!")
2021-06-09 00:22:37 +00:00
2021-07-30 13:14:35 +00:00
if hasattr(form, "generate_new_password") and form.generate_new_password.data:
2021-07-29 22:24:10 +00:00
password = randomPassword()
2022-06-28 15:01:42 +00:00
flashRandomPassword(model.email, password)
2021-07-29 18:55:22 +00:00
2021-07-29 22:24:10 +00:00
admin_change_password(
model, password, notify=False
2022-02-23 19:36:29 +00:00
) # Password is automatically hashed with this function
2021-07-29 18:55:22 +00:00
user_assistant = model.assistant
if model.has_role("assistant"):
if user_assistant is None:
# Create assistant instance after new role assignment
semester_experiments = form.semester_experiments.data if form.semester_experiments else []
assistant = Assistant(user=model, semester_experiments=semester_experiments)
self.session.add(assistant)
elif (
user_assistant is not None
and not user_assistant.semester_experiments
and not user_assistant.appointments
and not user_assistant.experiment_marks
):
# Delete assistant instance if there is no dependency
# Useful for undoing an unwanted role assignment
self.session.delete(user_assistant)
user_admin = model.admin
if model.has_role("admin"):
if user_admin is None:
# Create admin instance after new role assignment
flash(f"Admin role was assigned to {model}!", "danger")
admin = Admin(user=model)
self.session.add(admin)
elif user_admin is not None and not user_admin.experiment_marks:
# Delete admin instance if there is no dependency
# Useful for undoing an unwanted role assignment
self.session.delete(user_admin)
2022-02-27 18:30:28 +00:00
2022-09-12 17:08:02 +00:00
# Lower email
model.email = model.email.lower()
2022-05-17 00:31:09 +00:00
def after_model_change(self, form, model, is_created):
if is_created:
flash(
2022-07-02 14:46:02 +00:00
f"{model.email} registered with role(s): {', '.join(role.name for role in model.roles)}.",
2022-05-17 00:31:09 +00:00
category="success",
)
2023-11-02 19:04:09 +00:00
elif model == current_user:
flash(f"Active semester is {model.active_semester}.", "warning")
2022-05-17 00:31:09 +00:00
2021-07-30 00:03:44 +00:00
class SemesterView(SecureAdminModelView):
2022-06-19 22:39:31 +00:00
class CreateForm(FlaskForm):
2022-07-02 22:48:05 +00:00
@staticmethod
2022-04-18 16:37:21 +00:00
def defaultFormLabel():
last_semester = Semester.lastSemester()
if last_semester.label == "WS":
return "SS"
2023-11-02 18:24:14 +00:00
return "WS"
2022-07-02 22:48:05 +00:00
@staticmethod
2022-04-18 16:37:21 +00:00
def defaultFormYear():
last_semester = Semester.lastSemester()
if last_semester.label == "WS":
return last_semester.year + 1
2023-11-02 18:24:14 +00:00
return last_semester.year
2022-05-07 19:05:16 +00:00
label = RadioField(
"Semester",
choices=["WS", "SS"],
validators=[DataRequired()],
default=defaultFormLabel,
)
2022-04-18 16:37:21 +00:00
year = IntegerField(
"Year",
validators=[DataRequired(), NumberRange(MIN_YEAR, MAX_YEAR)],
default=defaultFormYear,
2022-06-17 17:15:38 +00:00
description=f"Between {MIN_YEAR} and {MAX_YEAR}.",
2022-04-18 16:37:21 +00:00
)
2021-07-29 14:27:15 +00:00
transfer_parts = BooleanField(
2021-07-01 14:38:37 +00:00
"Transfer parts",
2022-05-16 01:37:08 +00:00
description="This option transfers the parts you have in your active semester. Make sure that your active semester is the last semester before creating a new one!",
default=True,
2021-07-29 14:27:15 +00:00
)
transfer_assistants = BooleanField(
2021-07-01 14:38:37 +00:00
"Transfer Assistants",
2022-05-16 01:37:08 +00:00
description="This option transfers assistants of your active semester to active experiments in the new semester. Make sure that your active semester is the last semester before creating a new one! Active experiments are transferred anyway. If you do not want an experiment to be transferred, set it to inactive before creating the new semester. Experiments which are switched to active before creating the new semester will be created in the new semester without assistants. It is recommended to check the assistants of all experiments after creating a new semester.",
2021-07-01 14:38:37 +00:00
default=True,
2021-07-29 14:27:15 +00:00
)
set_old_semesters_done = BooleanField(
"Set all old semesters as done",
description="Setting semesters as done prevents assistants from changing or even seeing marks in these semesters. Choose this option only if all marks in all old semesters are already set.",
default=False,
)
2021-07-29 14:27:15 +00:00
can_delete = False
2022-05-30 00:53:17 +00:00
column_display_all_relations = True
2021-07-29 14:27:15 +00:00
2021-11-30 00:41:18 +00:00
column_list = [
"label",
"year",
2022-09-17 13:55:49 +00:00
"done",
2021-11-30 00:41:18 +00:00
"parts",
]
column_searchable_list = [
"label",
"year",
]
2022-03-02 17:59:14 +00:00
column_default_sort = [
2022-09-17 13:55:49 +00:00
("id", True),
]
form_columns = [
"done",
2022-03-02 17:59:14 +00:00
]
2021-04-24 11:38:03 +00:00
2022-06-30 02:08:21 +00:00
column_formatters = {
"parts": part_formatter,
"semester_experiments": semester_experiment_formatter,
"active_users": user_formatter,
"groups": group_formatter,
}
2022-07-01 16:50:39 +00:00
column_formatters_export = {
"parts": str_without_semester_formatter,
"semester_experiments": str_without_semester_formatter,
"groups": str_without_semester_formatter,
}
2022-06-30 02:08:21 +00:00
2022-09-17 13:55:49 +00:00
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."
2022-09-17 13:55:49 +00:00
},
}
2021-07-29 22:24:10 +00:00
def customCreateModel(self, form):
2022-05-15 18:05:00 +00:00
return Semester.initFromOldSemester(
2021-07-29 22:24:10 +00:00
label=form.label.data,
year=form.year.data,
oldSemester=current_user.active_semester,
2021-07-29 22:24:10 +00:00
transferParts=form.transfer_parts.data,
transferAssistants=form.transfer_assistants.data,
)
2022-09-17 13:55:49 +00:00
def on_model_change(self, form, model, is_created):
if is_created:
if form.set_old_semesters_done.data:
last_semester = db.session.get(Semester, model.id - 1)
last_semester.set_done(model)
else:
current_user.active_semester = model
flash(f"Active semester changed to the new semester {model}!", "warning")
2023-11-02 19:04:09 +00:00
elif model.done:
next_semester = db.session.get(Semester, model.id + 1)
model.set_done(next_semester)
2021-04-24 11:38:03 +00:00
2021-07-29 22:24:10 +00:00
def programQueryFactory():
return Program.query
2021-07-30 00:03:44 +00:00
class PartView(SecureAdminModelView):
2022-05-30 00:53:17 +00:00
column_display_all_relations = True
2021-07-28 22:58:50 +00:00
2022-07-02 22:48:05 +00:00
column_sortable_list: list[str] = []
2021-11-30 00:41:18 +00:00
column_list = [
"program",
"number",
]
2022-05-30 00:53:17 +00:00
form_columns = column_list
2021-07-29 22:24:10 +00:00
form_extra_fields = {
"program": QuerySelectField(
2022-05-07 19:05:16 +00:00
"Program",
query_factory=programQueryFactory,
validators=[DataRequired()],
allow_blank=True,
blank_text="-",
2021-07-29 22:24:10 +00:00
)
}
2022-04-04 17:08:19 +00:00
form_args = {
"number": {"widget": NumberInput(min=1)},
}
2021-04-24 11:38:03 +00:00
2022-06-30 02:08:21 +00:00
column_formatters = {
"program": program_formatter,
"semester": semester_formatter,
"part_students": part_student_formatter,
}
2022-03-03 03:26:05 +00:00
2022-05-21 16:32:34 +00:00
def query_modifier(self, query):
return query.where(Part.semester == current_user.active_semester)
2021-06-10 01:14:30 +00:00
2021-07-29 22:24:10 +00:00
def customCreateModel(self, form):
return Part(program=form.program.data, number=form.number.data, semester=current_user.active_semester)
2021-07-29 22:24:10 +00:00
2021-07-30 00:03:44 +00:00
class StudentView(SecureAdminModelView):
2022-05-30 00:53:17 +00:00
column_display_all_relations = True
2021-11-30 00:41:18 +00:00
column_list = [
"student_number",
"first_name",
"last_name",
"uni_email",
"contact_email",
"part_students",
]
column_descriptions = {
"contact_email": "The preferred contact email address if entered by the student",
}
2021-11-30 00:41:18 +00:00
column_sortable_list = [
"student_number",
"first_name",
"last_name",
]
2023-11-02 19:10:06 +00:00
column_searchable_list = [
*column_sortable_list,
2021-11-30 00:41:18 +00:00
"uni_email",
"contact_email",
]
2021-11-30 00:41:18 +00:00
form_excluded_columns = [
"part_students",
]
2021-04-26 22:26:11 +00:00
2021-04-27 21:28:47 +00:00
form_args = {
2022-04-04 17:08:19 +00:00
"student_number": {"widget": NumberInput(min=0)},
"uni_email": {"validators": [Email()]},
"contact_email": {"validators": [Email()]},
2021-04-27 21:28:47 +00:00
}
2022-07-01 16:50:39 +00:00
column_formatters = {
"uni_email": email_formatter,
"contact_email": email_formatter,
"part_students": part_student_part_formatter,
}
2022-07-02 22:48:05 +00:00
@staticmethod
2022-07-01 16:50:39 +00:00
def part_students_export_formatter(view, context, model, name):
part_students = deep_getattr(model, name)
if part_students is None:
return ""
2022-07-02 14:46:02 +00:00
return ", ".join(str(part_student.part) for part_student in part_students)
2022-07-01 16:50:39 +00:00
column_formatters_export = {
"part_students": part_students_export_formatter,
}
def on_model_change(self, form, model, is_created):
2022-09-12 17:08:02 +00:00
# Lower uni email
model.uni_email = model.uni_email.lower()
if model.contact_email is not None:
# Lower contact email
model.contact_email = model.contact_email.lower()
# Don't save contact_email if it is similar to uni_email
# No need to strip strings because of email form validation
if model.contact_email == model.uni_email:
model.contact_email = None
2021-06-10 01:14:30 +00:00
2021-07-12 14:42:11 +00:00
def partQueryFactory():
2022-05-21 19:04:06 +00:00
return Part.query.where(Part.semester == current_user.active_semester)
2021-06-10 01:14:30 +00:00
2021-07-12 14:42:11 +00:00
def groupQueryFactory():
2022-05-21 19:04:06 +00:00
return Group.query.where(Group.semester == current_user.active_semester)
2021-07-01 11:12:43 +00:00
2022-05-07 00:09:37 +00:00
class PartRowFilter(FilterEqual):
def get_options(self, view):
2022-05-07 21:53:47 +00:00
if not has_request_context():
2023-11-02 18:01:07 +00:00
return ()
2022-05-07 21:53:47 +00:00
parts = db.session.scalars(select(Part).where(Part.semester == current_user.active_semester))
2022-06-01 21:02:17 +00:00
return tuple((part.id, part.str_without_semester()) for part in parts)
2022-03-02 00:56:42 +00:00
2021-07-30 00:03:44 +00:00
class PartStudentView(SecureAdminModelView):
2022-05-07 00:09:37 +00:00
class PartFilter(PartRowFilter):
2022-03-02 00:56:42 +00:00
def apply(self, query, value, alias=None):
2022-05-21 19:19:26 +00:00
return query.where(PartStudent.part_id == int(value))
2022-03-02 00:56:42 +00:00
2022-06-19 22:39:31 +00:00
class CreateForm(FlaskForm):
2022-07-02 22:48:05 +00:00
@staticmethod
2021-07-12 14:42:11 +00:00
def studentQueryFactory():
return Student.query
2021-04-26 22:26:11 +00:00
student = QuerySelectField(
2021-07-13 01:37:00 +00:00
"Student",
query_factory=studentQueryFactory,
validators=[DataRequired()],
allow_blank=True,
blank_text="-",
)
part = QuerySelectField(
2021-07-13 01:37:00 +00:00
"Part",
query_factory=partQueryFactory,
validators=[DataRequired()],
allow_blank=True,
blank_text="-",
)
group = QuerySelectField(
"Group",
query_factory=groupQueryFactory,
allow_blank=True,
blank_text="-",
)
2021-04-26 22:26:11 +00:00
class EditForm(CreateForm):
student = None
part = None
2022-05-30 00:53:17 +00:00
column_display_all_relations = True
2022-03-02 00:56:42 +00:00
column_filters = (
PartFilter(PartStudent, "Part"),
"student.student_number",
"student.first_name",
"student.last_name",
"group.number",
"experiment_marks",
)
2022-05-06 23:44:39 +00:00
refreshFiltersCache = True
2022-06-30 02:08:21 +00:00
column_formatters = {
"student": student_formatter,
"group": group_formatter,
"part": part_formatter,
"experiment_marks": experiment_mark_formatter,
}
2022-07-01 16:50:39 +00:00
column_formatters_export = {
"group": str_without_semester_formatter,
"part": str_without_semester_formatter,
}
2022-03-03 03:26:05 +00:00
2022-05-21 16:32:34 +00:00
def query_modifier(self, query):
return query.join(Part).where(Part.semester == current_user.active_semester)
2021-07-12 14:42:11 +00:00
def on_model_change(self, form, model, is_created):
2022-05-15 18:59:57 +00:00
PartStudent.check(model.part, model.group)
2021-04-27 21:28:47 +00:00
2021-07-29 21:18:24 +00:00
def partStudentQueryFactory():
2022-05-21 16:44:53 +00:00
return PartStudent.query.join(Part).where(Part.semester == current_user.active_semester)
2021-07-13 01:37:00 +00:00
2022-05-07 00:09:37 +00:00
class ProgramRowFilter(FilterEqual):
def get_options(self, view):
2022-05-07 21:53:47 +00:00
if not has_request_context():
2023-11-02 18:01:07 +00:00
return ()
2022-05-07 21:53:47 +00:00
programs = db.session.scalars(select(Program))
2022-06-01 21:02:17 +00:00
return tuple((program.id, str(program)) for program in programs)
2022-03-02 00:56:42 +00:00
2022-05-06 23:44:39 +00:00
class GroupView(SecureAdminModelView):
2022-05-07 00:09:37 +00:00
class ProgramFilter(ProgramRowFilter):
2022-03-02 00:56:42 +00:00
def apply(self, query, value, alias=None):
2022-05-21 19:19:26 +00:00
return query.where(Group.program_id == int(value))
2022-03-02 00:56:42 +00:00
2022-07-02 22:48:05 +00:00
@staticmethod
2021-08-18 18:11:40 +00:00
def formFactory(is_created, group):
if is_created:
2021-07-29 21:18:24 +00:00
def query_factory():
2023-11-02 17:09:10 +00:00
return partStudentQueryFactory().where(PartStudent.group is None)
2021-07-29 21:18:24 +00:00
else:
def query_factory():
2022-05-21 16:44:53 +00:00
return partStudentQueryFactory().where(
2021-08-18 18:06:18 +00:00
or_(
2023-11-02 17:09:10 +00:00
and_(PartStudent.group is None, Part.program == group.program),
2021-08-18 18:06:18 +00:00
PartStudent.group == group,
)
)
2021-07-29 21:18:24 +00:00
2022-06-19 22:39:31 +00:00
class CustomForm(FlaskForm):
2021-07-29 21:18:24 +00:00
part_students = QuerySelectMultipleField(
"Part Students",
query_factory=query_factory,
validators=[DataRequired()],
description="The part students have to be in the same program!",
2021-07-29 21:18:24 +00:00
)
return CustomForm
2022-05-30 00:53:17 +00:00
column_display_all_relations = True
2022-03-03 03:26:05 +00:00
2022-05-30 00:53:17 +00:00
column_exclude_list = [
"semester",
2021-11-30 00:41:18 +00:00
]
2022-05-06 23:44:39 +00:00
2022-03-02 00:56:42 +00:00
column_filters = (
ProgramFilter(Group, "Program"),
2021-11-30 00:41:18 +00:00
"number",
2022-03-02 00:56:42 +00:00
)
2022-05-06 23:44:39 +00:00
refreshFiltersCache = True
2022-06-30 02:08:21 +00:00
column_formatters = {
"program": program_formatter,
"part_students": part_student_formatter,
"group_experiments": group_experiment_formatter,
}
2022-05-21 16:32:34 +00:00
def query_modifier(self, query):
2022-05-21 16:44:40 +00:00
return query.where(Group.semester == current_user.active_semester)
2021-07-12 14:42:11 +00:00
2021-07-29 22:24:10 +00:00
def customCreateModel(self, form):
return Group.customInit(form.part_students.data)
2021-07-29 21:18:24 +00:00
def create_form(self, obj=None):
2022-05-06 23:06:08 +00:00
formClass = GroupView.formFactory(is_created=True, group=None)
return formClass(get_form_data(), obj=obj)
2021-07-29 21:18:24 +00:00
def edit_form(self, obj=None):
2022-05-06 23:06:08 +00:00
formClass = GroupView.formFactory(is_created=False, group=obj)
return formClass(get_form_data(), obj=obj)
2021-07-29 21:18:24 +00:00
2021-06-07 15:15:10 +00:00
2021-07-30 00:03:44 +00:00
class ExperimentView(SecureAdminModelView):
2022-05-07 00:09:37 +00:00
class ProgramFilter(ProgramRowFilter):
2022-03-02 17:59:14 +00:00
def apply(self, query, value, alias=None):
2022-05-21 19:19:26 +00:00
return query.where(Experiment.program_id == int(value))
2022-03-02 17:59:14 +00:00
2022-05-30 00:53:17 +00:00
column_display_all_relations = True
2021-07-28 22:58:50 +00:00
2022-03-02 17:59:14 +00:00
column_filters = (
ProgramFilter(Experiment, "Program"),
"active",
)
2022-05-06 23:44:39 +00:00
refreshFiltersCache = True
2021-11-30 00:41:18 +00:00
column_list = [
"number",
"program",
"title",
"active",
]
column_descriptions = {
"active": "Active experiments are present in new semesters",
}
2022-05-30 00:53:17 +00:00
column_searchable_list = [
"number",
"title",
]
2023-11-02 19:10:06 +00:00
form_columns = [
*column_list,
2021-07-28 11:59:41 +00:00
"description",
"wiki_link",
"building",
2022-02-23 19:36:29 +00:00
"room",
"responsibility",
"duration_in_days",
2022-03-02 17:59:14 +00:00
]
2021-06-21 16:26:38 +00:00
2021-11-30 00:41:18 +00:00
column_editable_list = [
"active",
]
2021-07-13 16:41:00 +00:00
form_args = {
2022-04-04 17:08:19 +00:00
"number": {"widget": NumberInput(min=1)},
"wiki_link": {"validators": [URL()]},
2022-04-04 17:08:19 +00:00
"duration_in_days": {"widget": NumberInput(min=1)},
}
form_extra_fields = {
"program": QuerySelectField(
2022-05-07 19:05:16 +00:00
"Program",
query_factory=programQueryFactory,
validators=[DataRequired()],
allow_blank=True,
blank_text="-",
)
}
2021-07-13 16:41:00 +00:00
2022-06-30 02:08:21 +00:00
column_formatters = {
"program": program_formatter,
"semester_experiments": semester_experiment_with_semester_formatter,
}
2021-06-21 16:26:38 +00:00
2021-07-28 22:31:43 +00:00
def assistantQueryFactory():
2023-11-02 17:09:10 +00:00
return Assistant.query.join(User).where(User.active is True)
2021-07-28 22:31:43 +00:00
2022-06-17 17:51:18 +00:00
def weighting_field(label: str, default: float):
return DecimalField(
label,
validators=[DataRequired(), NumberRange(0, 1)],
default=default,
description="Between 0 and 1.",
places=2,
widget=NumberInput(step=0.01),
)
2021-07-30 00:03:44 +00:00
class SemesterExperimentView(SecureAdminModelView):
2022-05-07 00:09:37 +00:00
class ProgramFilter(ProgramRowFilter):
2022-03-02 17:59:14 +00:00
def apply(self, query, value, alias=None):
2022-05-21 18:56:02 +00:00
return query.join(Experiment).where(Experiment.program_id == int(value))
2022-03-02 17:59:14 +00:00
2022-06-19 22:39:31 +00:00
class CreateForm(FlaskForm):
2022-07-02 22:48:05 +00:00
@staticmethod
2021-07-28 22:31:43 +00:00
def experimentQueryFactory():
2023-11-02 17:09:10 +00:00
return Experiment.query.where(Experiment.active is True)
2021-07-28 22:31:43 +00:00
experiment = QuerySelectField(
"Experiment",
query_factory=experimentQueryFactory,
validators=[DataRequired()],
allow_blank=True,
blank_text="-",
)
2022-06-17 17:51:18 +00:00
oral_weighting = weighting_field("Oral weighting", 0.5)
protocol_weighting = weighting_field("Protocol weighting", 0.5)
final_weighting = weighting_field("Final weighting", 1.0)
2022-05-07 19:05:16 +00:00
assistants = QuerySelectMultipleField(
"Assistants",
query_factory=assistantQueryFactory,
)
2021-07-28 22:31:43 +00:00
class EditForm(CreateForm):
experiment = None
2022-05-30 00:53:17 +00:00
column_display_all_relations = True
2021-07-28 22:31:43 +00:00
2021-11-30 00:41:18 +00:00
column_list = [
"experiment",
"assistants",
]
2022-05-06 23:44:39 +00:00
2022-03-02 17:59:14 +00:00
column_filters = (ProgramFilter(SemesterExperiment, "Program"),)
2022-05-06 23:44:39 +00:00
refreshFiltersCache = True
2022-03-02 17:59:14 +00:00
column_searchable_list = [
"experiment.number",
"experiment.title",
]
2021-06-21 16:26:38 +00:00
2022-06-30 02:08:21 +00:00
column_formatters = {
"experiment": experiment_formatter,
"semester": semester_formatter,
"assistants": assistant_formatter,
"group_experiments": group_experiment_formatter,
}
2022-03-03 03:26:05 +00:00
2022-05-21 16:32:34 +00:00
def query_modifier(self, query):
return query.where(SemesterExperiment.semester == current_user.active_semester)
2021-06-21 16:07:18 +00:00
2021-07-29 22:24:10 +00:00
def customCreateModel(self, form):
return SemesterExperiment(
semester=current_user.active_semester,
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,
2021-07-29 22:24:10 +00:00
)
2021-07-28 22:31:43 +00:00
def on_model_change(self, form, model, is_created):
model.checkAndRoundWeightings()
def update_model(self, form, model):
2022-04-04 16:47:53 +00:00
weightingsChanged = (
form.oral_weighting.data != model.oral_weighting
or form.protocol_weighting.data != model.protocol_weighting
or form.final_weighting.data != model.final_weighting
)
updateSuccessful = super().update_model(form, model)
if updateSuccessful and weightingsChanged:
# Custom after_model_change
model.updateFinalExperimentAndPartMarks()
return updateSuccessful
2021-06-21 16:07:18 +00:00
2022-05-09 00:13:18 +00:00
def userHasRoleFilterFactory(column, role_name):
class UserHasRoleFilter(BooleanEqualFilter):
def apply(self, query, value, alias=None):
2022-05-21 18:56:02 +00:00
query = query.join(User)
2022-05-09 00:13:18 +00:00
if bool(int(value)): # value is string "0" or "1"
2022-05-21 18:56:02 +00:00
return query.join(User.roles).where(Role.name == role_name)
2023-11-02 18:24:14 +00:00
return query.where(not_(User.roles.any(Role.name == role_name)))
2022-05-09 00:13:18 +00:00
return UserHasRoleFilter(column, f"Currently has {role_name} role")
2021-07-30 00:03:44 +00:00
class AssistantView(SecureAdminModelView):
2022-07-02 22:48:05 +00:00
@staticmethod
def assistantUserQueryFactory():
2022-05-21 19:04:06 +00:00
return User.query.join(User.roles).where(Role.name == "assistant")
2022-06-30 02:08:21 +00:00
column_display_all_relations = True
2021-07-28 22:58:50 +00:00
2021-11-30 00:41:18 +00:00
column_list = [
2022-06-01 21:02:17 +00:00
"user",
2021-11-30 00:41:18 +00:00
"semester_experiments",
]
column_searchable_list = [
"user.first_name",
"user.last_name",
]
2022-05-09 00:13:18 +00:00
column_filters = (
userHasRoleFilterFactory(Assistant, "assistant"),
"user.active",
)
2021-11-30 00:41:18 +00:00
form_excluded_columns = [
"experiment_marks",
"appointments",
]
form_extra_fields = {
"user": QuerySelectField(
"User",
query_factory=assistantUserQueryFactory,
validators=[DataRequired()],
allow_blank=True,
blank_text="-",
2021-08-15 23:15:19 +00:00
),
"semester_experiments": QuerySelectMultipleField(
"Semester Experiments",
query_factory=semesterExperimentQueryFactory,
allow_blank=True,
blank_text="-",
),
}
2021-06-24 17:24:14 +00:00
2022-06-30 02:08:21 +00:00
column_formatters = {
"user": user_formatter,
"semester_experiments": semester_experiment_with_semester_formatter,
"appointments": appointment_formatter,
"experiment_marks": experiment_mark_formatter,
}
2021-06-24 17:24:14 +00:00
2022-03-03 03:26:05 +00:00
class AdminView(SecureAdminModelView):
can_export = False
can_set_page_size = False
can_create = False
can_edit = False
can_delete = False
column_list = [
2022-06-01 21:02:17 +00:00
"user",
2022-03-03 03:26:05 +00:00
]
2022-05-09 00:13:18 +00:00
column_filters = (
userHasRoleFilterFactory(Admin, "admin"),
"user.active",
)
2022-03-03 03:26:05 +00:00
2022-06-30 02:08:21 +00:00
column_formatters = {
"user": user_formatter,
}
2022-03-01 21:23:30 +00:00
2022-05-07 00:09:37 +00:00
class ExperimentRowFilter(FilterEqual):
def get_options(self, view):
2022-05-07 21:53:47 +00:00
if not has_request_context():
2023-11-02 18:01:07 +00:00
return ()
2022-05-07 21:53:47 +00:00
2023-11-02 17:09:10 +00:00
activeExperiments = db.session.scalars(select(Experiment).where(Experiment.active is True))
2022-05-07 00:09:37 +00:00
return tuple(
(
f"{activeExperiment.number},{activeExperiment.program_id}",
2022-06-01 21:02:17 +00:00
f"{activeExperiment.number} {activeExperiment.program}",
2022-05-07 00:09:37 +00:00
)
for activeExperiment in activeExperiments
2022-03-02 00:56:42 +00:00
)
2022-07-02 22:48:05 +00:00
@staticmethod
2022-05-21 18:56:02 +00:00
def get_values(value):
values = value.split(",")
experimentNumber = int(values[0])
programId = int(values[1])
return (experimentNumber, programId)
2022-03-02 00:56:42 +00:00
2022-06-17 17:15:38 +00:00
def group_experiment_note_field():
return TextAreaField(
"Note",
validators=[Optional()],
description="This note can be seen and edited by assistants that are responsible for this semester experiment.",
)
2022-06-17 17:51:18 +00:00
def appointment_fields(number=None):
if number is None:
# Used in AppointmentView
label_addition = ""
date_validator = DataRequired()
date_description = None
else:
# Used in GroupExperimentView
label_addition = f"Appointment-{number} "
date_validator = Optional()
if number == 1:
date_description = "Set if you already want to add an appointment. Otherwise, leave it blank and you can do it later under the Appointment tab."
else:
date_description = "Add another appointment (see above)."
date = DateField(
label_addition + "Date",
validators=[date_validator],
description=date_description,
)
special = BooleanField(
label_addition + "Special",
default=False,
description="A special appointment should take place in the semester break.",
)
assistant = QuerySelectField(
label_addition + "Assistant",
query_factory=assistantQueryFactory,
allow_blank=True,
blank_text="Auto assign if experiment has only one assistant",
)
return date, special, assistant
2021-07-30 00:03:44 +00:00
class GroupExperimentView(SecureAdminModelView):
2022-05-07 00:09:37 +00:00
class ExperimentFilter(ExperimentRowFilter):
2022-03-01 21:23:30 +00:00
def apply(self, query, value, alias=None):
2022-05-21 18:56:02 +00:00
experimentNumber, programId = ExperimentRowFilter.get_values(value)
return (
query.join(SemesterExperiment)
.join(Experiment)
.where(Experiment.program_id == programId, Experiment.number == experimentNumber)
2022-03-01 21:23:30 +00:00
)
2022-06-19 22:39:31 +00:00
class EditForm(FlaskForm):
2022-06-17 17:15:38 +00:00
note = group_experiment_note_field()
2022-06-19 22:39:31 +00:00
class CreateForm(FlaskForm):
2021-07-01 11:12:43 +00:00
group = QuerySelectField(
2021-07-13 01:37:00 +00:00
"Group",
query_factory=groupQueryFactory,
validators=[DataRequired()],
allow_blank=True,
blank_text="-",
2021-07-01 11:12:43 +00:00
)
semester_experiment = QuerySelectField(
"Semester Experiment",
query_factory=semesterExperimentQueryFactory,
2021-07-01 11:12:43 +00:00
validators=[DataRequired()],
allow_blank=True,
blank_text="-",
)
2022-06-17 17:51:18 +00:00
appointment1_date, appointment1_special, appointment1_assistant = appointment_fields(1)
appointment2_date, appointment2_special, appointment2_assistant = appointment_fields(2)
2021-07-01 11:12:43 +00:00
2022-06-17 17:15:38 +00:00
note = group_experiment_note_field()
2021-07-01 11:12:43 +00:00
2022-05-30 00:53:17 +00:00
column_display_all_relations = True
2022-05-06 23:44:39 +00:00
2022-03-02 00:56:42 +00:00
column_filters = (
2022-03-01 21:23:30 +00:00
ExperimentFilter(GroupExperiment, "Experiment"),
2022-03-02 00:56:42 +00:00
"group.number",
2021-11-30 00:41:18 +00:00
"appointments",
2022-03-02 00:56:42 +00:00
"experiment_marks",
)
2022-05-06 23:44:39 +00:00
refreshFiltersCache = True
2021-06-24 17:39:26 +00:00
column_default_sort = ("experiment_marks_missing", True)
2022-06-30 02:08:21 +00:00
column_formatters = {
"semester_experiment": semester_experiment_formatter,
"group": group_formatter,
"appointments": appointment_date_formatter,
"experiment_marks": experiment_mark_formatter,
"experiment_marks_missing": experiment_marks_missing_formatter,
2022-06-30 02:08:21 +00:00
}
2022-03-03 03:26:05 +00:00
2022-07-02 22:48:05 +00:00
@staticmethod
2022-07-01 16:50:39 +00:00
def appointments_export_formatter(view, context, model, name):
appointments = deep_getattr(model, name)
if appointments is None:
return ""
2022-07-02 14:46:02 +00:00
return ", ".join(str(appointment.date) for appointment in appointments)
2022-07-01 16:50:39 +00:00
column_formatters_export = {
"semester_experiment": str_without_semester_formatter,
"group": str_without_semester_formatter,
"appointments": appointments_export_formatter,
}
2022-05-21 16:32:34 +00:00
def query_modifier(self, query):
return query.join(Group).where(Group.semester == current_user.active_semester)
2021-07-29 22:24:10 +00:00
def customCreateModel(self, form):
2022-05-15 18:59:57 +00:00
return GroupExperiment(semester_experiment=form.semester_experiment.data, group=form.group.data)
2021-07-01 17:43:59 +00:00
2021-07-29 22:24:10 +00:00
def on_model_change(self, form, model, is_created):
if is_created:
2022-05-15 20:24:49 +00:00
for date, special, assistant in (
(form.appointment1_date.data, form.appointment1_special.data, form.appointment1_assistant.data),
(form.appointment2_date.data, form.appointment2_special.data, form.appointment2_assistant.data),
2021-07-01 17:43:59 +00:00
):
2022-05-15 20:24:49 +00:00
if date is not None:
appointment = Appointment(date=date, special=special, group_experiment=model, assistant=assistant)
2021-07-01 17:43:59 +00:00
self.session.add(appointment)
2021-07-29 22:24:10 +00:00
def after_model_change(self, form, model, is_created):
2023-11-02 18:38:09 +00:00
if is_created and model.appointments:
flash(f"Appointments {model.appointments} added.", "success")
2021-07-01 17:43:59 +00:00
2021-06-24 17:39:26 +00:00
2021-07-12 14:42:11 +00:00
def groupExperimentQueryFactory():
2022-05-21 18:56:02 +00:00
return GroupExperiment.query.join(SemesterExperiment).where(
SemesterExperiment.semester == current_user.active_semester
2021-07-12 14:42:11 +00:00
)
2022-05-07 00:09:37 +00:00
class AssistantRowFilter(FilterEqual):
def get_options(self, view):
2022-05-07 21:53:47 +00:00
if not has_request_context():
2023-11-02 18:01:07 +00:00
return ()
2022-05-07 21:53:47 +00:00
2022-05-07 00:09:37 +00:00
activeAssistants = assistantQueryFactory()
2022-06-01 21:02:17 +00:00
return tuple((assistant.id, str(assistant)) for assistant in activeAssistants)
2022-03-02 00:56:42 +00:00
2021-07-30 00:03:44 +00:00
class AppointmentView(SecureAdminModelView):
2022-05-07 00:09:37 +00:00
class ExperimentFilter(ExperimentRowFilter):
2022-03-02 00:56:42 +00:00
def apply(self, query, value, alias=None):
2022-05-21 18:56:02 +00:00
experimentNumber, programId = ExperimentRowFilter.get_values(value)
return query.join(Experiment).where(
Experiment.program_id == programId, Experiment.number == experimentNumber
2022-03-02 00:56:42 +00:00
)
2022-05-07 00:09:37 +00:00
class AssistantFilter(AssistantRowFilter):
2022-03-02 00:56:42 +00:00
def apply(self, query, value, alias=None):
2022-05-21 19:19:26 +00:00
return query.where(Appointment.assistant_id == int(value))
2022-03-02 00:56:42 +00:00
2022-06-19 22:39:31 +00:00
class CreateAndEditForm(FlaskForm):
2021-07-12 14:42:11 +00:00
group_experiment = QuerySelectField(
2021-07-13 01:37:00 +00:00
"Group Experiment",
query_factory=groupExperimentQueryFactory,
validators=[DataRequired()],
allow_blank=True,
blank_text="-",
2021-07-12 14:42:11 +00:00
)
2022-06-17 17:51:18 +00:00
date, special, assistant = appointment_fields()
2021-07-12 14:42:11 +00:00
2022-05-30 02:18:19 +00:00
column_descriptions = {
"special": "A special appointment should take place in the semester break",
}
2022-03-02 00:56:42 +00:00
column_filters = (
ExperimentFilter(Appointment, "Experiment"),
AssistantFilter(Appointment, "Assistant"),
"group_experiment.group",
"date",
"special",
2022-03-02 00:56:42 +00:00
)
2022-05-06 23:44:39 +00:00
refreshFiltersCache = True
2021-11-30 00:41:18 +00:00
column_editable_list = [
"date",
"special",
]
2021-07-12 14:42:11 +00:00
2022-06-30 02:08:21 +00:00
column_formatters = {
"group_experiment": group_experiment_formatter,
"assistant": assistant_formatter,
}
2022-03-03 03:26:05 +00:00
2022-05-21 16:32:34 +00:00
def query_modifier(self, query):
return (
query.join(GroupExperiment)
.join(SemesterExperiment)
.where(SemesterExperiment.semester == current_user.active_semester)
2021-07-12 14:42:11 +00:00
)
2021-07-29 22:24:10 +00:00
def customCreateModel(self, form):
2022-05-15 20:24:49 +00:00
return Appointment(
date=form.date.data,
special=form.special.data,
group_experiment=form.group_experiment.data,
assistant=form.assistant.data,
2021-07-29 22:24:10 +00:00
)
2021-07-12 14:42:11 +00:00
2022-05-17 00:31:09 +00:00
def customUpdateModel(self, form, model):
if form.date is None:
# For editable
model.special = form.special.data
elif form.special is None:
# For editable
model.date = form.date.data
else:
# Not editable, full form
model.custom_update(
date=form.date.data,
special=form.special.data,
group_experiment=form.group_experiment.data,
assistant=form.assistant.data,
)
2021-07-01 11:12:43 +00:00
return True
2021-07-01 11:12:43 +00:00
2021-07-30 00:03:44 +00:00
class ExperimentMarkView(SecureAdminModelView):
2022-05-07 00:09:37 +00:00
class AssistantFilter(AssistantRowFilter):
2022-03-01 21:23:30 +00:00
def apply(self, query, value, alias=None):
2022-05-21 19:19:26 +00:00
return query.where(ExperimentMark.assistant_id == int(value))
2022-03-01 21:23:30 +00:00
class AdminFilter(FilterEqual):
def get_options(self, view):
2022-05-07 21:53:47 +00:00
if not has_request_context():
2023-11-02 18:01:07 +00:00
return ()
2022-05-07 21:53:47 +00:00
2023-11-02 17:09:10 +00:00
admins = db.session.scalars(select(Admin).join(User).where(User.active is True))
2022-06-01 21:02:17 +00:00
return tuple((admin.id, str(admin)) for admin in admins)
2022-03-01 21:23:30 +00:00
def apply(self, query, value, alias=None):
2022-05-21 19:19:26 +00:00
return query.where(ExperimentMark.admin_id == int(value))
2022-03-02 00:56:42 +00:00
2022-05-07 00:09:37 +00:00
class ExperimentFilter(ExperimentRowFilter):
2022-03-02 00:56:42 +00:00
def apply(self, query, value, alias=None):
2022-05-21 18:56:02 +00:00
experimentNumber, programId = ExperimentRowFilter.get_values(value)
return query.join(Experiment).where(
Experiment.program_id == programId, Experiment.number == experimentNumber
2022-03-02 00:56:42 +00:00
)
2022-05-07 00:09:37 +00:00
class ProgramFilter(ProgramRowFilter):
2022-03-02 00:56:42 +00:00
def apply(self, query, value, alias=None):
2022-05-21 18:56:02 +00:00
return query.join(PartStudent).join(Part).where(Part.program_id == int(value))
2022-03-02 00:56:42 +00:00
2022-05-07 00:09:37 +00:00
class PartFilter(PartRowFilter):
2022-03-02 00:56:42 +00:00
def apply(self, query, value, alias=None):
2022-05-21 18:56:02 +00:00
return query.join(PartStudent).where(PartStudent.part_id == int(value))
2022-03-02 00:56:42 +00:00
2022-06-19 22:39:31 +00:00
class CreateForm(FlaskForm):
2021-07-13 00:50:15 +00:00
part_student = QuerySelectField(
"Part Student",
2021-07-29 21:18:24 +00:00
query_factory=partStudentQueryFactory,
2021-07-13 00:50:15 +00:00
validators=[DataRequired()],
allow_blank=True,
blank_text="-",
)
group_experiment = QuerySelectField(
"Group Experiment",
query_factory=groupExperimentQueryFactory,
validators=[DataRequired()],
allow_blank=True,
blank_text="-",
)
2022-06-19 22:39:31 +00:00
class EditForm(FlaskForm):
2022-06-17 17:58:43 +00:00
oral_mark = mark_field("Oral")
protocol_mark = mark_field("Protocol")
2021-07-13 00:50:15 +00:00
column_descriptions = {
2022-04-11 23:52:01 +00:00
"oral_mark": f"Between {MIN_MARK} and {MAX_MARK}",
"protocol_mark": f"Between {MIN_MARK} and {MAX_MARK}",
2021-08-29 16:15:14 +00:00
"final_experiment_mark": "Calculated automatically with oral and protocol marks and weightings",
2022-02-27 18:30:28 +00:00
"assistant": "The last assistant who edited the mark",
"admin": "The last admin who edited the mark",
}
2022-03-02 00:56:42 +00:00
column_filters = (
AssistantFilter(ExperimentMark, "Assistant"),
AdminFilter(ExperimentMark, "Admin"),
ExperimentFilter(ExperimentMark, "Experiment"),
ProgramFilter(ExperimentMark, "Program"),
PartFilter(ExperimentMark, "Part"),
2021-07-30 21:42:20 +00:00
"group_experiment.group",
2021-08-29 16:15:14 +00:00
"oral_mark",
"protocol_mark",
"final_experiment_mark",
2022-03-02 00:56:42 +00:00
)
2022-05-06 23:44:39 +00:00
refreshFiltersCache = True
2021-07-30 21:42:20 +00:00
column_default_sort = [("oral_mark", False), ("protocol_mark", False)]
2021-07-13 00:50:15 +00:00
2022-06-30 02:08:21 +00:00
column_formatters = {
"part_student": part_student_formatter,
"group_experiment": group_experiment_formatter,
"assistant": assistant_formatter,
"admin": admin_formatter,
}
2022-03-03 03:26:05 +00:00
2022-05-21 16:32:34 +00:00
def query_modifier(self, query):
return (
query.join(GroupExperiment)
.join(SemesterExperiment)
.where(SemesterExperiment.semester == current_user.active_semester)
2021-07-13 00:50:15 +00:00
)
2021-08-18 17:39:21 +00:00
def customCreateModel(self, form):
2022-05-15 19:07:00 +00:00
return ExperimentMark(part_student=form.part_student.data, group_experiment=form.group_experiment.data)
def customUpdateModel(self, form, model):
form_oral_mark = form.oral_mark.data
form_protocol_mark = form.protocol_mark.data
if (form_oral_mark != model.oral_mark) or (form_protocol_mark != model.protocol_mark):
model.set_oral_protocol_mark(form_oral_mark, form_protocol_mark)
model.admin = current_user.admin
return True
2022-05-07 00:09:37 +00:00
return False
2021-07-01 14:38:37 +00:00
2021-07-30 00:03:44 +00:00
class ProgramView(SecureAdminModelView):
2022-03-03 03:26:05 +00:00
can_export = False
can_set_page_size = False
2022-05-30 00:53:17 +00:00
column_display_all_relations = True
2021-07-28 23:04:58 +00:00
2021-11-30 00:41:18 +00:00
column_list = [
"label",
]
2022-06-30 02:08:21 +00:00
column_details_exclude_list = [
"groups",
]
2022-07-02 22:48:05 +00:00
column_sortable_list: list[str] = []
2021-11-30 00:41:18 +00:00
form_excluded_columns = [
"parts",
"experiments",
"groups",
]
2021-07-14 02:27:17 +00:00
2022-06-30 02:08:21 +00:00
column_formatters = {
"parts": part_with_semester_formatter,
"experiments": experiment_formatter,
}
2021-07-14 02:27:17 +00:00
2021-09-11 18:53:11 +00:00
class ImportView(SecureAdminBaseView):
class FileForm(FlaskForm):
file = FileField(
label="Import file",
validators=[FileRequired(), FileAllowed(["txt"], "Only txt files are allowed!")],
)
2022-04-10 19:01:42 +00:00
submit = SubmitField(
label="Upload and import",
)
2021-09-11 18:53:11 +00:00
2022-06-19 22:39:31 +00:00
@expose("/", methods=("GET", "POST"))
2021-09-11 18:53:11 +00:00
def index(self):
form = ImportView.FileForm()
if form.validate_on_submit():
f = form.file.data
filename = secure_filename(f.filename)
2022-09-07 22:33:00 +00:00
directory = data_dir / "db/import_files"
2021-09-11 18:53:11 +00:00
Path(directory).mkdir(exist_ok=True)
2022-09-07 22:33:00 +00:00
filePath = directory / filename
2021-09-11 18:53:11 +00:00
f.save(filePath)
try:
importFromFile(filePath)
2022-05-15 20:24:49 +00:00
except Exception as ex:
2022-05-17 23:07:27 +00:00
flash(str(ex), "error")
2021-09-11 18:53:11 +00:00
2022-10-09 01:00:51 +00:00
return self.render("admin_import.jinja.html", form=form)
2021-09-11 18:53:11 +00:00
2022-04-10 19:01:42 +00:00
class ActionsView(SecureAdminBaseView):
class ActionsForm(FlaskForm):
2023-11-01 22:18:47 +00:00
manual_backup = BooleanField(
label="Create a manual database backup",
)
2022-09-24 14:16:48 +00:00
update_final_experiment_and_part_marks = BooleanField(
label="Manually update all final experiment and part marks in the active semester",
)
deactivate_assistants = BooleanField(
label="Deactivate assistants that do not have experiments in a semester that is not set as done",
)
submit = SubmitField(
label="Submit",
render_kw={"class": "btn btn-primary btn-block"},
2022-04-10 19:01:42 +00:00
)
2022-06-19 22:39:31 +00:00
@expose("/", methods=("GET", "POST"))
2022-04-10 19:01:42 +00:00
def index(self):
form = ActionsView.ActionsForm()
if form.validate_on_submit():
2023-11-01 22:18:47 +00:00
if form.manual_backup.data:
manual_backup()
2022-09-24 14:16:48 +00:00
if form.update_final_experiment_and_part_marks.data:
update_final_experiment_and_part_marks()
if form.deactivate_assistants.data:
deactivate_assistants()
2022-04-10 19:01:42 +00:00
2022-09-24 14:16:48 +00:00
return redirect(self.url)
2022-04-10 19:01:42 +00:00
2022-10-09 01:00:51 +00:00
return self.render("admin_actions.jinja.html", form=form)
2022-04-10 19:01:42 +00:00
2022-04-11 23:52:01 +00:00
class AnalysisView(SecureAdminBaseView):
class AnalysisForm(FlaskForm):
2022-09-24 17:29:56 +00:00
assistant_marks_submit = SubmitField(
label="Active assistant's marks in all semesters",
2022-04-11 23:52:01 +00:00
)
2022-09-24 17:29:56 +00:00
final_part_marks_submit = SubmitField(
label="Final part marks in active semester",
2022-04-12 13:02:05 +00:00
)
2022-06-19 22:39:31 +00:00
@expose("/", methods=("GET", "POST"))
2022-04-11 23:52:01 +00:00
def index(self):
form = AnalysisView.AnalysisForm()
if form.validate_on_submit():
2022-09-24 17:29:56 +00:00
if form.assistant_marks_submit.data:
return assistant_marks_analysis(self)
2023-11-02 18:24:14 +00:00
if form.final_part_marks_submit.data:
2022-09-24 17:29:56 +00:00
return final_part_marks_analysis(self)
2022-04-12 13:02:05 +00:00
2022-09-11 12:55:53 +00:00
return self.render("analysis/analysis.jinja.html", form=form)
2022-04-11 23:52:01 +00:00
2022-02-23 18:37:09 +00:00
class DocsView(SecureAdminBaseView):
2022-03-04 02:49:02 +00:00
@expose("/")
2022-02-23 18:37:09 +00:00
def index(self):
2022-09-11 12:55:53 +00:00
return self.render("docs/docs.jinja.html", role="admin")
2022-05-30 15:02:16 +00:00
2022-08-09 12:46:48 +00:00
def init_admin_model_views(app):
adminSpace.add_view(StudentView(Student, url="student"))
adminSpace.add_view(PartStudentView(PartStudent, url="part_student"))
adminSpace.add_view(GroupView(Group, url="group"))
adminSpace.add_view(GroupExperimentView(GroupExperiment, url="group_experiment"))
adminSpace.add_view(AppointmentView(Appointment, url="appointment"))
adminSpace.add_view(ExperimentMarkView(ExperimentMark, url="experiment_mark"))
adminSpace.add_view(ExperimentView(Experiment, url="experiment"))
adminSpace.add_view(SemesterExperimentView(SemesterExperiment, url="semester_experiment"))
adminSpace.add_view(SemesterView(Semester, url="semester"))
adminSpace.add_view(PartView(Part, url="part"))
adminSpace.add_view(AssistantView(Assistant, url="assistant"))
adminSpace.add_view(AdminView(Admin, url="admin"))
adminSpace.add_view(UserView(User, url="user"))
adminSpace.add_view(ProgramView(Program, url="program"))
adminSpace.add_view(ImportView(name="Import", url="import"))
adminSpace.add_view(ActionsView(name="Actions", url="actions"))
adminSpace.add_view(AnalysisView(name="Analysis", url="analysis"))
adminSpace.add_view(DocsView(name="Docs", url="docs"))
2022-09-21 14:52:04 +00:00
adminSpace.add_link(MenuLink(name="User settings", url="/user-settings"))
2022-09-11 18:05:09 +00:00
adminSpace.add_link(MenuLink(name="Logout", url="/logout"))
2022-09-21 14:52:04 +00:00
adminSpace.init_app(app)