# -*- coding: utf-8 -*-
"""
Report module.
All functions/views dealing with report. Defines the report blueprint
"""
import os
import shutil
import typing
from werkzeug.wrappers.response import Response
from flask import Blueprint, flash, g, redirect, render_template, url_for, current_app, abort
# from werkzeug.exceptions import HTTPException, abort
import ResearchNotes.database_transactions as dbt
from ResearchNotes.auth import login_required
from ResearchNotes.database import db, Measurements, Reports
from ResearchNotes.form import ReportCreateForm
from ResearchNotes.files import uploaddir_path, make_file_list
from ResearchNotes.url_security import token_decode
bp = Blueprint("report", __name__, url_prefix="/reports")
[docs]@bp.route("/<int:mid>/create", methods=("GET", "POST"))
@login_required
def create(mid: int) -> typing.Union[str, Response]:
"""
Create a report in a given PMM for the PMM id.
Parameters
----------
mid : int
PPM ID for which the report is created.
Returns
-------
str|Flask.Response
Render create report template or redirect to report view.
"""
measurement = db.get_or_404(Measurements, mid)
form = ReportCreateForm()
if form.validate_on_submit():
new_report = {
"creator_id": g.user.id,
"title": form.title.data,
"long_dis": form.long_dis.data,
"creator": g.user.UserName,
"sample_id": measurement.sample_id,
"measurement_id": mid,
}
r_id = dbt.create_report(db, new_report)
flash(f"Report {new_report['title']} created", "alert-info")
return redirect(url_for("report.report_view", rid=r_id))
return render_template("report/create.html", form=form, measurement=measurement)
[docs]def get_report(rid: int, check_author: bool = True) -> Reports:
"""
Get report from database.
Retrieve report ID from database. Check, if user has the right to access the
database entry first
Parameters
----------
rid : int
Report ID in database.
check_author : TYPE, optional
DESCRIPTION. The default is True.
Returns
-------
report : ResearchNotes.Report
Report entry into database.
"""
report = db.get_or_404(
Reports,
rid,
description=f" : get_report : User {g.user} tried to load report {rid} which does not exist",
)
if (
not current_app.config["ESS_PER_GROUP"] and int(report.reports_sample.creator_id) != g.user.id
) and g.user.id not in report.reports_sample.sharedsamplelist:
abort(
403,
description=f" : get_report : Authorization failure. User {g.user} tried to load report {rid}",
)
elif (
current_app.config["ESS_PER_GROUP"]
and int(report.reports_sample.group_id) != g.user.group_id
and g.user.id not in report.reports_sample.sharedsamplelist
):
abort(
403,
description=f" : get_report : Authorization failure. User {g.user} tried to load report {rid}",
)
return report
[docs]@bp.route("/<int:rid>/update", methods=("GET", "POST"))
@login_required
def update(rid: int) -> typing.Union[str, Response]:
"""
Update an existing report in the database.
First retrieve it, then put the old
values into the Form. Finally, write new values to the database and update
modified date.
Parameters
----------
rid : int
Report ID.
Returns
-------
str|Flask.Response
Render update form or redirect to report view.
"""
report = get_report(rid)
form = ReportCreateForm(obj=report)
if form.validate_on_submit():
dbt.update_report(db, report, {"title": form.title.data, "long_dis": form.long_dis.data})
flash("Report information updated", "alert-info")
return redirect(url_for("report.report_view", rid=rid))
return render_template(
"report/create.html", form=form, report=report, measurement=report.report_measurement
)
[docs]@bp.route("/<int:rid>/report", methods=("GET", "POST"))
@login_required
def report_view(rid: int) -> str:
"""
Display report and associated files.
Parameters
----------
rid : int
Report ID
Returns
-------
str
Renders report view template.
"""
report = get_report(rid)
files = make_file_list(
uploaddir_path(["r", report.id, report.sample_id, report.reports_sample.identifier])
)
return render_template(
"report/report.html",
report=report,
files=files,
)
def _delete_report(report) -> typing.Optional[Exception]:
"""
Delete a report entry - mainly used by sample.delete and measurement.delete.
Sub-function to actually delete the report and all files associated with it.
Parameters
----------
report : ResearchNote.Report
Report object.
Returns
-------
error : None|str
Returns None at success or the error code string (abort).
"""
error = None # type: None
path = uploaddir_path(["r", report.id, report.sample_id, report.reports_sample.identifier])
if os.path.exists(path):
try:
shutil.rmtree(path)
except shutil.Error as error:
abort(500, description=f"Failed to delete {path}. Exception {error}")
#
with dbt.Transaction(db) as db_session:
db_session.delete(report)
return error
[docs]@bp.route("/delete/<string:token>")
@login_required
def delete(token: str) -> Response:
"""
Delete report entry.
Unlike samples, we have don't have to implement a cascade of deletions
and cross-references since there are no "one report to many" database
relationships.
Parameters
----------
token : str
Signed string that encodes the id of the report to delete
Returns
-------
Flask.Response
Redirect to measurement view (if _delete not does an abort).
"""
rid = token_decode(token, current_app.config["SEC_SESSION_KEY"], g.salt)
report = get_report(rid)
mid = report.measurement_id
error = _delete_report(report)
if error is None:
flash(f"Deleted report {report.title}", "alert-info")
return redirect(url_for("measurements.measurement_view", mid=mid))