mirror of
https://codeberg.org/Mo8it/AdvLabDB.git
synced 2024-12-20 23:41:20 +00:00
Add analysis
This commit is contained in:
parent
e891fe4050
commit
b33046dbc3
3 changed files with 90 additions and 6 deletions
|
@ -1,5 +1,8 @@
|
|||
from base64 import b64encode
|
||||
from io import BytesIO
|
||||
from pathlib import Path
|
||||
|
||||
import numpy as np
|
||||
from flask import flash, has_request_context, redirect, request, url_for
|
||||
from flask_admin import expose
|
||||
from flask_admin.contrib.sqla.fields import QuerySelectField, QuerySelectMultipleField
|
||||
|
@ -10,6 +13,7 @@ from flask_admin.model.template import EndpointLinkRowAction
|
|||
from flask_security import admin_change_password, current_user, hash_password
|
||||
from flask_wtf import FlaskForm
|
||||
from flask_wtf.file import FileAllowed, FileField, FileRequired
|
||||
from matplotlib.figure import Figure
|
||||
from sqlalchemy import and_, func, or_
|
||||
from werkzeug.utils import secure_filename
|
||||
from wtforms import Form
|
||||
|
@ -36,6 +40,8 @@ from advlabdb.customClasses import (
|
|||
from advlabdb.database_import import importFromFile
|
||||
from advlabdb.exceptions import DataBaseException, ModelViewException
|
||||
from advlabdb.models import (
|
||||
MAX_MARK,
|
||||
MIN_MARK,
|
||||
Admin,
|
||||
Appointment,
|
||||
Assistant,
|
||||
|
@ -1270,20 +1276,20 @@ class ExperimentMarkView(SecureAdminModelView):
|
|||
class EditForm(Form):
|
||||
oral_mark = IntegerField(
|
||||
"Oral Mark",
|
||||
validators=[NumberRange(min=0, max=15), Optional()],
|
||||
description="Between 0 and 15",
|
||||
validators=[NumberRange(min=MIN_MARK, max=MAX_MARK), Optional()],
|
||||
description=f"Between {MIN_MARK} and {MAX_MARK}",
|
||||
)
|
||||
protocol_mark = IntegerField(
|
||||
"Protocol Mark",
|
||||
validators=[NumberRange(min=0, max=15), Optional()],
|
||||
description="Between 0 and 15",
|
||||
validators=[NumberRange(min=MIN_MARK, max=MAX_MARK), Optional()],
|
||||
description=f"Between {MIN_MARK} and {MAX_MARK}",
|
||||
)
|
||||
|
||||
form = EditForm
|
||||
|
||||
column_descriptions = {
|
||||
"oral_mark": "Between 0 and 15",
|
||||
"protocol_mark": "Between 0 and 15",
|
||||
"oral_mark": f"Between {MIN_MARK} and {MAX_MARK}",
|
||||
"protocol_mark": f"Between {MIN_MARK} and {MAX_MARK}",
|
||||
"final_experiment_mark": "Calculated automatically with oral and protocol marks and weightings",
|
||||
"assistant": "The last assistant who edited the mark",
|
||||
"admin": "The last admin who edited the mark",
|
||||
|
@ -1429,6 +1435,65 @@ class ActionsView(SecureAdminBaseView):
|
|||
return self.render("actions.html", form=form)
|
||||
|
||||
|
||||
class AnalysisView(SecureAdminBaseView):
|
||||
class AnalysisForm(FlaskForm):
|
||||
assistantMarksSubmit = SubmitField(
|
||||
label="Assistant's marks",
|
||||
)
|
||||
|
||||
def markHist(data, title):
|
||||
fig = Figure()
|
||||
ax = fig.subplots()
|
||||
ax.set_title(title)
|
||||
ax.set_xlim(MIN_MARK - 0.5, MAX_MARK + 0.5)
|
||||
ax.set_xticks(np.arange(MAX_MARK + 1))
|
||||
ax.set_xlabel("Mark")
|
||||
|
||||
if data:
|
||||
hist = ax.hist(
|
||||
data,
|
||||
bins=np.arange(MAX_MARK) - 0.5,
|
||||
)
|
||||
ax.set_yticks(np.arange(len(data) + 1))
|
||||
else:
|
||||
ax.set_yticks(np.arange(2))
|
||||
|
||||
buf = BytesIO()
|
||||
fig.savefig(buf, format="png")
|
||||
|
||||
return b64encode(buf.getbuffer()).decode("ascii")
|
||||
|
||||
def markHists(markType, activeAssistants):
|
||||
attr = markType.lower() + "_mark"
|
||||
return [
|
||||
AnalysisView.markHist(
|
||||
data=[getattr(experimentMark, attr) for experimentMark in assistant.experiment_marks],
|
||||
title=f"{assistant.repr()} - {markType} Marks",
|
||||
)
|
||||
for assistant in activeAssistants
|
||||
]
|
||||
|
||||
@expose(methods=("GET", "POST"))
|
||||
def index(self):
|
||||
form = AnalysisView.AnalysisForm()
|
||||
|
||||
if form.validate_on_submit():
|
||||
if form.assistantMarksSubmit.data:
|
||||
activeAssistants = Assistant.query.filter(Assistant.user.has(User.active == True))
|
||||
|
||||
oralMarkHists = AnalysisView.markHists("Oral", activeAssistants)
|
||||
protocolMarkHists = AnalysisView.markHists("Protocol", activeAssistants)
|
||||
|
||||
return self.render(
|
||||
"analysis/assistant_marks.html",
|
||||
histIndices=range(len(oralMarkHists)),
|
||||
oralMarkHists=oralMarkHists,
|
||||
protocolMarkHists=protocolMarkHists,
|
||||
)
|
||||
|
||||
return self.render("analysis/analysis.html", form=form)
|
||||
|
||||
|
||||
class DocsView(SecureAdminBaseView):
|
||||
@expose("/")
|
||||
def index(self):
|
||||
|
@ -1452,6 +1517,7 @@ adminSpace.add_view(RoleView(Role, db.session))
|
|||
adminSpace.add_view(ProgramView(Program, db.session))
|
||||
adminSpace.add_view(ImportView(name="Import"))
|
||||
adminSpace.add_view(ActionsView(name="Actions"))
|
||||
adminSpace.add_view(AnalysisView(name="Analysis"))
|
||||
adminSpace.add_view(DocsView(name="Docs"))
|
||||
|
||||
initActiveSemesterMenuLinks(adminSpace)
|
||||
|
|
13
advlabdb/templates/analysis/analysis.html
Normal file
13
advlabdb/templates/analysis/analysis.html
Normal file
|
@ -0,0 +1,13 @@
|
|||
{% from "macros.html" import information %}
|
||||
{% extends "admin/master.html" %}
|
||||
|
||||
{% block body %}
|
||||
{{information(current_user, userActiveSemester, role="admin")}}
|
||||
|
||||
<hr>
|
||||
|
||||
<form method="POST">
|
||||
{{ form.csrf_token }}
|
||||
{{ form.assistantMarksSubmit }}
|
||||
</form>
|
||||
{% endblock body %}
|
5
advlabdb/templates/analysis/assistant_marks.html
Normal file
5
advlabdb/templates/analysis/assistant_marks.html
Normal file
|
@ -0,0 +1,5 @@
|
|||
{% for histInd in histIndices %}
|
||||
<img src="data:image/png;base64,{{oralMarkHists[histInd]}}"\>
|
||||
<img src="data:image/png;base64,{{protocolMarkHists[histInd]}}"\>
|
||||
<hr>
|
||||
{% endfor %}
|
Loading…
Reference in a new issue