Source code for ResearchNotes.locations

# -*- coding: utf-8 -*-
"""
Location management modul.

Everything for locations and sub-location management goes here.

"""

import typing

from flask import (
    Blueprint,
    redirect,
    url_for,
    flash,
    render_template,
    current_app,
    g,
)
from werkzeug import Response


from ResearchNotes.database import db, ParentLocations, Locations

import ResearchNotes.database_transactions as dbt
from ResearchNotes.auth import role_required

from ResearchNotes.form import (
    ConfigCreateMType,
)

from ResearchNotes.url_security import token_decode

bp = Blueprint("locations", __name__, url_prefix="/locations")

# =======================================================================================================
# Parent location management
# =======================================================================================================


[docs]@bp.route("/register_location", methods=("GET", "POST")) @role_required(["Supervisor", "StudentAdmin"]) def register_parent_location() -> str | Response: """ Register a new location. Returns ------- str|Response """ exists = ( db.session.execute( db.select(ParentLocations).order_by("name").filter_by(group_id=g.user.group_id) ) .scalars() .all() ) form = ConfigCreateMType() if form.validate_on_submit(): location_name = form.mtype.data if location_name in [l.name for l in exists]: error = f"Location {location_name} is already registered." else: dbt.create_parent_location( db, { "name": location_name, "group_id": g.user.group_id, }, ) flash("Location created.", "alert-success") return redirect(url_for("conf.index")) flash(error, "alert-warning") return render_template("setup/registertype.html", form=form, existing=exists, what="Locations")
[docs]@bp.route("/update_location/<int:pl_id>", methods=("GET", "POST")) @role_required(["Supervisor", "StudentAdmin"]) def update_parent_location(pl_id: int) -> str | Response: """ Update a parent location name. Returns ------- str|Response """ exists = ( db.session.execute( db.select(ParentLocations).order_by("name").filter_by(group_id=g.user.group_id) ) .scalars() .all() ) p_location = db.get_or_404(ParentLocations, pl_id) form = ConfigCreateMType(data={"mtype": p_location.name}) if form.validate_on_submit(): new_name = form.mtype.data if not (new_name == "Not defined" or new_name in [l.name for l in exists]): with dbt.Transaction(db): p_location.name = new_name flash("Location updated.", "alert-success") return redirect(url_for("conf.index")) flash(f"Location {new_name} is already registered.", "alert-warning") return render_template("setup/registertype.html", form=form, existing=exists, what="Locations")
[docs]@bp.route("/delete_location/<string:token>", methods=["DELETE"]) @role_required(["Supervisor", "StudentAdmin"]) def htmx_delete_parent_location(token: str) -> str: """ Delete a parent location. On delete, we will move all the sub-locations to 'Not defined' leaving the rest untouched. Parameters ---------- token : str Signed string that encodes the id of the location to delete Returns ------- str Rendered location page. """ lid = token_decode(token, current_app.config["SEC_SESSION_KEY"], g.salt) location = db.session.get(ParentLocations, lid) current_app.logger.info(f" htmx : Delete parent location {location.name}") for member in location.sublocations: current_app.logger.debug(f"{member.name} will move to location 'Not defined'") with dbt.Transaction(db): member.parent_location_id = 1 if member.name == "None": current_app.logger.debug(f"'None' will be renamed {location.name} ") member.name = location.name with dbt.Transaction(db) as db_session: db_session.delete(location) locations = ( db.session.execute(db.select(ParentLocations).filter_by(name="Not defined")).scalars().all() + db.session.execute( db.select(ParentLocations).order_by("name").filter_by(group_id=g.user.group_id) ) .scalars() .all() ) current_app.logger.debug(" htmx : Parent location deleted") return render_template("setup/location_list.html", locations=locations)
# ======================================================================================================= # Sub-location management # =======================================================================================================
[docs]@bp.route("/register_sublocation/<int:pl_id>", methods=("GET", "POST")) @role_required(["Supervisor", "StudentAdmin"]) def register_location(pl_id: int) -> typing.Union[str, Response]: """ Register a new location. Returns ------- str|Response """ exists = [ l for l in db.session.get(ParentLocations, pl_id).sublocations if l.group_id == g.user.group_id or l.name == "None" ] form = ConfigCreateMType() if form.validate_on_submit(): location_name = form.mtype.data if location_name in [l.name for l in exists]: error = f"Sub-location {location_name} is already registered." else: dbt.create_location( db, { "name": location_name, "group_id": g.user.group_id, "parent_location_id": pl_id, }, ) flash("Sub-location created.", "alert-success") return redirect(url_for("conf.index")) flash(error, "alert-warning") return render_template("setup/registertype.html", form=form, existing=exists, what="Sub-Locations")
[docs]@bp.route("/update_sublocation/<int:lid>", methods=("GET", "POST")) @role_required(["Supervisor", "StudentAdmin"]) def update_location(lid: int) -> str | Response: """ Update a location name. Parameters ---------- lid : int Location ID to change name for. Returns ------- str|Response """ location = db.get_or_404(Locations, lid) exists = [ l for l in location.parent_location.sublocations if l.group_id == g.user.group_id or l.name == "None" ] form = ConfigCreateMType(data={"mtype": location.name}) if form.validate_on_submit(): new_name = form.mtype.data if not (new_name in [l.name for l in exists] or new_name == "None"): with dbt.Transaction(db): location.name = new_name flash("Location updated.", "alert-success") return redirect(url_for("conf.index")) flash(f"Location {new_name } is already registered.", "alert-warning") return render_template("setup/registertype.html", form=form, existing=exists, what="Sub-Locations")
[docs]@bp.route("/htmx_move_sublocation/<list:info>", methods=["PUT"]) @role_required(["Supervisor", "StudentAdmin"]) def htmx_move_sublocation(info: list[int]) -> str: """ Move location to new parent location and update location list. Parameters ---------- info :list[int] Sub-location and location ID Returns ------- str Rendered location list """ lid, pl_id = info current_app.logger.debug(f" htmx : Move sub-Location {lid} to location {pl_id}") location = db.get_or_404(Locations, lid) with dbt.Transaction(db): location.parent_location_id = pl_id locations = ( db.session.execute(db.select(ParentLocations).filter_by(name="Not defined")).scalars().all() + db.session.execute( db.select(ParentLocations).order_by("name").filter_by(group_id=g.user.group_id) ) .scalars() .all() ) current_app.logger.debug(f" htmx : Sub-Location {location.name} moved") return render_template("setup/location_list.html", locations=locations)
[docs]@bp.route("/delete_sublocation/<string:token>", methods=["DELETE"]) @role_required(["Supervisor", "StudentAdmin"]) def htmx_delete_location(token: str) -> str: """ Delete a location where ess, or instrument are stored. On delete, we will move all the ess or instruments connected with the location to the None of the parent location. Parameters ---------- token : str Signed string that encodes the id of the location to delete Returns ------- str Rendered location page. """ lid = token_decode(token, current_app.config["SEC_SESSION_KEY"], g.salt) location = db.session.get(Locations, lid) current_app.logger.info(f" htmx : Delete location {location.name}") sister_location = db.session.execute( db.select(Locations).filter_by(parent_location_id=location.parent_location_id, name="None") ).scalar() for member in location.samples: current_app.logger.debug( f"ESS {member.identifier} will move to location {sister_location.parent_location.name}/{sister_location.name}" ) with dbt.Transaction(db): member.location_id = sister_location.id for member in location.items: current_app.logger.debug( f"Item {member.name} will move to location {sister_location.parent_location.name}/{sister_location.name}" ) with dbt.Transaction(db): member.location_id = sister_location.id for instrument in location.instruments: current_app.logger.debug( f"{instrument.identifier} will move to location {sister_location.parent_location.name}/{sister_location.name}" ) with dbt.Transaction(db): instrument.location_id = sister_location.id with dbt.Transaction(db) as db_session: db_session.delete(location) locations = ( db.session.execute(db.select(ParentLocations).filter_by(name="Not defined")).scalars().all() + db.session.execute( db.select(ParentLocations).order_by("name").filter_by(group_id=g.user.group_id) ) .scalars() .all() ) current_app.logger.debug(" htmx : Location deleted") return render_template("setup/location_list.html", locations=locations)