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
|
from pathlib import Path
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
from flask import flash, has_request_context, redirect, request, url_for
|
from flask import flash, has_request_context, redirect, request, url_for
|
||||||
from flask_admin import expose
|
from flask_admin import expose
|
||||||
from flask_admin.contrib.sqla.fields import QuerySelectField, QuerySelectMultipleField
|
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_security import admin_change_password, current_user, hash_password
|
||||||
from flask_wtf import FlaskForm
|
from flask_wtf import FlaskForm
|
||||||
from flask_wtf.file import FileAllowed, FileField, FileRequired
|
from flask_wtf.file import FileAllowed, FileField, FileRequired
|
||||||
|
from matplotlib.figure import Figure
|
||||||
from sqlalchemy import and_, func, or_
|
from sqlalchemy import and_, func, or_
|
||||||
from werkzeug.utils import secure_filename
|
from werkzeug.utils import secure_filename
|
||||||
from wtforms import Form
|
from wtforms import Form
|
||||||
|
@ -36,6 +40,8 @@ from advlabdb.customClasses import (
|
||||||
from advlabdb.database_import import importFromFile
|
from advlabdb.database_import import importFromFile
|
||||||
from advlabdb.exceptions import DataBaseException, ModelViewException
|
from advlabdb.exceptions import DataBaseException, ModelViewException
|
||||||
from advlabdb.models import (
|
from advlabdb.models import (
|
||||||
|
MAX_MARK,
|
||||||
|
MIN_MARK,
|
||||||
Admin,
|
Admin,
|
||||||
Appointment,
|
Appointment,
|
||||||
Assistant,
|
Assistant,
|
||||||
|
@ -1270,20 +1276,20 @@ class ExperimentMarkView(SecureAdminModelView):
|
||||||
class EditForm(Form):
|
class EditForm(Form):
|
||||||
oral_mark = IntegerField(
|
oral_mark = IntegerField(
|
||||||
"Oral Mark",
|
"Oral Mark",
|
||||||
validators=[NumberRange(min=0, max=15), Optional()],
|
validators=[NumberRange(min=MIN_MARK, max=MAX_MARK), Optional()],
|
||||||
description="Between 0 and 15",
|
description=f"Between {MIN_MARK} and {MAX_MARK}",
|
||||||
)
|
)
|
||||||
protocol_mark = IntegerField(
|
protocol_mark = IntegerField(
|
||||||
"Protocol Mark",
|
"Protocol Mark",
|
||||||
validators=[NumberRange(min=0, max=15), Optional()],
|
validators=[NumberRange(min=MIN_MARK, max=MAX_MARK), Optional()],
|
||||||
description="Between 0 and 15",
|
description=f"Between {MIN_MARK} and {MAX_MARK}",
|
||||||
)
|
)
|
||||||
|
|
||||||
form = EditForm
|
form = EditForm
|
||||||
|
|
||||||
column_descriptions = {
|
column_descriptions = {
|
||||||
"oral_mark": "Between 0 and 15",
|
"oral_mark": f"Between {MIN_MARK} and {MAX_MARK}",
|
||||||
"protocol_mark": "Between 0 and 15",
|
"protocol_mark": f"Between {MIN_MARK} and {MAX_MARK}",
|
||||||
"final_experiment_mark": "Calculated automatically with oral and protocol marks and weightings",
|
"final_experiment_mark": "Calculated automatically with oral and protocol marks and weightings",
|
||||||
"assistant": "The last assistant who edited the mark",
|
"assistant": "The last assistant who edited the mark",
|
||||||
"admin": "The last admin 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)
|
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):
|
class DocsView(SecureAdminBaseView):
|
||||||
@expose("/")
|
@expose("/")
|
||||||
def index(self):
|
def index(self):
|
||||||
|
@ -1452,6 +1517,7 @@ adminSpace.add_view(RoleView(Role, db.session))
|
||||||
adminSpace.add_view(ProgramView(Program, db.session))
|
adminSpace.add_view(ProgramView(Program, db.session))
|
||||||
adminSpace.add_view(ImportView(name="Import"))
|
adminSpace.add_view(ImportView(name="Import"))
|
||||||
adminSpace.add_view(ActionsView(name="Actions"))
|
adminSpace.add_view(ActionsView(name="Actions"))
|
||||||
|
adminSpace.add_view(AnalysisView(name="Analysis"))
|
||||||
adminSpace.add_view(DocsView(name="Docs"))
|
adminSpace.add_view(DocsView(name="Docs"))
|
||||||
|
|
||||||
initActiveSemesterMenuLinks(adminSpace)
|
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