Source code for labfrog.field_selection

# SPDX-FileCopyrightText: 2024-2025 Helmholtz-Zentrum Dresden-Rossendorf e.V (HZDR)
# SPDX-License-Identifier: Apache-2.0

import logging

from bson import ObjectId
from flask import (
    Blueprint,
    current_app,
    flash,
    jsonify,
    redirect,
    render_template,
    request,
    session,
    url_for,
)
from flask_login import login_required

from labfrog.campaign_layout_setup import (
    build_campaign_layout_setup_context,
    handle_campaign_layout_setup_submission,
)
from labfrog.db import get_db
from labfrog.form_feedback import current_username, flash_form_errors
from labfrog.forms import DynamicForm, FieldSelectionForm
from labfrog.helpers.choices import normalize_choice_value, ordered_unique_strings
from labfrog.helpers.layout import (
    _ensure_shot_identity_fields,
    available_campaign_names,
    build_layout_inventory,
    field_layout_filter,
    find_mode_layout_doc,
    get_campaign_layout_doc,
    paired_mode,
    resolve_layout_selections,
    split_layout_picker_fields,
    upsert_field_layout_doc,
)
from labfrog.helpers.urls import get_safe_return_url
from labfrog.request_state import refresh_diagnostic_choice_cache

blueprint = Blueprint("field_selection", __name__)


def _default_edit_password():
    password = (current_app.config.get("DEFAULT_EDIT_PASSWORD") or "").strip()
    return password or None


def _demo_edit_password():
    if not current_app.config.get("DEMO_MODE"):
        return None
    password = (current_app.config.get("DEMO_EDIT_PASSWORD") or "").strip()
    return password or None


def _normalize_demo_name(value: str | None) -> str:
    if not value:
        return ""
    text = str(value).strip()
    if len(text) >= 2 and text[0] == text[-1] and text[0] in {'"', "'"}:
        return text[1:-1].strip()
    return text


def _password_for_layout(layout_name: str | None) -> str | None:
    normalized = _normalize_demo_name(layout_name)
    if normalized == "DEFAULT":
        return _default_edit_password()
    demo_default_layout = (current_app.config.get("DEMO_DEFAULT_LAYOUT") or "").strip()
    if not current_app.config.get("DEMO_MODE") or not demo_default_layout:
        return None
    if normalized == _normalize_demo_name(demo_default_layout):
        return _demo_edit_password()
    return None


def _layout_is_demo_locked(layout_name: str | None) -> bool:
    return bool(_password_for_layout(layout_name))


def _demo_password_valid(request_obj, layout_name: str | None = None) -> bool:
    password = _password_for_layout(layout_name)
    if not password:
        return True
    provided = None
    if request_obj.is_json:
        payload = request_obj.get_json(silent=True) or {}
        provided = payload.get("demo_edit_password")
    if provided is None:
        provided = request_obj.form.get("demo_edit_password")
    if provided is None:
        provided = request_obj.args.get("demo_edit_password")
    return provided == password


def _campaign_choices() -> list[str]:
    """Return available campaign names for layout mapping."""
    return available_campaign_names()


def _non_selectable_layout_fields(mode: str) -> set[str]:
    base_fields = {
        "Campaign",
        "target",
        "material",
        "thickness",
        "notes",
        "mode",
        "date_time",
        "update_date_time",
        "comments",
    }
    if mode == "set":
        base_fields.update({"set_number", "set_length", "shot_number_list"})
    else:
        base_fields.add("shot_number")
    return base_fields


def _filter_layout_picker_fields(
    all_fields: list[str],
    visible_fields: list[str],
    always_include: list[str] | None,
    mode: str,
) -> tuple[list[str], list[str]]:
    excluded = set(always_include or []) | _non_selectable_layout_fields(mode)
    filtered_all = [field for field in all_fields if field not in excluded]
    filtered_visible = [field for field in visible_fields if field not in excluded]
    return filtered_all, filtered_visible


[docs] @blueprint.route("/campaign_layout_setup", methods=["GET", "POST"]) @login_required def campaign_layout_setup(): """Guide users when a campaign has no mapped form layout yet.""" mode = ( "set" if request.values.get("mode") == "set" or session.get("mode") == "set" else "shot" ) campaign = normalize_choice_value( request.values.get("campaign") or session.get("selected_campaign") or "" ) next_url = get_safe_return_url( request.values.get("next"), url_for("add_entry.add_entry" if mode == "shot" else "edit_entry.edit_entry"), ) session["selected_campaign"] = campaign session["mode"] = mode PARAMETERS_DB = current_app.config["MONGODB_COLLECTION_FOR_SETTINGS"] parameters_collection = get_db()[PARAMETERS_DB] existing_layout = get_campaign_layout_doc(parameters_collection, mode, campaign) if existing_layout and existing_layout.get("layout_name"): return redirect(next_url) if request.method == "POST": response = handle_campaign_layout_setup_submission( parameters_collection, mode, campaign, next_url, request.form ) if response is not None: return response return render_template( "campaign_layout_setup.html", active_tab="field_selection", selected_layout=_get_selected_source_layout(mode), **build_campaign_layout_setup_context( parameters_collection, mode, campaign, next_url ), )
def _diagnostic_names() -> list[str]: diagnostics_db = get_db()["diagnostics"] names = { doc.get("DisplayName") for doc in diagnostics_db.find({}, {"DisplayName": 1, "_id": 0}) if doc.get("DisplayName") } return sorted(names) def _get_selected_source_layout(mode: str) -> str: """Return the shared source field layout for management views.""" return ( session.get("selected_layout") or session.get(f"selected_source_layout_{mode}") or session.get(f"selected_source_layout_{paired_mode(mode)}") or "DEFAULT" ) def _set_selected_source_layout(mode: str, layout_name: str) -> None: """Persist the source field layout as one shared campaign-level selection.""" normalized = layout_name or "DEFAULT" session["selected_layout"] = normalized session[f"selected_source_layout_{mode}"] = normalized session[f"selected_source_layout_{paired_mode(mode)}"] = normalized def _set_selected_source_layout_pair( parameters_collection, *, mode: str, layout_name: str, ) -> None: """Persist the active layout as the shared selection for both modes.""" del parameters_collection _set_selected_source_layout(mode, layout_name) def _resolved_layout_picker_fields( parameters_collection, *, layout_data: dict | None, mode: str, all_fields: list[str], always_include: list[str] | None, ) -> tuple[list[str], list[str]]: """Return mode-filtered selected and visible picker fields for one layout.""" if not layout_data: return [], [] _, _, selected_fields = resolve_layout_selections(layout_data) if selected_fields: _, visible_fields = _filter_layout_picker_fields( list(all_fields), list(selected_fields), always_include, mode ) elif layout_data.get("layout_name", "DEFAULT") == "DEFAULT": selected_fields = list(all_fields) visible_fields = list(all_fields) else: visible_fields = [] return selected_fields, visible_fields def _sync_diagnostics_from_selected(selected_fields): """Update diagnostics_only + DIAG_NAMES/TOOLTIPS from selected fields.""" if not selected_fields: session["diagnostics_only"] = [] return [] diagnostics_db = get_db()["diagnostics"] diag_names = { doc.get("DisplayName") for doc in diagnostics_db.find({}, {"DisplayName": 1, "_id": 0}) if doc.get("DisplayName") } diagnostics_only = [ field for field in ordered_unique_strings(selected_fields) if field in diag_names ] session["diagnostics_only"] = diagnostics_only refresh_diagnostic_choice_cache(diagnostics_only) return diagnostics_only def _layout_picker_campaign_choices( applied_campaigns: list[str] | None = None, ) -> list[str]: """Return campaign choices plus any campaigns already bound to the layout.""" return ordered_unique_strings([ "NONE", *available_campaign_names(applied_campaigns or []), ]) def _field_selection_render_context( parameters_collection, *, form, all_fields: list[str], visible_fields: list[str], selected_layout: str, picker_selected_layout: str | None = None, applied_campaigns: list[str], diag_names: set[str], campaigns: list[str] | None = None, setup_wizard: bool = False, wizard_saved: bool = False, wizard_next_url: str | None = None, organize_prompt: bool = False, organize_prompt_url: str | None = None, draft_mode: str = "", ): """Build the shared template context for the field-selection picker UI.""" mode = session.get("mode", "shot") available_layouts, layouts = build_layout_inventory(parameters_collection, mode) protected_layouts = [] if _default_edit_password(): protected_layouts.append("DEFAULT") demo_default_layout = (current_app.config.get("DEMO_DEFAULT_LAYOUT") or "").strip() if ( current_app.config.get("DEMO_MODE") and demo_default_layout and _demo_edit_password() ): protected_layouts.append(_normalize_demo_name(demo_default_layout)) return { "active_tab": "form_layout", "form": form, "available_layouts": available_layouts, "Layouts": layouts, "selected_layout": selected_layout, "picker_selected_layout": ( picker_selected_layout if picker_selected_layout is not None else selected_layout ), "applied_campaigns": applied_campaigns, "campaigns": campaigns or _layout_picker_campaign_choices(applied_campaigns), "diagnostic_names": sorted(diag_names), "setup_wizard": setup_wizard, "wizard_saved": wizard_saved, "wizard_next_url": wizard_next_url, "organize_prompt": organize_prompt, "organize_prompt_url": organize_prompt_url, "draft_mode": draft_mode, "layout_picker_label": ( "Start from previously config" if draft_mode == "fresh" else "Choose layout" ), "layout_picker_placeholder": ( "-- Saved configs --" if draft_mode == "fresh" else "-- Select a Layout --" ), "layout_picker_help": ( "Optionally preload fields from a saved config before saving this campaign's field layout." if draft_mode == "fresh" else "Choose an existing field layout or enter a name to save a new one. Move fields between the lists, then save the field layout." ), **split_layout_picker_fields(all_fields, visible_fields, diag_names), "protected_layouts": sorted(set(protected_layouts)), "layout_password_required": bool(protected_layouts), } #######################################################################################################################################
[docs] @blueprint.route("/field_selection", methods=["GET", "POST"]) # canonical alias @blueprint.route("/select_fields", methods=["GET", "POST"]) # current public path @login_required def field_selection(): """ Render the layout picker and persist layout field/campaign selections. """ SELECTED_FIELDS_LIST = session.get("SELECTED_FIELDS_LIST", []) visible_fields = list(SELECTED_FIELDS_LIST) PARAMETERS_DB = current_app.config["MONGODB_COLLECTION_FOR_SETTINGS"] parameters_collection = get_db()[PARAMETERS_DB] mode = session.get("mode", "shot") all_fields = list(DynamicForm.get_all_fields(collection=PARAMETERS_DB, mode=mode)) always_include = session.get("always_include") all_fields, visible_fields = _filter_layout_picker_fields( all_fields, visible_fields, always_include, mode ) diag_names = set(_diagnostic_names()) form = FieldSelectionForm(all_fields=all_fields, selected_fields=visible_fields) draft_mode = (request.args.get("draft_mode") or "").strip() draft_layout_name = (request.args.get("draft_layout_name") or "").strip() draft_campaign = normalize_choice_value(request.args.get("draft_campaign") or "") draft_source_layout = (request.args.get("draft_source_layout") or "").strip() setup_wizard = (request.args.get("setup_wizard") or "").strip() == "1" wizard_saved = (request.args.get("wizard_saved") or "").strip() == "1" organize_prompt = (request.args.get("organize_prompt") or "").strip() == "1" selected_layout = draft_layout_name or _get_selected_source_layout(mode) picker_selected_layout = ( draft_source_layout if draft_mode == "clone" else ("" if draft_mode == "fresh" else selected_layout) ) current_responsible_person = current_username(default="") if selected_layout != "Default": form.layout_name.data = selected_layout form.responsible_person.data = current_responsible_person layout_data = None if draft_mode == "clone" and draft_source_layout: layout_data = find_mode_layout_doc( parameters_collection, draft_source_layout, mode ) elif not draft_mode: layout_data = find_mode_layout_doc(parameters_collection, selected_layout, mode) applied_campaigns = [] if layout_data: form.description.data = layout_data.get("description") form.responsible_person.data = current_responsible_person selected_parameters, selected_diagnostics, selected_from_layout = ( resolve_layout_selections(layout_data) ) form.selected_fields.data = selected_from_layout applied_campaigns = layout_data.get("campaigns") or [] _, visible_fields = _resolved_layout_picker_fields( parameters_collection, layout_data=layout_data, mode=mode, all_fields=all_fields, always_include=always_include, ) if not draft_mode: session["SELECTED_FIELDS_LIST"] = _ensure_shot_identity_fields( selected_from_layout ) session["diagnostics_only"] = list(selected_diagnostics) _sync_diagnostics_from_selected(session["SELECTED_FIELDS_LIST"]) elif draft_mode == "fresh": visible_fields = [] form.selected_fields.data = [] form.description.data = "" if draft_campaign: applied_campaigns = [draft_campaign] if "submit" in request.form and form.validate_on_submit(): layout_name = request.form["layout_name"] form.layout_name.data = layout_name if _layout_is_demo_locked(layout_name) and not _demo_password_valid( request, layout_name ): flash( f"Layout {layout_name} is protected. Password required to modify.", "error", ) return redirect(url_for("field_selection.field_selection")) form.description.data = request.form.get("description") form.responsible_person.data = current_responsible_person selected_parameters = request.form.getlist("selected_parameters") selected_diagnostics = request.form.getlist("selected_diagnostics") selected_fields = request.form.getlist("selected_fields") form.selected_fields.data = selected_fields applied_campaigns = request.form.getlist("campaigns") if not applied_campaigns: flash("Please select at least one campaign.", "error") return render_template( "field_selection.html", **_field_selection_render_context( parameters_collection, form=form, all_fields=all_fields, visible_fields=selected_fields, selected_layout=selected_layout, picker_selected_layout=picker_selected_layout, applied_campaigns=applied_campaigns, diag_names=diag_names, setup_wizard=setup_wizard or draft_mode in {"clone", "fresh"}, wizard_saved=wizard_saved, draft_mode=draft_mode, ), ) form.data["selected_fields"] = selected_fields session["SELECTED_FIELDS_LIST"] = _ensure_shot_identity_fields(selected_fields) diagnostics_only = _sync_diagnostics_from_selected( session["SELECTED_FIELDS_LIST"] ) normalized_diagnostics = selected_diagnostics or diagnostics_only upsert_field_layout_doc( parameters_collection, layout_name=layout_name, mode=mode, description=request.form.get("description"), responsible_person=current_responsible_person, campaigns=applied_campaigns, selected_parameters=selected_parameters, selected_diagnostics=normalized_diagnostics, ) upsert_field_layout_doc( parameters_collection, layout_name=layout_name, mode=paired_mode(mode), description=request.form.get("description"), responsible_person=current_responsible_person, campaigns=applied_campaigns, selected_parameters=selected_parameters, selected_diagnostics=normalized_diagnostics, ) _set_selected_source_layout_pair( parameters_collection, mode=mode, layout_name=layout_name, ) session.pop("_flashes", None) flash(f"Layout {layout_name} saved successfully!", "success") session["template_selected"] = True if draft_mode in {"clone", "fresh"} or setup_wizard: wizard_campaign = ( applied_campaigns[0] if applied_campaigns else draft_campaign or session.get("selected_campaign", "") ) return redirect( url_for( "field_selection.field_selection", setup_wizard="1", wizard_saved="1", draft_layout_name=layout_name, draft_campaign=wizard_campaign, ) ) prompt_campaign = ( applied_campaigns[0] if applied_campaigns else session.get("selected_campaign", "") ) return redirect( url_for( "field_selection.field_selection", organize_prompt="1", draft_layout_name=layout_name, draft_campaign=prompt_campaign, ) ) flash_form_errors(form, logger=logging.getLogger(__name__)) return render_template( "field_selection.html", **_field_selection_render_context( parameters_collection, form=form, all_fields=all_fields, visible_fields=visible_fields, selected_layout=selected_layout, picker_selected_layout=picker_selected_layout, applied_campaigns=applied_campaigns, diag_names=diag_names, setup_wizard=setup_wizard or draft_mode in {"clone", "fresh"}, wizard_saved=wizard_saved, wizard_next_url=( url_for( "organize_sections.organize_sections", setup_wizard="1", draft_layout_name=selected_layout, draft_campaign=( draft_campaign or session.get("selected_campaign", "") ), ) if (setup_wizard or draft_mode in {"clone", "fresh"}) and wizard_saved else None ), organize_prompt=organize_prompt, organize_prompt_url=( url_for( "organize_sections.organize_sections", draft_layout_name=selected_layout, draft_campaign=( draft_campaign or session.get("selected_campaign", "") ), ) if organize_prompt else None ), draft_mode=draft_mode, ), )
[docs] @blueprint.route("/fetch_layout_fields", methods=["POST"]) # legacy alias @blueprint.route("/select_fields/load_layout", methods=["POST"]) def fetch_layout_fields(): """ Fetch the field data for a selected layout from the database. This route retrieves the custom field data associated with a particular layout selected by the user. Returns: jsonify: Returns a JSON representation of the field data for the selected layout. """ visible_fields = list(session.get("SELECTED_FIELDS_LIST", [])) mode = session.get("mode", "shot") PARAMETERS_DB = current_app.config["MONGODB_COLLECTION_FOR_SETTINGS"] parameters_collection = get_db()[PARAMETERS_DB] always_include = session.get("always_include", []) if always_include is None: base_fields_ions = parameters_collection.find_one({ "layout_name": "DEFAULT", "mode": mode, }) always_include = list(base_fields_ions.get("always_include")) session["always_include"] = always_include all_fields = list(DynamicForm.get_all_fields(collection=PARAMETERS_DB, mode=mode)) all_fields, visible_fields = _filter_layout_picker_fields( all_fields, visible_fields, always_include, mode ) selected_layout = request.form.get("selected_layout") preview_only = (request.form.get("preview_only") or "").strip() == "1" layout_data = find_mode_layout_doc(parameters_collection, selected_layout, mode) if not layout_data: return jsonify([]) selected_from_layout, visible_fields = _resolved_layout_picker_fields( parameters_collection, layout_data=layout_data, mode=mode, all_fields=all_fields, always_include=always_include, ) # print("all fields to choose from", all_fields) form = FieldSelectionForm(all_fields=all_fields, selected_fields=visible_fields) if layout_data: form.layout_name.data = layout_data.get("layout_name") form.description.data = layout_data.get("description") form.responsible_person.data = layout_data.get("responsible_person") form.selected_fields.data = selected_from_layout if layout_data: selected_fields = ordered_unique_strings(visible_fields) non_selected_fields = [ field for field in ordered_unique_strings(all_fields) if field not in selected_fields ] response_data = { "layout_name": layout_data.get("layout_name"), "description": layout_data.get("description"), "responsible_person": layout_data.get("responsible_person"), "selected_fields": selected_fields, "non_selected_fields": non_selected_fields, "campaigns": layout_data.get("campaigns", []), } if not preview_only: session["SELECTED_FIELDS_LIST"] = _ensure_shot_identity_fields( selected_fields ) _sync_diagnostics_from_selected(session["SELECTED_FIELDS_LIST"]) selected_layout = response_data["layout_name"] _set_selected_source_layout_pair( parameters_collection, mode=mode, layout_name=selected_layout, ) return jsonify(response_data) return jsonify([]) # Return an empty list if the layout is not found
[docs] @blueprint.route("/modify_layout/<layout_id>", methods=["GET", "POST"]) # legacy alias @blueprint.route("/select_fields/modify_layout/<layout_id>", methods=["GET", "POST"]) def modify_layout(layout_id): """ Render and persist edits to an existing saved layout. """ SELECTED_FIELDS_LIST = session.get("SELECTED_FIELDS_LIST", []) visible_fields = SELECTED_FIELDS_LIST mode = session.get("mode", "shot") PARAMETERS_DB = current_app.config["MONGODB_COLLECTION_FOR_SETTINGS"] parameters_collection = get_db()[PARAMETERS_DB] always_include = session.get("always_include") if always_include is None: base_fields_ions = parameters_collection.find_one({ "layout_name": "DEFAULT", "mode": mode, }) always_include = list(base_fields_ions.get("always_include")) session["always_include"] = always_include all_fields = list(DynamicForm.get_all_fields(collection=PARAMETERS_DB, mode=mode)) all_fields, visible_fields = _filter_layout_picker_fields( all_fields, visible_fields, always_include, mode ) layout_data = parameters_collection.find_one({"_id": ObjectId(layout_id)}) selected_layout = request.form.get("selected_layout") or ( layout_data.get("layout_name") if layout_data is not None else _get_selected_source_layout(mode) ) current_responsible_person = current_username(default="") if layout_data is not None: _selected_from_layout, visible_fields = _resolved_layout_picker_fields( parameters_collection, layout_data=layout_data, mode=mode, all_fields=all_fields, always_include=always_include, ) else: visible_fields = all_fields diag_names = set(_diagnostic_names()) form = FieldSelectionForm(all_fields=all_fields, selected_fields=visible_fields) if layout_data: form.layout_name.data = layout_data.get("layout_name") form.description.data = layout_data.get("description") form.responsible_person.data = layout_data.get("responsible_person") form.selected_fields.data = visible_fields if request.method == "POST" and "submit" in request.form: if layout_data and _layout_is_demo_locked(layout_data.get("layout_name")): if not _demo_password_valid(request, layout_data.get("layout_name")): flash( f"Layout {layout_data.get('layout_name')} is protected. Password required to modify.", "error", ) return redirect(url_for("field_selection.field_selection")) form.layout_name.data = request.form["layout_name"] form.description.data = request.form["description"] form.responsible_person.data = current_responsible_person selected_parameters = request.form.getlist("selected_parameters") selected_diagnostics = request.form.getlist("selected_diagnostics") form.selected_fields.data = request.form.getlist("selected_fields") applied_campaigns = request.form.getlist("campaigns") if not applied_campaigns: flash("Please select at least one campaign.", "error") return render_template( "field_selection.html", **_field_selection_render_context( parameters_collection, form=form, all_fields=all_fields, visible_fields=form.selected_fields.data, selected_layout=selected_layout, picker_selected_layout=selected_layout, applied_campaigns=applied_campaigns, diag_names=diag_names, campaigns=_layout_picker_campaign_choices(applied_campaigns), ), ) # Get the layout data from the form or request diagnostics_only = _sync_diagnostics_from_selected( request.form.getlist("selected_fields") ) normalized_diagnostics = selected_diagnostics or diagnostics_only upsert_field_layout_doc( parameters_collection, layout_name=request.form["layout_name"], mode=mode, description=request.form["description"], responsible_person=form.responsible_person.data, campaigns=applied_campaigns, selected_parameters=selected_parameters, selected_diagnostics=normalized_diagnostics, ) upsert_field_layout_doc( parameters_collection, layout_name=request.form["layout_name"], mode=paired_mode(mode), description=request.form["description"], responsible_person=form.responsible_person.data, campaigns=applied_campaigns, selected_parameters=selected_parameters, selected_diagnostics=normalized_diagnostics, ) session["SELECTED_FIELDS_LIST"] = _ensure_shot_identity_fields( request.form.getlist("selected_fields") ) selected_layout = request.form["layout_name"] _set_selected_source_layout_pair( parameters_collection, mode=mode, layout_name=selected_layout, ) flash( f"Layout {request.form['layout_name']} modified successfully!", "success", ) return redirect(url_for("field_selection.field_selection")) applied_campaigns = [] if layout_data: applied_campaigns = layout_data.get("campaigns") or [] campaign_choices = _layout_picker_campaign_choices(applied_campaigns) return render_template( "field_selection.html", **_field_selection_render_context( parameters_collection, form=form, all_fields=all_fields, visible_fields=visible_fields, selected_layout=selected_layout, picker_selected_layout=selected_layout, applied_campaigns=applied_campaigns, diag_names=diag_names, campaigns=campaign_choices, ), )
[docs] @blueprint.route( "/delete_layout/<layout_id>", methods=["POST", "DELETE"] ) # legacy alias @blueprint.route("/select_fields/delete_layout/<layout_id>", methods=["POST", "DELETE"]) def delete_layout(layout_id): """ Delete a specific layout identified by its ID. The route allows users to delete a specific layout from the database based on its ID. Returns: str: A response message indicating the success or failure of the deletion operation. """ PARAMETERS_DB = current_app.config["MONGODB_COLLECTION_FOR_SETTINGS"] parameters_collection = get_db()[PARAMETERS_DB] if request.method == "POST": # Convert the layout_id to ObjectId layout_id = ObjectId(layout_id) # Delete the layout based on ObjectId layout_doc = parameters_collection.find_one( {"_id": layout_id}, {"layout_name": 1} ) if layout_doc and _layout_is_demo_locked(layout_doc.get("layout_name")): if not _demo_password_valid(request, layout_doc.get("layout_name")): return "Password required to delete protected layout", 403 result = parameters_collection.delete_one({"_id": layout_id}) if result.deleted_count == 1: return "Layout deleted successfully", 200 return "Layout not found", 404 # Handle GET requests or other HTTP methods if needed return "Method not allowed", 405
#######################################################################################################################################