Coverage for heritrace/routes/api.py: 100%
352 statements
« prev ^ index » next coverage.py v7.6.12, created at 2025-04-18 11:10 +0000
« prev ^ index » next coverage.py v7.6.12, created at 2025-04-18 11:10 +0000
1# heritrace/routes/api.py
3import traceback
4from typing import Dict, Optional
6from flask import Blueprint, current_app, g, jsonify, request
7from flask_babel import gettext
8from flask_login import current_user, login_required
9from heritrace.editor import Editor
10from heritrace.extensions import (get_custom_filter, get_dataset_endpoint,
11 get_provenance_endpoint)
12from heritrace.services.resource_lock_manager import LockStatus
13from heritrace.utils.shacl_utils import validate_new_triple
14from heritrace.utils.sparql_utils import (find_orphaned_entities,
15 get_available_classes,
16 get_catalog_data,
17 get_deleted_entities_with_filtering,
18 import_entity_graph)
19from heritrace.utils.strategies import (OrphanHandlingStrategy,
20 ProxyHandlingStrategy)
21from heritrace.utils.uri_utils import generate_unique_uri
22from rdflib import RDF, XSD, Graph, URIRef
23from resources.datatypes import DATATYPE_MAPPING
24from SPARQLWrapper import JSON
26api_bp = Blueprint("api", __name__)
29@api_bp.route("/catalogue")
30@login_required
31def catalogue_api():
32 selected_class = request.args.get("class")
33 page = int(request.args.get("page", 1))
34 per_page = int(request.args.get("per_page", 50))
35 sort_property = request.args.get("sort_property")
36 sort_direction = request.args.get("sort_direction", "ASC")
38 allowed_per_page = [50, 100, 200, 500]
39 if per_page not in allowed_per_page:
40 per_page = 100
42 if not sort_property or sort_property.lower() == "null":
43 sort_property = None
45 catalog_data = get_catalog_data(
46 selected_class, page, per_page, sort_property, sort_direction
47 )
49 catalog_data["available_classes"] = get_available_classes()
50 return jsonify(catalog_data)
53@api_bp.route("/time-vault")
54@login_required
55def get_deleted_entities_api():
56 """
57 API endpoint to retrieve deleted entities with pagination and sorting.
58 Only processes and returns entities whose classes are marked as visible.
59 """
60 selected_class = request.args.get("class")
61 page = int(request.args.get("page", 1))
62 per_page = int(request.args.get("per_page", 50))
63 sort_property = request.args.get("sort_property", "deletionTime")
64 sort_direction = request.args.get("sort_direction", "DESC")
66 allowed_per_page = [50, 100, 200, 500]
67 if per_page not in allowed_per_page:
68 per_page = 100
70 deleted_entities, available_classes, selected_class, sortable_properties, total_count = (
71 get_deleted_entities_with_filtering(
72 page, per_page, sort_property, sort_direction, selected_class
73 )
74 )
76 return jsonify(
77 {
78 "entities": deleted_entities,
79 "total_pages": (total_count + per_page - 1) // per_page if total_count > 0 else 0,
80 "current_page": page,
81 "per_page": per_page,
82 "total_count": total_count,
83 "sort_property": sort_property,
84 "sort_direction": sort_direction,
85 "selected_class": selected_class,
86 "available_classes": available_classes,
87 "sortable_properties": sortable_properties,
88 }
89 )
92@api_bp.route("/check-lock", methods=["POST"])
93@login_required
94def check_lock():
95 """Check if a resource is locked."""
96 try:
97 data = request.get_json()
98 resource_uri = data.get("resource_uri")
100 if not resource_uri:
101 return (
102 jsonify(
103 {"status": "error", "message": gettext("No resource URI provided")}
104 ),
105 400,
106 )
108 status, lock_info = g.resource_lock_manager.check_lock_status(resource_uri)
110 if status == LockStatus.LOCKED:
111 return jsonify(
112 {
113 "status": "locked",
114 "title": gettext("Resource Locked"),
115 "message": gettext(
116 "This resource is currently being edited by %(user)s [%(orcid)s]",
117 user=lock_info.user_name,
118 orcid=lock_info.user_id,
119 ),
120 }
121 )
122 elif status == LockStatus.ERROR:
123 return (
124 jsonify(
125 {
126 "status": "error",
127 "title": gettext("Error"),
128 "message": gettext("An error occurred while checking the lock"),
129 }
130 ),
131 500,
132 )
133 else:
134 return jsonify({"status": "available"})
136 except Exception as e:
137 current_app.logger.error(f"Error in check_lock: {str(e)}")
138 return (
139 jsonify(
140 {
141 "status": "error",
142 "title": gettext("Error"),
143 "message": gettext("An unexpected error occurred"),
144 }
145 ),
146 500,
147 )
150@api_bp.route("/acquire-lock", methods=["POST"])
151@login_required
152def acquire_lock():
153 """Try to acquire a lock on a resource."""
154 try:
155 data = request.get_json()
156 resource_uri = data.get("resource_uri")
157 linked_resources = data.get("linked_resources", [])
159 if not resource_uri:
160 return (
161 jsonify(
162 {"status": "error", "message": gettext("No resource URI provided")}
163 ),
164 400,
165 )
167 # First check if the resource or any related resource is locked by another user
168 status, lock_info = g.resource_lock_manager.check_lock_status(resource_uri)
169 if status == LockStatus.LOCKED:
170 return (
171 jsonify(
172 {
173 "status": "locked",
174 "title": gettext("Resource Locked"),
175 "message": gettext(
176 "This resource is currently being edited by %(user)s [%(orcid)s]",
177 user=lock_info.user_name,
178 orcid=lock_info.user_id,
179 ),
180 }
181 ),
182 200,
183 )
185 # Use the provided linked_resources
186 success = g.resource_lock_manager.acquire_lock(resource_uri, linked_resources)
188 if success:
189 return jsonify({"status": "success"})
191 return (
192 jsonify(
193 {
194 "status": "error",
195 "message": gettext("Resource is locked by another user"),
196 }
197 ),
198 423,
199 )
201 except Exception as e:
202 current_app.logger.error(f"Error in acquire_lock: {str(e)}")
203 return (
204 jsonify(
205 {"status": "error", "message": gettext("An unexpected error occurred")}
206 ),
207 500,
208 )
211@api_bp.route("/release-lock", methods=["POST"])
212@login_required
213def release_lock():
214 """Release a lock on a resource."""
215 try:
216 data = request.get_json()
217 resource_uri = data.get("resource_uri")
219 if not resource_uri:
220 return (
221 jsonify(
222 {"status": "error", "message": gettext("No resource URI provided")}
223 ),
224 400,
225 )
227 success = g.resource_lock_manager.release_lock(resource_uri)
229 if success:
230 return jsonify({"status": "success"})
232 return (
233 jsonify({"status": "error", "message": gettext("Unable to release lock")}),
234 400,
235 )
237 except Exception as e:
238 current_app.logger.error(f"Error in release_lock: {str(e)}")
239 return (
240 jsonify(
241 {"status": "error", "message": gettext("An unexpected error occurred")}
242 ),
243 500,
244 )
247@api_bp.route("/renew-lock", methods=["POST"])
248@login_required
249def renew_lock():
250 """Renew an existing lock on a resource."""
251 try:
252 data = request.get_json()
253 resource_uri = data.get("resource_uri")
255 if not resource_uri:
256 return (
257 jsonify(
258 {"status": "error", "message": gettext("No resource URI provided")}
259 ),
260 400,
261 )
263 # When renewing a lock, we don't need to check for linked resources again
264 # Just pass an empty list as we're only refreshing the existing lock
265 success = g.resource_lock_manager.acquire_lock(resource_uri, [])
267 if success:
268 return jsonify({"status": "success"})
270 return (
271 jsonify({"status": "error", "message": gettext("Unable to renew lock")}),
272 423,
273 )
275 except Exception as e:
276 current_app.logger.error(f"Error in renew_lock: {str(e)}")
277 return (
278 jsonify(
279 {"status": "error", "message": gettext("An unexpected error occurred")}
280 ),
281 500,
282 )
285@api_bp.route("/validate-literal", methods=["POST"])
286@login_required
287def validate_literal():
288 """Validate a literal value and suggest appropriate datatypes."""
289 value = request.json.get("value")
290 if not value:
291 return jsonify({"error": gettext("Value is required.")}), 400
293 matching_datatypes = []
294 for datatype, validation_func, _ in DATATYPE_MAPPING:
295 if validation_func(value):
296 matching_datatypes.append(str(datatype))
298 if not matching_datatypes:
299 return jsonify({"error": gettext("No matching datatypes found.")}), 400
301 return jsonify({"valid_datatypes": matching_datatypes}), 200
304@api_bp.route("/check_orphans", methods=["POST"])
305@login_required
306def check_orphans():
307 """
308 Check for orphaned entities and intermediate relations (proxies) that would result from the requested changes.
309 Applies separate handling strategies for orphans and proxies, but returns a unified report.
310 """
311 try:
312 # Get strategies from configuration
313 orphan_strategy = current_app.config.get(
314 "ORPHAN_HANDLING_STRATEGY", OrphanHandlingStrategy.KEEP
315 )
316 proxy_strategy = current_app.config.get(
317 "PROXY_HANDLING_STRATEGY", ProxyHandlingStrategy.KEEP
318 )
320 data = request.json
321 # Validate required fields
322 if not data or "changes" not in data or "entity_type" not in data:
323 return (
324 jsonify(
325 {
326 "status": "error",
327 "error_type": "validation",
328 "message": gettext(
329 "Invalid request: 'changes' and 'entity_type' are required fields"
330 ),
331 }
332 ),
333 400,
334 )
336 changes = data.get("changes", [])
337 entity_type = data.get("entity_type")
338 custom_filter = get_custom_filter()
340 orphans = []
341 intermediate_orphans = []
343 # Check for orphans and proxies based on their respective strategies
344 check_for_orphans = orphan_strategy in (
345 OrphanHandlingStrategy.DELETE,
346 OrphanHandlingStrategy.ASK,
347 )
348 check_for_proxies = proxy_strategy in (
349 ProxyHandlingStrategy.DELETE,
350 ProxyHandlingStrategy.ASK,
351 )
352 if check_for_orphans or check_for_proxies:
353 for change in changes:
354 if change["action"] == "delete":
355 found_orphans, found_intermediates = find_orphaned_entities(
356 change["subject"],
357 entity_type,
358 change.get("predicate"),
359 change.get("object"),
360 )
361 # Only collect orphans if we need to handle them
362 if check_for_orphans:
363 orphans.extend(found_orphans)
365 # Only collect proxies if we need to handle them
366 if check_for_proxies:
367 intermediate_orphans.extend(found_intermediates)
369 # If both strategies are KEEP or no entities found, return empty result
370 if (orphan_strategy == OrphanHandlingStrategy.KEEP or not orphans) and (
371 proxy_strategy == ProxyHandlingStrategy.KEEP or not intermediate_orphans
372 ):
373 return jsonify({"status": "success", "affected_entities": []})
375 # Format entities for display
376 def format_entities(entities, is_intermediate=False):
377 return [
378 {
379 "uri": entity["uri"],
380 "label": custom_filter.human_readable_entity(
381 entity["uri"], [entity["type"]]
382 ),
383 "type": custom_filter.human_readable_predicate(
384 entity["type"], [entity["type"]]
385 ),
386 "is_intermediate": is_intermediate,
387 }
388 for entity in entities
389 ]
391 # Create a unified list of affected entities
392 affected_entities = format_entities(orphans) + format_entities(
393 intermediate_orphans, is_intermediate=True
394 )
396 # Determine if we should automatically delete entities
397 should_delete_orphans = orphan_strategy == OrphanHandlingStrategy.DELETE
398 should_delete_proxies = proxy_strategy == ProxyHandlingStrategy.DELETE
400 # If both strategies are DELETE, we can automatically delete everything
401 if should_delete_orphans and should_delete_proxies:
402 return jsonify(
403 {
404 "status": "success",
405 "affected_entities": affected_entities,
406 "should_delete": True,
407 "orphan_strategy": orphan_strategy.value,
408 "proxy_strategy": proxy_strategy.value,
409 }
410 )
412 # If at least one strategy is ASK, we need to ask the user
413 return jsonify(
414 {
415 "status": "success",
416 "affected_entities": affected_entities,
417 "should_delete": False,
418 "orphan_strategy": orphan_strategy.value,
419 "proxy_strategy": proxy_strategy.value,
420 }
421 )
422 except ValueError as e:
423 # Handle validation errors specifically
424 error_message = str(e)
425 current_app.logger.warning(
426 f"Validation error in check_orphans: {error_message}"
427 )
428 return (
429 jsonify(
430 {
431 "status": "error",
432 "error_type": "validation",
433 "message": gettext(
434 "An error occurred while checking for orphaned entities"
435 ),
436 }
437 ),
438 400,
439 )
440 except Exception as e:
441 # Handle other errors
442 error_message = f"Error checking orphans: {str(e)}"
443 current_app.logger.error(f"{error_message}\n{traceback.format_exc()}")
444 return (
445 jsonify(
446 {
447 "status": "error",
448 "error_type": "system",
449 "message": gettext(
450 "An error occurred while checking for orphaned entities"
451 ),
452 }
453 ),
454 500,
455 )
458@api_bp.route("/apply_changes", methods=["POST"])
459@login_required
460def apply_changes():
461 try:
462 # Remove all debug logging statements
463 changes = request.json
464 subject = changes[0]["subject"]
465 affected_entities = changes[0].get("affected_entities", [])
466 delete_affected = changes[0].get("delete_affected", False)
468 # Tieni traccia delle entità già eliminate per evitare duplicazioni
469 deleted_entities = set()
470 editor = Editor(
471 get_dataset_endpoint(),
472 get_provenance_endpoint(),
473 current_app.config["COUNTER_HANDLER"],
474 URIRef(f"https://orcid.org/{current_user.orcid}"),
475 current_app.config["PRIMARY_SOURCE"],
476 current_app.config["DATASET_GENERATION_TIME"],
477 dataset_is_quadstore=current_app.config["DATASET_IS_QUADSTORE"],
478 )
480 # Se c'è un'operazione di eliminazione completa dell'entità, includiamo le entità referenzianti
481 has_entity_deletion = any(
482 change["action"] == "delete" and not change.get("predicate")
483 for change in changes
484 )
486 # Import entity con l'opzione per includere le entità referenzianti se necessario
487 editor = import_entity_graph(
488 editor,
489 subject,
490 include_referencing_entities=has_entity_deletion
491 )
492 editor.preexisting_finished()
494 graph_uri = None
495 if editor.dataset_is_quadstore:
496 for quad in editor.g_set.quads((URIRef(subject), None, None, None)):
497 # Ottieni direttamente l'identificatore del grafo
498 graph_context = quad[3]
499 graph_uri = get_graph_uri_from_context(graph_context)
500 break
502 # Gestisci prima le creazioni
503 temp_id_to_uri = {}
504 for change in changes:
505 if change["action"] == "create":
506 data = change.get("data")
507 if data:
508 subject = create_logic(
509 editor,
510 data,
511 subject,
512 graph_uri,
513 temp_id_to_uri=temp_id_to_uri,
514 parent_entity_type=None,
515 )
517 # Poi gestisci le altre modifiche
518 orphan_strategy = current_app.config.get(
519 "ORPHAN_HANDLING_STRATEGY", OrphanHandlingStrategy.KEEP
520 )
521 proxy_strategy = current_app.config.get(
522 "PROXY_HANDLING_STRATEGY", ProxyHandlingStrategy.KEEP
523 )
524 # Separiamo le operazioni di delete in due fasi:
525 # 1. Prima eliminiamo tutte le entità orfane/intermedie
526 # 2. Poi eliminiamo le triple specifiche
528 # Fase 1: Elimina le entità orfane/intermedie
529 if affected_entities and delete_affected:
530 # Separa gli orfani dalle entità proxy
531 orphans = [entity for entity in affected_entities if not entity.get("is_intermediate")]
532 proxies = [entity for entity in affected_entities if entity.get("is_intermediate")]
534 # Gestione degli orfani secondo la strategia per gli orfani
535 should_delete_orphans = (
536 orphan_strategy == OrphanHandlingStrategy.DELETE
537 or (orphan_strategy == OrphanHandlingStrategy.ASK and delete_affected)
538 )
540 if should_delete_orphans and orphans:
541 for orphan in orphans:
542 orphan_uri = orphan["uri"]
543 if orphan_uri in deleted_entities:
544 continue
546 delete_logic(editor, orphan_uri, graph_uri=graph_uri)
547 deleted_entities.add(orphan_uri)
549 # Gestione delle entità proxy secondo la strategia per i proxy
550 should_delete_proxies = (
551 proxy_strategy == ProxyHandlingStrategy.DELETE
552 or (proxy_strategy == ProxyHandlingStrategy.ASK and delete_affected)
553 )
555 if should_delete_proxies and proxies:
556 for proxy in proxies:
557 proxy_uri = proxy["uri"]
558 if proxy_uri in deleted_entities:
559 continue
561 delete_logic(editor, proxy_uri, graph_uri=graph_uri)
562 deleted_entities.add(proxy_uri)
564 # Fase 2: Processa tutte le altre modifiche
565 for change in changes:
566 if change["action"] == "delete":
567 subject_uri = change["subject"]
568 predicate = change.get("predicate")
569 object_value = change.get("object")
571 # Se stiamo eliminando un'intera entità
572 if not predicate:
573 if subject_uri in deleted_entities:
574 continue
576 delete_logic(editor, subject_uri, graph_uri=graph_uri, entity_type=change.get("entity_type"))
577 deleted_entities.add(subject_uri)
578 # Se stiamo eliminando una tripla specifica
579 elif object_value:
580 # Controlla se l'oggetto è un'entità che è già stata eliminata
581 if object_value in deleted_entities:
582 continue
584 delete_logic(editor, subject_uri, predicate, object_value, graph_uri, change.get("entity_type"))
586 # La gestione degli orfani e dei proxy è stata spostata all'inizio del ciclo
588 elif change["action"] == "update":
589 update_logic(
590 editor,
591 change["subject"],
592 change["predicate"],
593 change["object"],
594 change["newObject"],
595 graph_uri,
596 change.get("entity_type"),
597 )
598 elif change["action"] == "order":
599 order_logic(
600 editor,
601 change["subject"],
602 change["predicate"],
603 change["object"],
604 change["newObject"],
605 graph_uri,
606 temp_id_to_uri,
607 )
609 try:
610 editor.save()
611 except ValueError as ve:
612 # Re-raise ValueError so it can be caught by the outer try-except block
613 current_app.logger.error(f"Error during save operation: {str(ve)}")
614 raise
615 except Exception as save_error:
616 current_app.logger.error(f"Error during save operation: {str(save_error)}")
617 return jsonify(
618 {
619 "status": "error",
620 "error_type": "database",
621 "message": gettext("Failed to save changes to the database: {}").format(str(save_error)),
622 }
623 ), 500
625 return (
626 jsonify(
627 {
628 "status": "success",
629 "message": gettext("Changes applied successfully"),
630 }
631 ),
632 200,
633 )
635 except ValueError as e:
636 # Handle validation errors specifically
637 error_message = str(e)
638 current_app.logger.warning(f"Validation error: {error_message}")
639 return (
640 jsonify(
641 {
642 "status": "error",
643 "error_type": "validation",
644 "message": error_message,
645 }
646 ),
647 400,
648 )
649 except Exception as e:
650 # Handle other errors
651 error_message = (
652 f"Error while applying changes: {str(e)}\n{traceback.format_exc()}"
653 )
654 current_app.logger.error(error_message)
655 return (
656 jsonify(
657 {
658 "status": "error",
659 "error_type": "system",
660 "message": gettext("An error occurred while applying changes"),
661 }
662 ),
663 500,
664 )
667def get_graph_uri_from_context(graph_context):
668 """Extract the graph URI from a graph context.
670 Args:
671 graph_context: Either a Graph object or a direct URI reference
673 Returns:
674 The graph URI
675 """
676 if isinstance(graph_context, Graph):
677 return graph_context.identifier
678 else:
679 return graph_context
682def determine_datatype(value, datatype_uris):
683 for datatype_uri in datatype_uris:
684 validation_func = next(
685 (d[1] for d in DATATYPE_MAPPING if str(d[0]) == str(datatype_uri)), None
686 )
687 if validation_func and validation_func(value):
688 return URIRef(datatype_uri)
689 # If none match, default to XSD.string
690 return XSD.string
693def create_logic(
694 editor: Editor,
695 data: Dict[str, dict],
696 subject=None,
697 graph_uri=None,
698 parent_subject=None,
699 parent_predicate=None,
700 temp_id_to_uri=None,
701 parent_entity_type=None,
702):
703 entity_type = data.get("entity_type")
704 properties = data.get("properties", {})
705 temp_id = data.get("tempId")
707 if subject is None:
708 subject = generate_unique_uri(entity_type)
710 if temp_id and temp_id_to_uri is not None:
711 temp_id_to_uri[temp_id] = str(subject)
713 # Create the entity type using validate_new_triple
714 if parent_subject is not None:
715 type_value, _, error_message = validate_new_triple(
716 subject, RDF.type, entity_type, "create", entity_types=entity_type
717 )
718 if error_message:
719 raise ValueError(error_message)
721 if type_value is not None:
722 editor.create(URIRef(subject), RDF.type, type_value, graph_uri)
724 # Create the relationship to the parent using validate_new_triple
725 if parent_subject and parent_predicate:
726 # When creating a relationship, we need to validate that the parent can have this relationship
727 # with an entity of our type. Pass our entity_type as the object_entity_type for validation
728 parent_value, _, error_message = validate_new_triple(
729 parent_subject,
730 parent_predicate,
731 subject,
732 "create",
733 entity_types=parent_entity_type,
734 )
735 if error_message:
736 raise ValueError(error_message)
738 if parent_value is not None:
739 editor.create(
740 URIRef(parent_subject),
741 URIRef(parent_predicate),
742 parent_value,
743 graph_uri,
744 )
746 for predicate, values in properties.items():
747 if not isinstance(values, list):
748 values = [values]
749 for value in values:
750 if isinstance(value, dict) and "entity_type" in value:
751 # For nested entities, create them first
752 nested_subject = generate_unique_uri(value["entity_type"])
753 create_logic(
754 editor,
755 value,
756 nested_subject,
757 graph_uri,
758 subject,
759 predicate,
760 temp_id_to_uri,
761 parent_entity_type=entity_type, # Pass the current entity type as parent_entity_type
762 )
763 else:
764 # Use validate_new_triple to validate and get the correctly typed value
765 object_value, _, error_message = validate_new_triple(
766 subject, predicate, value, "create", entity_types=entity_type
767 )
768 if error_message:
769 raise ValueError(error_message)
771 if object_value is not None:
772 editor.create(
773 URIRef(subject), URIRef(predicate), object_value, graph_uri
774 )
776 return subject
779def update_logic(
780 editor: Editor,
781 subject,
782 predicate,
783 old_value,
784 new_value,
785 graph_uri=None,
786 entity_type=None,
787):
788 new_value, old_value, error_message = validate_new_triple(
789 subject, predicate, new_value, "update", old_value, entity_types=entity_type
790 )
791 if error_message:
792 raise ValueError(error_message)
794 editor.update(URIRef(subject), URIRef(predicate), old_value, new_value, graph_uri)
797def rebuild_entity_order(
798 editor: Editor,
799 ordered_by_uri: URIRef,
800 entities: list,
801 graph_uri=None
802):
803 """
804 Rebuild the ordering chain for a list of entities.
806 Args:
807 editor: The editor instance
808 ordered_by_uri: The property used for ordering
809 entities: List of entities to be ordered
810 graph_uri: Optional graph URI
811 """
812 # First, remove all existing ordering relationships
813 for entity in entities:
814 for s, p, o in list(editor.g_set.triples((entity, ordered_by_uri, None))):
815 editor.delete(entity, ordered_by_uri, o, graph_uri)
817 # Then rebuild the chain with the entities
818 for i in range(len(entities) - 1):
819 current_entity = entities[i]
820 next_entity = entities[i + 1]
821 editor.create(current_entity, ordered_by_uri, next_entity, graph_uri)
823 return editor
826def delete_logic(
827 editor: Editor,
828 subject,
829 predicate=None,
830 object_value=None,
831 graph_uri=None,
832 entity_type=None,
833):
834 # Ensure we have the correct data types for all values
835 subject_uri = URIRef(subject)
836 predicate_uri = URIRef(predicate) if predicate else None
838 # Validate and get correctly typed object value if we have a predicate
839 if predicate and object_value:
840 # Use validate_new_triple to validate the deletion and get the correctly typed object
841 _, object_value, error_message = validate_new_triple(
842 subject, predicate, None, "delete", object_value, entity_types=entity_type
843 )
844 if error_message:
845 raise ValueError(error_message)
847 editor.delete(subject_uri, predicate_uri, object_value, graph_uri)
850def order_logic(
851 editor: Editor,
852 subject,
853 predicate,
854 new_order,
855 ordered_by,
856 graph_uri=None,
857 temp_id_to_uri: Optional[Dict] = None,
858):
859 subject_uri = URIRef(subject)
860 predicate_uri = URIRef(predicate)
861 ordered_by_uri = URIRef(ordered_by)
862 # Ottieni tutte le entità ordinate attuali direttamente dall'editor
863 current_entities = [
864 o for _, _, o in editor.g_set.triples((subject_uri, predicate_uri, None))
865 ]
867 # Dizionario per mappare le vecchie entità alle nuove
868 old_to_new_mapping = {}
870 # Per ogni entità attuale
871 for old_entity in current_entities:
872 if str(old_entity) in new_order: # Processa solo le entità preesistenti
873 # Memorizza tutte le proprietà dell'entità attuale
874 entity_properties = list(editor.g_set.triples((old_entity, None, None)))
876 entity_type = next(
877 (o for _, p, o in entity_properties if p == RDF.type), None
878 )
880 if entity_type is None:
881 raise ValueError(
882 f"Impossibile determinare il tipo dell'entità per {old_entity}"
883 )
885 # Crea una nuova entità
886 new_entity_uri = generate_unique_uri(entity_type)
887 old_to_new_mapping[old_entity] = new_entity_uri
889 # Cancella la vecchia entità
890 editor.delete(subject_uri, predicate_uri, old_entity, graph_uri)
891 editor.delete(old_entity, graph=graph_uri)
893 # Ricrea il collegamento tra il soggetto principale e la nuova entità
894 editor.create(subject_uri, predicate_uri, new_entity_uri, graph_uri)
896 # Ripristina tutte le altre proprietà per la nuova entità
897 for _, p, o in entity_properties:
898 if p != predicate_uri and p != ordered_by_uri:
899 editor.create(new_entity_uri, p, o, graph_uri)
901 # Prepara la lista delle entità nel nuovo ordine
902 ordered_entities = []
903 for entity in new_order:
904 new_entity_uri = old_to_new_mapping.get(URIRef(entity))
905 if not new_entity_uri:
906 new_entity_uri = URIRef(temp_id_to_uri.get(entity, entity))
907 ordered_entities.append(new_entity_uri)
909 # Ricostruisci l'ordine
910 if ordered_entities:
911 rebuild_entity_order(editor, ordered_by_uri, ordered_entities, graph_uri)
913 return editor
916@api_bp.route("/human-readable-entity", methods=["POST"])
917@login_required
918def get_human_readable_entity():
919 custom_filter = get_custom_filter()
921 # Check if required parameters are present
922 if "uri" not in request.form or "entity_class" not in request.form:
923 return jsonify({"status": "error", "message": "Missing required parameters"}), 400
925 uri = request.form["uri"]
926 entity_class = request.form["entity_class"]
927 filter_instance = custom_filter
928 readable = filter_instance.human_readable_entity(uri, [entity_class])
929 return readable