mirror of
https://codeberg.org/Mo8it/AdvLabDB.git
synced 2024-11-08 21:21:06 +00:00
713 lines
23 KiB
Python
713 lines
23 KiB
Python
from flask import flash, request, url_for
|
|
from flask_admin.contrib.sqla.filters import BaseSQLAFilter
|
|
from flask_admin.menu import MenuLink
|
|
from flask_admin.model.template import EndpointLinkRowAction
|
|
from flask_security import current_user, hash_password, admin_change_password
|
|
from sqlalchemy import func, or_
|
|
from wtforms import Form, BooleanField, SelectField, TextField, RadioField
|
|
from wtforms.validators import DataRequired, Email, Optional, URL
|
|
from flask_admin.contrib.sqla.fields import QuerySelectMultipleField, QuerySelectField
|
|
from flask_admin.helpers import get_form_data
|
|
from wtforms.fields.html5 import DateField
|
|
|
|
from advlabdb import admin, app, db, user_datastore
|
|
from advlabdb.configUtils import getConfig
|
|
from advlabdb.customClasses import SecureModelView
|
|
from advlabdb.models import (
|
|
Appointment,
|
|
Assistant,
|
|
Experiment,
|
|
ExperimentMark,
|
|
Group,
|
|
GroupExperiment,
|
|
Part,
|
|
SemesterExperiment,
|
|
PartStudent,
|
|
Role,
|
|
Semester,
|
|
Student,
|
|
User,
|
|
Program,
|
|
)
|
|
from advlabdb.utils import (
|
|
randomPassword,
|
|
setUserActiveSemester,
|
|
userActiveSemester,
|
|
)
|
|
from advlabdb.exceptions import ModelViewException, DataBaseException
|
|
|
|
|
|
class UserView(SecureModelView):
|
|
class CreateForm(Form):
|
|
def roleQueryFactory():
|
|
return Role.query
|
|
|
|
def semesterQueryFactory():
|
|
return Semester.query
|
|
|
|
email = TextField("Email", validators=[DataRequired(), Email()])
|
|
roles = QuerySelectMultipleField(
|
|
"Roles",
|
|
query_factory=roleQueryFactory,
|
|
validators=[DataRequired()],
|
|
default=[Role.query.filter(Role.name == "assistant").first()],
|
|
)
|
|
active = BooleanField("Active", default=True)
|
|
active_semester = QuerySelectField(
|
|
"Active Semester",
|
|
query_factory=semesterQueryFactory,
|
|
allow_blank=True,
|
|
blank_text="-",
|
|
)
|
|
|
|
class EditForm(CreateForm):
|
|
generate_new_password = BooleanField("Generate new random password", default=False)
|
|
|
|
form = EditForm
|
|
|
|
can_view_details = True
|
|
|
|
column_list = ["email", "active", "roles", "assistant", "active_semester"]
|
|
column_searchable_list = ["email"]
|
|
column_filters = ["active", "active_semester", "assistant"]
|
|
column_editable_list = ["active"]
|
|
|
|
def create_form(self, obj=None):
|
|
form = self.CreateForm
|
|
return form(get_form_data(), obj=obj)
|
|
|
|
def flashPassword(password):
|
|
flash(f"Random password: {password}", category="warning")
|
|
|
|
def create_model(self, form):
|
|
try:
|
|
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, active_semester=form.active_semester.data
|
|
)
|
|
|
|
self.on_model_change(form, model, True)
|
|
self.session.commit()
|
|
except Exception as ex:
|
|
flash(str(ex), "error")
|
|
|
|
self.session.rollback()
|
|
else:
|
|
flash(
|
|
f"{email} registered with roles: {', '.join([role.name for role in form.roles.data])}.",
|
|
category="success",
|
|
)
|
|
|
|
UserView.flashPassword(password)
|
|
|
|
self.after_model_change(form, model, True)
|
|
return model
|
|
|
|
def on_model_delete(self, model):
|
|
if model == current_user:
|
|
raise ModelViewException("Tried to delete yourself as user!")
|
|
|
|
def on_model_change(self, form, model, is_created):
|
|
if not is_created:
|
|
if model == current_user and not form.active.data:
|
|
raise ModelViewException("Tried to deactiavte yourself as user!")
|
|
|
|
if form.generate_new_password.data:
|
|
password = randomPassword()
|
|
|
|
UserView.flashPassword(password)
|
|
|
|
admin_change_password(
|
|
model, password, notify=False
|
|
) # Password is automatically hashed with this method
|
|
|
|
|
|
class RoleView(SecureModelView):
|
|
can_create = False
|
|
can_edit = False
|
|
can_delete = False
|
|
column_display_actions = False
|
|
|
|
column_list = ["name", "description"]
|
|
|
|
|
|
class SemesterView(SecureModelView):
|
|
class CreateForm(Form):
|
|
def labelDefault():
|
|
if userActiveSemester().label == "WS":
|
|
return "SS"
|
|
else:
|
|
return "WS"
|
|
|
|
def yearDefault():
|
|
activeSemester = userActiveSemester()
|
|
if activeSemester.label == "WS":
|
|
return activeSemester.year + 1
|
|
else:
|
|
return activeSemester.year
|
|
|
|
label = RadioField("Semester", choices=["WS", "SS"], validators=[DataRequired()], default=labelDefault)
|
|
year = TextField("Year", validators=[DataRequired()], default=yearDefault)
|
|
transfer_parts = BooleanField(
|
|
"Transfer parts",
|
|
description="This option transfers the parts you have in your current active semester. Make sure that your semester is the last semester before creating a new one (recommended)!",
|
|
default=True,
|
|
)
|
|
transfer_assistants = BooleanField(
|
|
"Transfer Assistants",
|
|
description="This option transfers assistants of your active semester to active experiments in the new semester. Make sure that your semester is the last semester before creating a new one (recommended)! Active experiments are transfered anyway. If you do not want an experiment to be transfered, 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 important to check the assistants of all experiments after creating a new semester.",
|
|
default=True,
|
|
)
|
|
|
|
form = CreateForm
|
|
|
|
can_edit = False
|
|
can_delete = False
|
|
can_view_details = True
|
|
|
|
column_list = ["label", "year", "parts"]
|
|
column_details_list = column_list + ["semester_experiments", "active_users", "groups"]
|
|
column_searchable_list = ["label", "year"]
|
|
|
|
def customCreateModel(self, form):
|
|
return Semester.customInit(
|
|
label=form.label.data,
|
|
year=form.year.data,
|
|
oldSemester=userActiveSemester(),
|
|
transferParts=form.transfer_parts.data,
|
|
transferAssistants=form.transfer_assistants.data,
|
|
)
|
|
|
|
def after_model_change(self, form, model, is_created):
|
|
setUserActiveSemester(model.id)
|
|
|
|
admin.add_link(
|
|
MenuLink(
|
|
name=model.repr(),
|
|
url=url_for("set_semester") + "?semester_id=" + str(model.id),
|
|
category="Active semester",
|
|
)
|
|
)
|
|
|
|
|
|
def programQueryFactory():
|
|
return Program.query
|
|
|
|
|
|
class PartView(SecureModelView):
|
|
can_view_details = True
|
|
|
|
column_sortable_list = []
|
|
column_list = ["program", "number", "semester"]
|
|
column_details_list = column_list + ["part_students"]
|
|
form_columns = ["program", "number"]
|
|
|
|
form_extra_fields = {
|
|
"program": QuerySelectField(
|
|
"Program", query_factory=programQueryFactory, validators=[DataRequired()], allow_blank=True, blank_text="-"
|
|
)
|
|
}
|
|
|
|
def queryFilter(self):
|
|
return Part.semester == userActiveSemester()
|
|
|
|
def customCreateModel(self, form):
|
|
return Part(program=form.program.data, number=form.number.data, semester=userActiveSemester())
|
|
|
|
|
|
class StudentView(SecureModelView):
|
|
can_view_details = True
|
|
|
|
column_list = ["student_number", "first_name", "last_name", "uni_email", "contact_email", "part_students"]
|
|
column_details_list = column_list + ["bachelor_thesis", "bachelor_thesis_work_group", "note"]
|
|
|
|
column_sortable_list = ["student_number", "first_name", "last_name"]
|
|
column_searchable_list = column_sortable_list + ["uni_email", "contact_email"]
|
|
|
|
form_excluded_columns = ["part_students"]
|
|
|
|
form_args = {
|
|
"uni_email": {"validators": [Email()]},
|
|
"contact_email": {"validators": [Email()]},
|
|
}
|
|
|
|
column_extra_row_actions = [
|
|
EndpointLinkRowAction(
|
|
"glyphicon glyphicon-time",
|
|
id_arg="flt1_0",
|
|
title="Experiments history",
|
|
endpoint="experimentmark.index_view",
|
|
)
|
|
]
|
|
|
|
|
|
def partQueryFactory():
|
|
return Part.query.filter(Part.id.in_([part.id for part in userActiveSemester().parts]))
|
|
|
|
|
|
def groupQueryFactory():
|
|
return Group.query.filter(Group.semester == userActiveSemester())
|
|
|
|
|
|
markChoices = [(-1, "-")] + list(zip(range(16)[::-1], range(16)[::-1]))
|
|
|
|
|
|
class PartStudentView(SecureModelView):
|
|
class CreateForm(Form):
|
|
def studentQueryFactory():
|
|
return Student.query
|
|
|
|
student = QuerySelectField(
|
|
"Student",
|
|
query_factory=studentQueryFactory,
|
|
validators=[DataRequired()],
|
|
allow_blank=True,
|
|
blank_text="-",
|
|
)
|
|
part = QuerySelectField(
|
|
"Part",
|
|
query_factory=partQueryFactory,
|
|
validators=[DataRequired()],
|
|
allow_blank=True,
|
|
blank_text="-",
|
|
)
|
|
group = QuerySelectField(
|
|
"Group",
|
|
query_factory=groupQueryFactory,
|
|
allow_blank=True,
|
|
blank_text="-",
|
|
)
|
|
|
|
class EditForm(CreateForm):
|
|
student = None
|
|
part = None
|
|
final_part_mark = SelectField("Final Part Mark", choices=markChoices, coerce=int)
|
|
|
|
form = EditForm
|
|
|
|
column_filters = ["part", "student", "group"]
|
|
|
|
def queryFilter(self):
|
|
return PartStudent.part_id.in_([part.id for part in userActiveSemester().parts])
|
|
|
|
def create_form(self, obj=None):
|
|
form = self.CreateForm
|
|
return form(get_form_data(), obj=obj)
|
|
|
|
def on_model_change(self, form, model, is_created):
|
|
PartStudent.check(model.group, model.part)
|
|
|
|
def update_model(self, form, model):
|
|
if form.final_part_mark.data == -1:
|
|
form.final_part_mark.data = None
|
|
|
|
return super().update_model(form, model)
|
|
|
|
|
|
def partStudentQueryFactory():
|
|
return PartStudent.query.filter(PartStudent.part_id.in_([part.id for part in userActiveSemester().parts]))
|
|
|
|
|
|
class GroupView(SecureModelView):
|
|
def formFactory(create, group):
|
|
if create:
|
|
|
|
def query_factory():
|
|
return partStudentQueryFactory().filter(PartStudent.group == None)
|
|
|
|
else:
|
|
|
|
def query_factory():
|
|
return partStudentQueryFactory().filter(or_(PartStudent.group == None, PartStudent.group == group))
|
|
|
|
class CustomForm(Form):
|
|
part_students = QuerySelectMultipleField(
|
|
"Part Students", query_factory=query_factory, validators=[DataRequired()]
|
|
)
|
|
|
|
return CustomForm
|
|
|
|
column_list = ["number", "semester", "program", "part_students", "group_experiments"]
|
|
column_filters = ["number", "semester", "program"]
|
|
|
|
def queryFilter(self):
|
|
return Group.semester == userActiveSemester()
|
|
|
|
def customCreateModel(self, form):
|
|
return Group.customInit(form.part_students.data)
|
|
|
|
def update_model(self, form, model):
|
|
try:
|
|
Group.check(form.part_students.data, model.program)
|
|
except Exception as ex:
|
|
flash(str(ex), "error")
|
|
else:
|
|
return super().update_model(form, model)
|
|
|
|
def create_form(self, obj=None):
|
|
form = GroupView.formFactory(create=True, group=None)
|
|
return form(get_form_data(), obj=obj)
|
|
|
|
def edit_form(self, obj=None):
|
|
form = GroupView.formFactory(create=False, group=obj)
|
|
return form(get_form_data(), obj=obj)
|
|
|
|
|
|
class ExperimentView(SecureModelView):
|
|
can_view_details = True
|
|
|
|
column_filters = ["active"]
|
|
column_list = ["number", "program", "title", "active"]
|
|
column_details_list = column_list + [
|
|
"description",
|
|
"wiki_link",
|
|
"room",
|
|
"building",
|
|
"responsibility",
|
|
"duration_in_days",
|
|
"oral_weighting",
|
|
"protocol_weighting",
|
|
"final_weighting",
|
|
"semester_experiments",
|
|
]
|
|
column_editable_list = ["active"]
|
|
|
|
form_columns = column_details_list
|
|
|
|
form_args = {"wiki_link": {"validators": [URL()]}}
|
|
form_extra_fields = {
|
|
"program": QuerySelectField(
|
|
"Program", query_factory=programQueryFactory, validators=[DataRequired()], allow_blank=True, blank_text="-"
|
|
)
|
|
}
|
|
|
|
|
|
def assistantQueryFactory():
|
|
return Assistant.query.filter(Assistant.user_id.in_([user.id for user in User.query.filter(User.active == True)]))
|
|
|
|
|
|
class SemesterExperimentView(SecureModelView):
|
|
class CreateForm(Form):
|
|
def experimentQueryFactory():
|
|
return Experiment.query.filter(Experiment.active == True)
|
|
|
|
experiment = QuerySelectField(
|
|
"Experiment",
|
|
query_factory=experimentQueryFactory,
|
|
validators=[DataRequired()],
|
|
allow_blank=True,
|
|
blank_text="-",
|
|
)
|
|
|
|
assistants = QuerySelectMultipleField("Assistants", query_factory=assistantQueryFactory)
|
|
|
|
form = CreateForm
|
|
|
|
can_view_details = True
|
|
|
|
column_list = ["experiment", "assistants", "semester"]
|
|
column_details_list = column_list + ["group_experiments"]
|
|
column_filters = ["experiment"]
|
|
|
|
def queryFilter(self):
|
|
return SemesterExperiment.semester == userActiveSemester()
|
|
|
|
def customCreateModel(self, form):
|
|
return SemesterExperiment(
|
|
semester=userActiveSemester(), experiment=form.experiment.data, assistants=form.assistants.data
|
|
)
|
|
|
|
|
|
class AssistantView(SecureModelView):
|
|
def assistantUserQueryFactory():
|
|
return User.query.filter(User.roles.any(Role.name == "assistant"))
|
|
|
|
can_view_details = True
|
|
|
|
column_list = ["first_name", "last_name", "user", "semester_experiments"]
|
|
column_details_list = column_list + [
|
|
"phone_number",
|
|
"mobile_phone_number",
|
|
"room",
|
|
"building",
|
|
"appointments",
|
|
"experiment_marks",
|
|
]
|
|
column_searchable_list = ["first_name", "last_name", "user.email"]
|
|
column_filters = ["user.active"]
|
|
form_excluded_columns = ["experiment_marks", "appointments", "semester_experiments", "user"]
|
|
|
|
form_extra_fields = {
|
|
"user": QuerySelectField(
|
|
"User",
|
|
query_factory=assistantUserQueryFactory,
|
|
validators=[DataRequired()],
|
|
allow_blank=True,
|
|
blank_text="-",
|
|
)
|
|
}
|
|
|
|
|
|
assistantBlankText = "Auto assign if experiment has only one assistant"
|
|
|
|
|
|
class GroupExperimentView(SecureModelView):
|
|
class CreateForm(Form):
|
|
def semesterExperimentQueryFactory():
|
|
return SemesterExperiment.query.filter(SemesterExperiment.semester == userActiveSemester())
|
|
|
|
group = QuerySelectField(
|
|
"Group",
|
|
query_factory=groupQueryFactory,
|
|
validators=[DataRequired()],
|
|
allow_blank=True,
|
|
blank_text="-",
|
|
)
|
|
semester_experiment = QuerySelectField(
|
|
"Semester Experiment",
|
|
query_factory=semesterExperimentQueryFactory,
|
|
validators=[DataRequired()],
|
|
allow_blank=True,
|
|
blank_text="-",
|
|
)
|
|
|
|
appointment1_date = DateField("Appointment-1 Date", validators=[Optional()])
|
|
appointment1_special = BooleanField("Appointment-1 Special", default=False)
|
|
appointment1_assistant = QuerySelectField(
|
|
"Appointment-1 Assistant",
|
|
query_factory=assistantQueryFactory,
|
|
allow_blank=True,
|
|
blank_text=assistantBlankText,
|
|
)
|
|
|
|
appointment2_date = DateField("Appointment-2 Date", validators=[Optional()])
|
|
appointment2_special = BooleanField("Appointment-2 Special", default=False)
|
|
appointment2_assistant = QuerySelectField(
|
|
"Appointment-2 Assistant",
|
|
query_factory=assistantQueryFactory,
|
|
allow_blank=True,
|
|
blank_text=assistantBlankText,
|
|
)
|
|
|
|
form = CreateForm
|
|
|
|
can_edit = False
|
|
|
|
column_list = ["group", "semester_experiment", "appointments", "experiment_marks"]
|
|
column_filters = ["group", "semester_experiment.experiment", "appointments"]
|
|
|
|
def queryFilter(self):
|
|
return GroupExperiment.group_id.in_(
|
|
[group.id for group in Group.query.filter(Group.semester == userActiveSemester())]
|
|
)
|
|
|
|
def customCreateModel(self, form):
|
|
return GroupExperiment.customInit(semester_experiment=form.semester_experiment.data, group=form.group.data)
|
|
|
|
def on_model_change(self, form, model, is_created):
|
|
if is_created:
|
|
for date, special, assistant in zip(
|
|
[form.appointment1_date.data, form.appointment2_date.data],
|
|
[form.appointment1_special.data, form.appointment2_special.data],
|
|
[form.appointment1_assistant.data, form.appointment2_assistant.data],
|
|
):
|
|
if date:
|
|
appointment = Appointment.customInit(date, special, assistant, model)
|
|
|
|
self.session.add(appointment)
|
|
|
|
def after_model_change(self, form, model, is_created):
|
|
if is_created:
|
|
if model.appointments:
|
|
flash(f"Appointments {model.appointments} added.", "success")
|
|
|
|
|
|
def groupExperimentQueryFactory():
|
|
return GroupExperiment.query.filter(
|
|
GroupExperiment.semester_experiment_id.in_(
|
|
[
|
|
semesterExperiment.id
|
|
for semesterExperiment in SemesterExperiment.query.filter(
|
|
SemesterExperiment.semester == userActiveSemester()
|
|
)
|
|
]
|
|
)
|
|
)
|
|
|
|
|
|
class AppointmentView(SecureModelView):
|
|
class CreateForm(Form):
|
|
group_experiment = QuerySelectField(
|
|
"Group Experiment",
|
|
query_factory=groupExperimentQueryFactory,
|
|
validators=[DataRequired()],
|
|
allow_blank=True,
|
|
blank_text="-",
|
|
)
|
|
date = DateField("Date", validators=[DataRequired()])
|
|
special = BooleanField("Special", default=False)
|
|
assistant = QuerySelectField(
|
|
"Assistant",
|
|
query_factory=assistantQueryFactory,
|
|
allow_blank=True,
|
|
blank_text=assistantBlankText,
|
|
)
|
|
|
|
form = CreateForm
|
|
|
|
column_filters = [
|
|
"date",
|
|
"special",
|
|
"group_experiment.group",
|
|
"group_experiment.semester_experiment.experiment",
|
|
"assistant",
|
|
]
|
|
|
|
def queryFilter(self):
|
|
return Appointment.group_experiment_id.in_(
|
|
groupExperiment.id for groupExperiment in groupExperimentQueryFactory()
|
|
)
|
|
|
|
def customCreateModel(self, form):
|
|
return Appointment.customInit(
|
|
form.date.data, form.special.data, form.assistant.data, form.group_experiment.data
|
|
)
|
|
|
|
def update_model(self, form, model):
|
|
try:
|
|
model.customUpdate(form.date.data, form.special.data, form.assistant.data, form.group_experiment.data)
|
|
|
|
self.on_model_change(form, model, False)
|
|
self.session.commit()
|
|
except Exception as ex:
|
|
flash(str(ex), "error")
|
|
|
|
self.session.rollback()
|
|
|
|
return False
|
|
else:
|
|
self.after_model_change(form, model, False)
|
|
return True
|
|
|
|
|
|
class ExperimentMarkView(SecureModelView):
|
|
class StudentIdFilter(BaseSQLAFilter):
|
|
def apply(self, query, value, alias=None):
|
|
return query.filter(self.column == value)
|
|
|
|
def operation(self):
|
|
return "equals"
|
|
|
|
def validate(self, value):
|
|
if Student.query.get(value):
|
|
return True
|
|
else:
|
|
return False
|
|
|
|
class CreateForm(Form):
|
|
part_student = QuerySelectField(
|
|
"Part Student",
|
|
query_factory=partStudentQueryFactory,
|
|
validators=[DataRequired()],
|
|
allow_blank=True,
|
|
blank_text="-",
|
|
)
|
|
group_experiment = QuerySelectField(
|
|
"Group Experiment",
|
|
query_factory=groupExperimentQueryFactory,
|
|
validators=[DataRequired()],
|
|
allow_blank=True,
|
|
blank_text="-",
|
|
)
|
|
|
|
oral_mark = SelectField("Oral Mark", choices=markChoices, coerce=int)
|
|
protocol_mark = SelectField("Protocol Mark", choices=markChoices, coerce=int)
|
|
|
|
form = CreateForm
|
|
|
|
column_filters = [
|
|
StudentIdFilter(PartStudent.id, "Student / ID"),
|
|
"part_student.student",
|
|
"group_experiment.semester_experiment.semester",
|
|
"group_experiment.semester_experiment.experiment",
|
|
"assistant",
|
|
"edited_by_admin",
|
|
]
|
|
|
|
def queryFilter(self=None):
|
|
return ExperimentMark.group_experiment_id.in_(
|
|
groupExperiment.id for groupExperiment in groupExperimentQueryFactory()
|
|
)
|
|
|
|
def checkForm(form):
|
|
if form.oral_mark.data == -1:
|
|
form.oral_mark.data = None
|
|
|
|
if form.protocol_mark.data == -1:
|
|
form.protocol_mark.data = None
|
|
|
|
return form
|
|
|
|
def create_model(self, form):
|
|
form = ExperimentMarkView.checkForm(form)
|
|
|
|
return super().create_model(form)
|
|
|
|
def update_model(self, form, model):
|
|
form = ExperimentMarkView.checkForm(form)
|
|
|
|
return super().update_model(form, model)
|
|
|
|
def after_model_change(self, form, model, is_created):
|
|
if model.oral_mark or model.protocol_mark:
|
|
try:
|
|
model.edited_by_admin = True
|
|
|
|
self.session.commit()
|
|
except Exception as ex:
|
|
flash(str(ex), "error")
|
|
|
|
self.session.rollback()
|
|
|
|
|
|
class ProgramView(SecureModelView):
|
|
can_view_details = True
|
|
|
|
column_list = ["label"]
|
|
form_excluded_columns = ["parts", "experiments", "groups"]
|
|
column_details_list = column_list + form_excluded_columns
|
|
|
|
|
|
admin.add_view(StudentView(Student, db.session))
|
|
admin.add_view(PartStudentView(PartStudent, db.session))
|
|
admin.add_view(GroupView(Group, db.session))
|
|
admin.add_view(GroupExperimentView(GroupExperiment, db.session))
|
|
admin.add_view(AppointmentView(Appointment, db.session))
|
|
admin.add_view(ExperimentMarkView(ExperimentMark, db.session))
|
|
admin.add_view(ExperimentView(Experiment, db.session))
|
|
admin.add_view(SemesterExperimentView(SemesterExperiment, db.session))
|
|
admin.add_view(AssistantView(Assistant, db.session))
|
|
admin.add_view(ProgramView(Program, db.session))
|
|
admin.add_view(PartView(Part, db.session))
|
|
admin.add_view(SemesterView(Semester, db.session))
|
|
admin.add_view(UserView(User, db.session))
|
|
admin.add_view(RoleView(Role, db.session))
|
|
|
|
with app.app_context():
|
|
semesters = Semester.query.order_by(Semester.id)
|
|
for semester in semesters:
|
|
admin.add_link(
|
|
MenuLink(
|
|
name=semester.repr(),
|
|
url=url_for("set_semester") + "?semester_id=" + str(semester.id),
|
|
category="Active semester",
|
|
)
|
|
)
|
|
|
|
admin.add_link(MenuLink(name="Logout", url=url_for("security.logout")))
|