2021-07-30 12:20:54 +00:00
|
|
|
from flask import flash, redirect, request, url_for
|
2022-02-23 23:08:14 +00:00
|
|
|
from flask_admin import AdminIndexView, BaseView, expose
|
2021-05-17 14:55:04 +00:00
|
|
|
from flask_admin.contrib.sqla import ModelView
|
2022-03-12 23:37:31 +00:00
|
|
|
from flask_admin.model.helpers import get_mdict_item_or_list
|
2022-03-04 02:49:02 +00:00
|
|
|
from flask_admin.model.template import EndpointLinkRowAction
|
2021-06-02 21:43:41 +00:00
|
|
|
from flask_security import current_user
|
2022-02-24 00:10:04 +00:00
|
|
|
from sqlalchemy import and_
|
2021-07-13 15:22:15 +00:00
|
|
|
|
2021-07-30 12:20:54 +00:00
|
|
|
from advlabdb.exceptions import DataBaseException, ModelViewException
|
2022-02-27 16:43:31 +00:00
|
|
|
from advlabdb.models import (
|
|
|
|
Assistant,
|
|
|
|
ExperimentMark,
|
|
|
|
GroupExperiment,
|
|
|
|
Part,
|
|
|
|
PartStudent,
|
|
|
|
SemesterExperiment,
|
|
|
|
)
|
2022-02-23 23:08:14 +00:00
|
|
|
from advlabdb.utils import reportBadAttempt, userActiveSemester
|
2021-05-17 14:55:04 +00:00
|
|
|
|
2021-04-18 23:33:46 +00:00
|
|
|
|
2021-05-17 14:55:04 +00:00
|
|
|
def adminViewIsAccessible():
|
|
|
|
return current_user.has_role("admin")
|
|
|
|
|
2021-04-18 23:33:46 +00:00
|
|
|
|
2021-07-30 00:03:44 +00:00
|
|
|
def assistantViewIsAccessible():
|
|
|
|
return current_user.has_role("assistant")
|
2021-05-17 14:55:04 +00:00
|
|
|
|
2021-07-30 00:03:44 +00:00
|
|
|
|
|
|
|
class CustomIndexView(AdminIndexView):
|
2021-07-01 12:02:23 +00:00
|
|
|
def inaccessible_callback(self, name, **kwargs):
|
|
|
|
# Redirect to login page if user doesn't have access
|
|
|
|
return redirect(url_for("security.login", next=request.url))
|
|
|
|
|
2021-04-18 23:33:46 +00:00
|
|
|
|
2021-07-30 00:03:44 +00:00
|
|
|
class SecureAdminIndexView(CustomIndexView):
|
|
|
|
def is_accessible(self):
|
|
|
|
return adminViewIsAccessible()
|
|
|
|
|
2022-03-04 02:49:02 +00:00
|
|
|
@expose("/")
|
2022-02-23 23:08:14 +00:00
|
|
|
def index(self):
|
2022-02-24 00:10:04 +00:00
|
|
|
active_semester_experiment_marks_query = ExperimentMark.query.filter(
|
|
|
|
ExperimentMark.part_student.has(PartStudent.part.has(Part.semester == userActiveSemester()))
|
|
|
|
)
|
|
|
|
|
|
|
|
number_of_all_experiment_marks = active_semester_experiment_marks_query.count()
|
|
|
|
|
|
|
|
number_of_missing_final_experiment_marks = active_semester_experiment_marks_query.filter(
|
|
|
|
ExperimentMark.final_experiment_mark == None
|
2022-02-23 23:08:14 +00:00
|
|
|
).count()
|
|
|
|
|
|
|
|
return self.render(
|
2022-02-24 00:10:04 +00:00
|
|
|
"admin_index.html",
|
|
|
|
number_of_missing_final_experiment_marks=number_of_missing_final_experiment_marks,
|
|
|
|
number_of_all_experiment_marks=number_of_all_experiment_marks,
|
2022-02-23 23:08:14 +00:00
|
|
|
)
|
|
|
|
|
2021-07-30 00:03:44 +00:00
|
|
|
|
|
|
|
class SecureAssistantIndexView(CustomIndexView):
|
|
|
|
def is_accessible(self):
|
|
|
|
return assistantViewIsAccessible()
|
|
|
|
|
2022-03-04 02:49:02 +00:00
|
|
|
@expose("/")
|
2022-02-24 00:10:04 +00:00
|
|
|
def index(self):
|
|
|
|
active_semester_experiment_marks_query = ExperimentMark.query.filter(
|
|
|
|
ExperimentMark.group_experiment.has(
|
|
|
|
GroupExperiment.semester_experiment.has(
|
|
|
|
and_(
|
|
|
|
SemesterExperiment.semester == userActiveSemester(),
|
|
|
|
SemesterExperiment.assistants.any(Assistant.user == current_user),
|
|
|
|
)
|
|
|
|
)
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
number_of_all_experiment_marks = active_semester_experiment_marks_query.count()
|
|
|
|
|
|
|
|
number_of_missing_final_experiment_marks = active_semester_experiment_marks_query.filter(
|
|
|
|
ExperimentMark.final_experiment_mark == None
|
|
|
|
).count()
|
|
|
|
|
|
|
|
return self.render(
|
|
|
|
"assistant_index.html",
|
|
|
|
number_of_missing_final_experiment_marks=number_of_missing_final_experiment_marks,
|
|
|
|
number_of_all_experiment_marks=number_of_all_experiment_marks,
|
|
|
|
)
|
|
|
|
|
2021-07-30 00:03:44 +00:00
|
|
|
|
|
|
|
class CustomModelView(ModelView):
|
2021-07-01 11:12:43 +00:00
|
|
|
create_modal = True
|
|
|
|
edit_modal = True
|
|
|
|
details_modal = True
|
|
|
|
|
2022-03-12 23:37:31 +00:00
|
|
|
can_view_details = False
|
|
|
|
|
2021-07-12 11:06:44 +00:00
|
|
|
queryFilter = None
|
2021-07-29 22:24:10 +00:00
|
|
|
customCreateModel = None
|
2021-07-12 11:06:44 +00:00
|
|
|
|
2021-07-01 12:02:23 +00:00
|
|
|
def inaccessible_callback(self, name, **kwargs):
|
|
|
|
# Redirect to login page if user doesn't have access
|
|
|
|
return redirect(url_for("security.login", next=request.url))
|
2021-07-12 11:06:44 +00:00
|
|
|
|
|
|
|
def get_query(self):
|
|
|
|
if self.queryFilter:
|
|
|
|
return super().get_query().filter(self.queryFilter())
|
|
|
|
else:
|
|
|
|
return super().get_query()
|
|
|
|
|
|
|
|
def get_count_query(self):
|
|
|
|
if self.queryFilter:
|
|
|
|
return super().get_count_query().filter(self.queryFilter())
|
|
|
|
else:
|
|
|
|
return super().get_count_query()
|
2021-07-13 15:22:15 +00:00
|
|
|
|
|
|
|
def handle_view_exception(self, exc):
|
2021-07-13 23:58:35 +00:00
|
|
|
if type(exc) in (ModelViewException, DataBaseException):
|
2021-07-13 15:22:15 +00:00
|
|
|
flash(str(exc), "error")
|
|
|
|
return True
|
|
|
|
|
|
|
|
return super().handle_view_exception(exc)
|
2021-07-29 22:24:10 +00:00
|
|
|
|
|
|
|
def create_model(self, form):
|
|
|
|
if not self.customCreateModel:
|
|
|
|
return super().create_model(form)
|
|
|
|
else:
|
|
|
|
try:
|
|
|
|
model = self.customCreateModel(form)
|
|
|
|
|
|
|
|
self.session.add(model)
|
|
|
|
|
|
|
|
self.on_model_change(form, model, True)
|
|
|
|
|
|
|
|
self.session.commit()
|
|
|
|
except Exception as ex:
|
|
|
|
flash(str(ex), "error")
|
|
|
|
|
|
|
|
self.session.rollback()
|
|
|
|
else:
|
|
|
|
self.after_model_change(form, model, True)
|
|
|
|
|
|
|
|
return model
|
2021-07-30 00:03:44 +00:00
|
|
|
|
|
|
|
|
|
|
|
class SecureAdminModelView(CustomModelView):
|
2021-11-30 00:36:19 +00:00
|
|
|
can_export = True
|
|
|
|
can_set_page_size = True
|
|
|
|
|
|
|
|
can_create = True
|
|
|
|
can_edit = True
|
|
|
|
can_delete = True
|
|
|
|
column_display_actions = True
|
|
|
|
|
2021-07-30 00:03:44 +00:00
|
|
|
list_template = "admin_list.html"
|
|
|
|
create_template = "admin_create.html"
|
|
|
|
edit_template = "admin_edit.html"
|
2022-03-03 02:13:06 +00:00
|
|
|
details_template = "admin_details.html"
|
2021-07-30 00:03:44 +00:00
|
|
|
|
|
|
|
def is_accessible(self):
|
|
|
|
return adminViewIsAccessible()
|
|
|
|
|
|
|
|
|
|
|
|
class SecureAssistantModelView(CustomModelView):
|
2021-11-30 00:36:19 +00:00
|
|
|
can_export = False
|
|
|
|
can_set_page_size = False
|
|
|
|
|
|
|
|
can_edit = False
|
|
|
|
column_display_actions = False
|
|
|
|
|
2021-07-30 00:03:44 +00:00
|
|
|
list_template = "assistant_list.html"
|
|
|
|
create_template = "assistant_create.html"
|
|
|
|
edit_template = "assistant_edit.html"
|
2022-03-03 02:14:13 +00:00
|
|
|
details_template = "assistant_details.html"
|
2021-07-30 00:03:44 +00:00
|
|
|
|
2022-03-12 23:37:31 +00:00
|
|
|
"""
|
|
|
|
Every variable and method defined below in this class except queryFilter is NOT ALLOWED TO BE (completely) OVERWRITTEN!
|
|
|
|
You can only extend the methods.
|
|
|
|
queryFilter has to be implemented by overriding it.
|
|
|
|
This is because of security reasons!
|
|
|
|
"""
|
|
|
|
|
|
|
|
# Assistants are not allowed to create or delete.
|
|
|
|
can_create = False
|
|
|
|
can_delete = False
|
|
|
|
|
2021-07-30 00:03:44 +00:00
|
|
|
def is_accessible(self):
|
|
|
|
return assistantViewIsAccessible()
|
2021-09-11 18:48:14 +00:00
|
|
|
|
2021-11-30 00:36:19 +00:00
|
|
|
def queryFilter(self):
|
|
|
|
"""
|
|
|
|
A default filter has to be implemented to restrict assistants read/write access.
|
|
|
|
See on_model_change!
|
|
|
|
"""
|
2022-03-03 02:04:15 +00:00
|
|
|
raise NotImplementedError()
|
2021-11-30 00:36:19 +00:00
|
|
|
|
|
|
|
def on_model_change(self, form, model, is_created):
|
|
|
|
"""
|
|
|
|
This method uses the filter returned by queryFilter (which has to be implemented!) to prevent assistants
|
2022-02-13 19:05:29 +00:00
|
|
|
from modifying models not listed on their view by sending a POST request with a different id.
|
2021-11-30 00:36:19 +00:00
|
|
|
You can extend this method by implementing a custom on_model_change and then calling super().on_model_change within it.
|
|
|
|
"""
|
|
|
|
if is_created:
|
|
|
|
reportBadAttempt("An assistant tried to create a model!")
|
|
|
|
raise ModelViewException("Assistants can not create models!")
|
|
|
|
|
|
|
|
if model not in self.get_query():
|
|
|
|
reportBadAttempt("An assistant tried to change a model not in his filter!")
|
|
|
|
raise ModelViewException("Unauthorized action!")
|
|
|
|
|
|
|
|
def on_model_delete(self, model):
|
|
|
|
reportBadAttempt("An assistant tried to delete a model!")
|
|
|
|
raise ModelViewException("Assistants can not delete models!")
|
|
|
|
|
2022-03-12 23:37:31 +00:00
|
|
|
@expose("/edit/", methods=("GET", "POST"))
|
|
|
|
def edit_view(self):
|
|
|
|
"""
|
|
|
|
Prevent an assistant from seeing the edit form of a model not in his filter by using the GET method and entering an id.
|
|
|
|
This is important if can_edit is set to True.
|
|
|
|
"""
|
|
|
|
id = get_mdict_item_or_list(request.args, "id")
|
|
|
|
|
|
|
|
if id is not None:
|
|
|
|
model = self.get_one(id)
|
|
|
|
|
|
|
|
if model not in self.get_query():
|
|
|
|
reportBadAttempt("An assistant tried to edit a model not in his filter!")
|
|
|
|
raise ModelViewException("Unauthorized action!")
|
|
|
|
|
|
|
|
return super().edit_view()
|
|
|
|
|
|
|
|
@expose("/details/")
|
|
|
|
def details_view(self):
|
|
|
|
"""
|
|
|
|
Prevent an assistant from seeing the details of a model not in his filter by using the GET method and entering an id.
|
|
|
|
This is important if can_view_details is set to True.
|
|
|
|
"""
|
|
|
|
id = get_mdict_item_or_list(request.args, "id")
|
|
|
|
|
|
|
|
if id is not None:
|
|
|
|
model = self.get_one(id)
|
|
|
|
|
|
|
|
if model not in self.get_query():
|
|
|
|
reportBadAttempt("An assistant tried to see details of a model not in his filter!")
|
|
|
|
raise ModelViewException("Unauthorized action!")
|
|
|
|
|
|
|
|
return super().details_view()
|
|
|
|
|
|
|
|
def get_details_columns(self):
|
|
|
|
"""
|
|
|
|
Prevent showing unintended data if can_view_details is set to True and column_details_list was not set (which equals None).
|
|
|
|
"""
|
|
|
|
if not self.column_details_list:
|
|
|
|
self.column_details_list = self.column_list
|
|
|
|
|
|
|
|
return super().get_details_columns()
|
|
|
|
|
2021-09-11 18:48:14 +00:00
|
|
|
|
|
|
|
class SecureAdminBaseView(BaseView):
|
|
|
|
def is_accessible(self):
|
|
|
|
return adminViewIsAccessible()
|
2022-02-23 18:37:09 +00:00
|
|
|
|
|
|
|
|
|
|
|
class SecureAssistantBaseView(BaseView):
|
|
|
|
def is_accessible(self):
|
|
|
|
return assistantViewIsAccessible()
|
2022-03-03 02:04:15 +00:00
|
|
|
|
|
|
|
|
|
|
|
class CustomIdEndpointLinkRowAction(EndpointLinkRowAction):
|
|
|
|
def customId(self, row):
|
|
|
|
raise NotImplementedError()
|
|
|
|
|
|
|
|
def render(self, context, row_id, row):
|
|
|
|
return super().render(context, self.customId(row), row)
|