# -*- 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)