Coverage for heritrace/routes/main.py: 100%
62 statements
« prev ^ index » next coverage.py v7.15.2, created at 2026-07-26 08:34 +0000
« prev ^ index » next coverage.py v7.15.2, created at 2026-07-26 08:34 +0000
1# SPDX-FileCopyrightText: 2024-2026 Arcangelo Massari <arcangelo.massari@unibo.it>
2#
3# SPDX-License-Identifier: ISC
5import json
7from flask import Blueprint, current_app, redirect, render_template, request, url_for
8from flask_login import login_required
9from SPARQLWrapper import JSON
10from SPARQLWrapper.SPARQLExceptions import SPARQLWrapperException
11from werkzeug.wrappers import Response as WerkzeugResponse
13from heritrace.extensions import get_dataset_endpoint, get_sparql
14from heritrace.utils.shacl_utils import determine_shape_for_classes
15from heritrace.utils.sparql_utils import (
16 CatalogQuery,
17 DeletedEntitiesQuery,
18 get_available_classes,
19 get_catalog_data,
20 get_deleted_entities_with_filtering,
21)
23main_bp = Blueprint("main", __name__)
26@main_bp.route("/")
27def index() -> str:
28 return render_template("index.jinja")
31@main_bp.route("/catalogue")
32@login_required
33def catalogue() -> str:
34 page = int(request.args.get("page", 1))
35 per_page = int(
36 request.args.get("per_page", current_app.config["CATALOGUE_DEFAULT_PER_PAGE"])
37 )
38 selected_class = request.args.get("class")
39 sort_property = request.args.get("sort_property")
40 sort_direction = request.args.get("sort_direction", "ASC")
41 selected_shape = request.args.get("shape")
43 available_classes = get_available_classes()
45 if not selected_class and available_classes:
46 selected_class = str(available_classes[0]["uri"])
48 if not selected_shape and selected_class:
49 selected_shape = determine_shape_for_classes([selected_class])
51 catalog_data = get_catalog_data(
52 CatalogQuery(
53 selected_class=selected_class,
54 page=page,
55 per_page=per_page,
56 sort_property=sort_property,
57 sort_direction=sort_direction,
58 selected_shape=selected_shape,
59 ),
60 available_classes,
61 )
63 return render_template(
64 "catalogue.jinja",
65 available_classes=available_classes,
66 selected_class=selected_class,
67 selected_shape=selected_shape,
68 page=page,
69 total_entity_pages=catalog_data["total_pages"],
70 per_page=per_page,
71 allowed_per_page=current_app.config["CATALOGUE_ALLOWED_PER_PAGE"],
72 sortable_properties=json.dumps(catalog_data["sortable_properties"]),
73 current_sort_property=catalog_data["sort_property"],
74 current_sort_direction=catalog_data["sort_direction"],
75 initial_entities=catalog_data["entities"],
76 )
79@main_bp.route("/time-vault")
80@login_required
81def time_vault() -> str:
82 """
83 Render the Time Vault page, which displays a list of deleted entities.
84 """
85 initial_page = request.args.get("page", 1, type=int)
86 initial_per_page = request.args.get(
87 "per_page", current_app.config["CATALOGUE_DEFAULT_PER_PAGE"], type=int
88 )
89 sort_direction = request.args.get("sort_direction", "DESC")
90 selected_class = request.args.get("class")
91 selected_shape = request.args.get("shape")
93 allowed_per_page = current_app.config["CATALOGUE_ALLOWED_PER_PAGE"]
95 (
96 initial_entities,
97 available_classes,
98 selected_class,
99 selected_shape,
100 sortable_properties,
101 total_count,
102 ) = get_deleted_entities_with_filtering(
103 DeletedEntitiesQuery(
104 initial_page,
105 initial_per_page,
106 sort_direction,
107 selected_class,
108 selected_shape,
109 )
110 )
112 return render_template(
113 "time_vault.jinja",
114 available_classes=available_classes,
115 selected_class=selected_class,
116 selected_shape=selected_shape,
117 page=initial_page,
118 total_entity_pages=(total_count + initial_per_page - 1) // initial_per_page
119 if total_count > 0
120 else 0,
121 per_page=initial_per_page,
122 allowed_per_page=allowed_per_page,
123 sortable_properties=json.dumps(sortable_properties),
124 current_sort_property=sortable_properties[0]["property"],
125 current_sort_direction=sort_direction,
126 initial_entities=initial_entities,
127 )
130@main_bp.route("/dataset-endpoint", methods=["POST"])
131@login_required
132def sparql_proxy() -> tuple[str, int, dict[str, str]]:
133 query = request.form.get("query", "")
135 sparql_wrapper = get_sparql()
136 sparql_wrapper.setQuery(query)
137 sparql_wrapper.setReturnFormat(JSON)
139 try:
140 results = sparql_wrapper.query().convert()
141 return (
142 json.dumps(results),
143 200,
144 {"Content-Type": "application/sparql-results+json"},
145 )
146 except (
147 json.JSONDecodeError,
148 OSError,
149 SPARQLWrapperException,
150 TypeError,
151 UnicodeDecodeError,
152 ) as error:
153 current_app.logger.exception("SPARQL query failed for query: %s", query)
154 return (
155 json.dumps({"error": str(error)}),
156 500,
157 {"Content-Type": "application/json"},
158 )
161@main_bp.route("/endpoint")
162@login_required
163def endpoint() -> str:
164 return render_template("endpoint.jinja", dataset_endpoint=get_dataset_endpoint())
167@main_bp.route("/search")
168@login_required
169def search() -> WerkzeugResponse:
170 subject = request.args.get("q")
171 return redirect(url_for("entity.about", subject=subject))