def delete(map_identifier, topic_identifier, file_identifier): topic_store = get_topic_store() topic_map = topic_store.get_map(map_identifier, current_user.id) if topic_map is None: abort(404) # If the map doesn't belong to the user and they don't have the right # collaboration mode on the map, then abort if not topic_map.owner and topic_map.collaboration_mode is not CollaborationMode.EDIT: abort(403) topic = topic_store.get_topic( map_identifier, topic_identifier, resolve_attributes=RetrievalMode.RESOLVE_ATTRIBUTES, ) if topic is None: abort(404) file_occurrence = topic_store.get_occurrence( map_identifier, file_identifier, resolve_attributes=RetrievalMode.RESOLVE_ATTRIBUTES, ) form_file_title = file_occurrence.get_attribute_by_name("title").value form_file_scope = file_occurrence.scope map_notes_count = topic_store.get_topic_occurrences_statistics( map_identifier, "notes")["note"] if request.method == "POST": # Delete file occurrence from topic store topic_store.delete_occurrence(map_identifier, file_occurrence.identifier) # Delete file from file system file_file_path = os.path.join( current_app.static_folder, RESOURCES_DIRECTORY, str(map_identifier), file_occurrence.resource_ref, ) if os.path.exists(file_file_path): os.remove(file_file_path) flash("3D content successfully deleted.", "warning") return redirect( url_for( "three_d.index", map_identifier=topic_map.identifier, topic_identifier=topic.identifier, )) return render_template( "three_d/delete.html", topic_map=topic_map, topic=topic, file_identifier=file_occurrence.identifier, file_title=form_file_title, file_scope=form_file_scope, map_notes_count=map_notes_count, )
def upload(map_identifier, topic_identifier): topic_store = get_topic_store() topic_map = topic_store.get_topic_map(map_identifier, current_user.id) if topic_map is None: abort(404) # If the map doesn't belong to the user and they don't have the right collaboration mode on the map, then abort if not topic_map.owner and topic_map.collaboration_mode is not CollaborationMode.EDIT: abort(403) topic = topic_store.get_topic( map_identifier, topic_identifier, resolve_attributes=RetrievalMode.RESOLVE_ATTRIBUTES, ) if topic is None: abort(404) form_image_title = "" form_image_scope = session["current_scope"] error = 0 if request.method == "POST": form_image_title = request.form["image-title"].strip() form_image_scope = request.form["image-scope"].strip() form_upload_file = request.files[ "image-file"] if "image-file" in request.files else None # If no values have been provided set their default values if not form_image_scope: form_image_scope = UNIVERSAL_SCOPE # Validate form inputs if not form_image_title: error = error | 1 if not form_upload_file: error = error | 2 else: if form_upload_file.filename == "": error = error | 4 elif not allowed_file(form_upload_file.filename): error = error | 8 if not topic_store.topic_exists(topic_map.identifier, form_image_scope): error = error | 16 if error != 0: flash( "An error occurred when uploading the image. Please review the warnings and fix accordingly.", "warning", ) else: image_file_name = f"{str(uuid.uuid4())}.{get_file_extension(form_upload_file.filename)}" # Create the image directory for this topic map and topic if it doesn't already exist image_directory = os.path.join(bp.root_path, RESOURCES_DIRECTORY, str(map_identifier), topic_identifier) if not os.path.isdir(image_directory): os.makedirs(image_directory) file_path = os.path.join(image_directory, image_file_name) form_upload_file.save(file_path) image_occurrence = Occurrence( instance_of="image", topic_identifier=topic.identifier, scope=form_image_scope, resource_ref=image_file_name, ) title_attribute = Attribute( "title", form_image_title, image_occurrence.identifier, data_type=DataType.STRING, ) # Persist objects to the topic store topic_store.set_occurrence(topic_map.identifier, image_occurrence) topic_store.set_attribute(topic_map.identifier, title_attribute) flash("Image successfully uploaded.", "success") return redirect( url_for( "image.index", map_identifier=topic_map.identifier, topic_identifier=topic.identifier, )) return render_template( "image/upload.html", error=error, topic_map=topic_map, topic=topic, image_title=form_image_title, image_scope=form_image_scope, )
def edit(map_identifier): topic_store = get_topic_store() topic_map = topic_store.get_topic_map(map_identifier, current_user.id) if topic_map is None: abort(404) if not topic_map.owner: abort(403) form_map_name = topic_map.name form_map_description = topic_map.description form_map_published = topic_map.published error = 0 if request.method == "POST": form_map_name = request.form["map-name"].strip() form_map_description = request.form["map-description"].strip() form_map_published = True if request.form.get("map-published") == "1" else False form_upload_file = request.files["map-image-file"] if "map-image-file" in request.files else None # Validate form inputs if not form_map_name: error = error | 1 if form_upload_file: if form_upload_file.filename == "": error = error | 2 elif not allowed_file(form_upload_file.filename): error = error | 4 if error != 0: flash( "An error occurred when submitting the form. Please review the warnings and fix accordingly.", "warning", ) else: if form_upload_file: # Upload the image for the topic map to the map's directory image_file_name = f"{str(uuid.uuid4())}.{get_file_extension(form_upload_file.filename)}" topic_map_directory = os.path.join(bp.root_path, RESOURCES_DIRECTORY, str(map_identifier)) file_path = os.path.join(topic_map_directory, image_file_name) form_upload_file.save(file_path) # TODO: Remove the map's previous image else: image_file_name = topic_map.image_path # Update the topic map promoted = form_map_published and topic_map.promoted topic_store.update_topic_map( map_identifier, form_map_name, form_map_description, image_file_name, published=form_map_published, promoted=promoted, ) flash("Map successfully updated.", "success") return redirect(url_for("map.view", map_identifier=map_identifier)) return render_template( "map/edit.html", error=error, topic_map=topic_map, map_name=form_map_name, map_description=form_map_description, map_shared=form_map_published, )
def delete_reference( map_identifier, topic_identifier, association_identifier, member_identifier, reference_identifier, ): topic_store = get_topic_store() topic_map = topic_store.get_topic_map(map_identifier, current_user.id) if topic_map is None: abort(404) # If the map doesn't belong to the user and they don't have the right # collaboration mode on the map, then abort if not topic_map.owner and topic_map.collaboration_mode is not CollaborationMode.EDIT: abort(403) topic = topic_store.get_topic( map_identifier, topic_identifier, resolve_attributes=RetrievalMode.RESOLVE_ATTRIBUTES, ) if topic is None: abort(404) association = topic_store.get_association(map_identifier, association_identifier) if association is None: abort(404) member = association.get_member(member_identifier) form_topic_reference = reference_identifier if request.method == "POST": if len(member.topic_refs) > 1: member.remove_topic_ref(form_topic_reference) topic_store.delete_association(map_identifier, association_identifier) topic_store.set_association(map_identifier, association) flash("Topic reference successfully deleted.", "warning") else: flash("Topic reference was not deleted.", "warning") return redirect( url_for( "association.view_member", map_identifier=topic_map.identifier, topic_identifier=topic.identifier, association_identifier=association_identifier, member_identifier=member_identifier, )) return render_template( "association/delete_reference.html", topic_map=topic_map, topic=topic, association=association, member=member, topic_reference=form_topic_reference, )
def edit(map_identifier, topic_identifier, image_identifier): topic_store = get_topic_store() topic_map = topic_store.get_topic_map(map_identifier, current_user.id) if topic_map is None: abort(404) # If the map doesn't belong to the user and they don't have the right collaboration mode on the map, then abort if not topic_map.owner and topic_map.collaboration_mode is not CollaborationMode.EDIT: abort(403) topic = topic_store.get_topic( map_identifier, topic_identifier, resolve_attributes=RetrievalMode.RESOLVE_ATTRIBUTES, ) if topic is None: abort(404) image_occurrence = topic_store.get_occurrence( map_identifier, image_identifier, resolve_attributes=RetrievalMode.RESOLVE_ATTRIBUTES, ) form_image_title = image_occurrence.get_attribute_by_name("title").value form_image_resource_ref = image_occurrence.resource_ref form_image_scope = image_occurrence.scope error = 0 if request.method == "POST": form_image_title = request.form["image-title"].strip() form_image_scope = request.form["image-scope"].strip() # If no values have been provided set their default values if not form_image_scope: form_image_scope = UNIVERSAL_SCOPE # Validate form inputs if not form_image_title: error = error | 1 if not topic_store.topic_exists(topic_map.identifier, form_image_scope): error = error | 2 if error != 0: flash( "An error occurred when submitting the form. Please review the warnings and fix accordingly.", "warning", ) else: # Update image's title if it has changed if image_occurrence.get_attribute_by_name( "title").value != form_image_title: topic_store.update_attribute_value( topic_map.identifier, image_occurrence.get_attribute_by_name("title").identifier, form_image_title, ) # Update image's scope if it has changed if image_occurrence.scope != form_image_scope: topic_store.update_occurrence_scope( map_identifier, image_occurrence.identifier, form_image_scope) flash("Image successfully updated.", "success") return redirect( url_for( "image.index", map_identifier=topic_map.identifier, topic_identifier=topic.identifier, )) return render_template( "image/edit.html", error=error, topic_map=topic_map, topic=topic, image_identifier=image_occurrence.identifier, image_title=form_image_title, image_resource_ref=form_image_resource_ref, image_scope=form_image_scope, )
def entity_edit( map_identifier, topic_identifier, entity_identifier, attribute_identifier, entity_type, ): topic_store = get_topic_store() topic_map = topic_store.get_topic_map(map_identifier) if topic_map is None: abort(404) if current_user.id != topic_map.user_identifier: abort(403) if "admin" not in current_user.roles: abort(403) topic = topic_store.get_topic( map_identifier, topic_identifier, resolve_attributes=RetrievalMode.RESOLVE_ATTRIBUTES, ) if topic is None: abort(404) entity = topic_store.get_association( map_identifier, entity_identifier, resolve_attributes=RetrievalMode.RESOLVE_ATTRIBUTES, ) if entity is None: entity = topic_store.get_occurrence(map_identifier, entity_identifier) if entity is None: abort(404) attribute = topic_store.get_attribute(map_identifier, attribute_identifier) if attribute is None: abort(404) form_attribute_name = attribute.name form_attribute_value = attribute.value form_attribute_type = str(attribute.data_type).capitalize() form_attribute_scope = attribute.scope error = 0 if request.method == "POST": form_attribute_name = request.form["attribute-name"].strip() form_attribute_value = request.form["attribute-value"].strip() form_attribute_type = request.form["attribute-type"] form_attribute_scope = request.form["attribute-scope"].strip() # If no values have been provided set their default values if not form_attribute_scope: form_attribute_scope = "*" # Universal scope # Validate form inputs if not form_attribute_name: error = error | 1 if not form_attribute_value: error = error | 2 if not topic_store.topic_exists(topic_map.identifier, form_attribute_scope): error = error | 4 if error != 0: flash( "An error occurred when submitting the form. Please review the warnings and fix accordingly.", "warning", ) else: # Update the attribute by deleting the existing attribute and adding a new one topic_store.delete_attribute(map_identifier, attribute.identifier) updated_attribute = Attribute( form_attribute_name, form_attribute_value, entity.identifier, data_type=DataType[form_attribute_type], ) topic_store.set_attribute(topic_map.identifier, updated_attribute) flash("Attribute successfully updated.", "success") return redirect( url_for( "attribute.entity_index", map_identifier=topic_map.identifier, topic_identifier=topic.identifier, entity_identifier=entity.identifier, entity_type=entity_type, )) data_types = [ ("STRING", "String"), ("NUMBER", "Number"), ("TIMESTAMP", "Timestamp"), ("BOOLEAN", "Boolean"), ] post_url = "attribute.entity_edit" cancel_url = "attribute.entity_index" return render_template( "attribute/edit.html", error=error, topic_map=topic_map, topic=topic, attribute=attribute, entity=entity, entity_type=entity_type, data_types=data_types, post_url=post_url, cancel_url=cancel_url, attribute_name=form_attribute_name, attribute_value=form_attribute_value, attribute_type=form_attribute_type, attribute_scope=form_attribute_scope, )
def entity_index(map_identifier, topic_identifier, entity_identifier, entity_type): topic_store = get_topic_store() topic_map = topic_store.get_topic_map(map_identifier) if topic_map is None: abort(404) if current_user.id != topic_map.user_identifier: abort(403) if "admin" not in current_user.roles: abort(403) topic = topic_store.get_topic( map_identifier, topic_identifier, resolve_attributes=RetrievalMode.RESOLVE_ATTRIBUTES, ) if topic is None: abort(404) entity = topic_store.get_association( map_identifier, entity_identifier, resolve_attributes=RetrievalMode.RESOLVE_ATTRIBUTES, ) if entity is None: entity = topic_store.get_occurrence(map_identifier, entity_identifier) if entity is None: abort(404) return_url = None if entity_type == "association": return_url = "association.index" elif entity_type == "image": return_url = "image.index" elif entity_type == "3d-scene": return_url = "three_d.index" elif entity_type == "file": return_url = "file.index" elif entity_type == "link": return_url = "link.index" elif entity_type == "video": return_url = "video.index" attributes = [] entity_attributes = topic_store.get_attributes(map_identifier, entity_identifier) for entity_attribute in entity_attributes: attributes.append({ "identifier": entity_attribute.identifier, "name": entity_attribute.name, "value": entity_attribute.value, "type": str(entity_attribute.data_type).lower(), "scope": entity_attribute.scope, }) creation_date_attribute = topic.get_attribute_by_name("creation-timestamp") creation_date = (maya.parse(creation_date_attribute.value) if creation_date_attribute else "Undefined") return render_template( "attribute/index.html", topic_map=topic_map, topic=topic, entity=entity, entity_type=entity_type, return_url=return_url, attributes=attributes, creation_date=creation_date, )
def delete(map_identifier, topic_identifier): topic_store = get_topic_store() topic_map = topic_store.get_map(map_identifier, current_user.id) if topic_map is None: current_app.logger.warning( f"Topic map not found: user identifier: [{current_user.id}], topic map identifier: [{map_identifier}]" ) abort(404) # If the map doesn't belong to the user and they don't have the right # collaboration mode on the map, then abort if not topic_map.owner and topic_map.collaboration_mode is not CollaborationMode.EDIT: abort(403) topic = topic_store.get_topic( map_identifier, topic_identifier, resolve_attributes=RetrievalMode.RESOLVE_ATTRIBUTES, ) if topic is None: current_app.logger.warning( f"Topic not found: user identifier: [{current_user.id}], topic map identifier: [{map_identifier}], topic identifier: [{topic_identifier}]" ) abort(404) map_notes_count = topic_store.get_topic_occurrences_statistics( map_identifier, "notes")["note"] if request.method == "POST": try: # Remove the topic from the topic store topic_store.delete_topic(map_identifier, topic_identifier) # Clear the breadcrumbs (of which this topic was part of) session["breadcrumbs"] = [] # Remove the topic's resources directory topic_directory = os.path.join(bp.root_path, RESOURCES_DIRECTORY, str(map_identifier), topic_identifier) if os.path.isdir(topic_directory): shutil.rmtree(topic_directory) except TopicDbError: flash( "Topic not deleted. Certain predefined topics cannot be deleted. Alternatively, you attempted to delete an association.", "warning", ) return redirect( url_for( "topic.view", map_identifier=topic_map.identifier, topic_identifier=topic_identifier, )) flash("Topic successfully deleted.", "success") return redirect( url_for( "topic.view", map_identifier=topic_map.identifier, topic_identifier="home", )) return render_template( "topic/delete.html", topic_map=topic_map, topic=topic, map_notes_count=map_notes_count, )
def add_note(map_identifier, topic_identifier): topic_store = get_topic_store() topic_map = topic_store.get_map(map_identifier, current_user.id) if topic_map is None: current_app.logger.warning( f"Topic map not found: user identifier: [{current_user.id}], topic map identifier: [{map_identifier}]" ) abort(404) # If the map doesn't belong to the user and they don't have the right # collaboration mode on the map, then abort if not topic_map.owner and ( topic_map.collaboration_mode is not CollaborationMode.EDIT and topic_map.collaboration_mode is not CollaborationMode.COMMENT): abort(403) topic = topic_store.get_topic( map_identifier, topic_identifier, resolve_attributes=RetrievalMode.RESOLVE_ATTRIBUTES, ) if topic is None: current_app.logger.warning( f"Topic not found: user identifier: [{current_user.id}], topic map identifier: [{map_identifier}], topic identifier: [{topic_identifier}]" ) abort(404) map_notes_count = topic_store.get_topic_occurrences_statistics( map_identifier, "notes")["note"] error = 0 if request.method == "POST": form_note_title = request.form["note-title"].strip() form_note_text = request.form["note-text"].strip() form_note_scope = request.form["note-scope"].strip() # If no values have been provided set their default values if not form_note_scope: form_note_scope = session["current_scope"] # Validate form inputs if not form_note_title: error = error | 1 if not form_note_text: error = error | 2 if not topic_store.topic_exists(topic_map.identifier, form_note_scope): error = error | 4 if error != 0: flash( "An error occurred when submitting the form. Please review the warnings and fix accordingly.", "warning", ) else: note_occurrence = Occurrence( instance_of="note", topic_identifier=topic.identifier, scope=form_note_scope, resource_data=form_note_text, ) title_attribute = Attribute( "title", form_note_title, note_occurrence.identifier, data_type=DataType.STRING, ) timestamp = str(datetime.now()) modification_attribute = Attribute( "modification-timestamp", timestamp, note_occurrence.identifier, data_type=DataType.TIMESTAMP, ) # Persist objects to the topic store topic_store.create_occurrence(topic_map.identifier, note_occurrence) topic_store.create_attribute(topic_map.identifier, title_attribute) topic_store.create_attribute(topic_map.identifier, modification_attribute) flash("Note successfully added.", "success") return redirect( url_for( "topic.view", map_identifier=topic_map.identifier, topic_identifier=topic.identifier, )) return render_template( "topic/add_note.html", error=error, topic_map=topic_map, topic=topic, note_title=form_note_title, note_text=form_note_text, note_scope=form_note_scope, map_notes_count=map_notes_count, ) return render_template( "topic/add_note.html", error=error, topic_map=topic_map, topic=topic, map_notes_count=map_notes_count, )
def view(map_identifier, topic_identifier): topic_store = get_topic_store() collaboration_mode = None is_map_owner = False if current_user.is_authenticated: # User is logged in is_map_owner = topic_store.is_map_owner(map_identifier, current_user.id) if is_map_owner: topic_map = topic_store.get_map(map_identifier, current_user.id) else: topic_map = topic_store.get_map(map_identifier) if topic_map is None: current_app.logger.warning( f"Topic map not found: user identifier: [{current_user.id}], topic map identifier: [{map_identifier}]" ) abort(404) collaboration_mode = topic_store.get_collaboration_mode( map_identifier, current_user.id) if topic_map.published: if not is_map_owner and topic_identifier == "home": flash( "You are accessing a published topic map of another user.", "primary", ) else: if not is_map_owner: # The map is private and doesn't belong to the user who is trying to access it if not collaboration_mode: # The user is not collaborating on the map abort(403) else: # User is not logged in topic_map = topic_store.get_map(map_identifier) if topic_map is None: current_app.logger.warning( f"Topic map not found: user identifier: [N/A], topic map identifier: [{map_identifier}]" ) abort(404) if not topic_map.published: # User is not logged in and the map is not published abort(403) # Determine if (active) scope filtering has been specified in the URL scope_filtered = request.args.get("filter", type=int) if scope_filtered is not None: session["scope_filter"] = scope_filtered if "scope_filter" in session: scope_filtered = session["scope_filter"] else: session["breadcrumbs"] = [] session["current_scope"] = UNIVERSAL_SCOPE session["scope_filter"] = 1 # If a scope has been specified in the URL, then use that to set the scope scope_identifier = request.args.get("scope", type=str) if scope_identifier and topic_store.topic_exists(map_identifier, scope_identifier): session["current_scope"] = scope_identifier # Get topic if scope_filtered: topic = topic_store.get_topic( map_identifier, topic_identifier, scope=session["current_scope"], resolve_attributes=RetrievalMode.RESOLVE_ATTRIBUTES, ) else: topic = topic_store.get_topic( map_identifier, topic_identifier, resolve_attributes=RetrievalMode.RESOLVE_ATTRIBUTES, ) if topic is None: if current_user.is_authenticated: # User is logged in current_app.logger.warning( f"Topic not found: user identifier: [{current_user.id}], topic map identifier: [{map_identifier}], topic identifier: [{topic_identifier}]" ) else: current_app.logger.warning( f"Topic not found: user identifier: [N/A], topic map identifier: [{map_identifier}], topic identifier: [{topic_identifier}]" ) session["inexistent_topic_identifier"] = topic_identifier abort(404) else: session.pop("inexistent_topic_identifier", None) if scope_filtered: topic_occurrences = topic_store.get_topic_occurrences( map_identifier, topic_identifier, scope=session["current_scope"], inline_resource_data=RetrievalMode.INLINE_RESOURCE_DATA, resolve_attributes=RetrievalMode.RESOLVE_ATTRIBUTES, ) else: topic_occurrences = topic_store.get_topic_occurrences( map_identifier, topic_identifier, inline_resource_data=RetrievalMode.INLINE_RESOURCE_DATA, resolve_attributes=RetrievalMode.RESOLVE_ATTRIBUTES, ) occurrences = { "text": None, "images": [], "3d-scenes": [], "files": [], "links": [], "videos": [], "notes": [], } for occurrence in topic_occurrences: if occurrence.instance_of == "text" and occurrence.scope == session[ "current_scope"]: if occurrence.resource_data: markdown = mistune.create_markdown( renderer=HighlightRenderer(escape=False), plugins=[ "strikethrough", "footnotes", "table", ], ) occurrences["text"] = markdown( occurrence.resource_data.decode()) elif occurrence.instance_of == "image": occurrences["images"].append({ "title": occurrence.get_attribute_by_name("title").value, "url": occurrence.resource_ref, }) elif occurrence.instance_of == "3d-scene": occurrences["3d-scenes"].append({ "title": occurrence.get_attribute_by_name("title").value, "url": occurrence.resource_ref, }) elif occurrence.instance_of == "file": occurrences["files"].append({ "title": occurrence.get_attribute_by_name("title").value, "url": occurrence.resource_ref, }) elif occurrence.instance_of == "url": occurrences["links"].append({ "title": occurrence.get_attribute_by_name("title").value, "url": occurrence.resource_ref, }) elif occurrence.instance_of == "video": occurrences["videos"].append({ "title": occurrence.get_attribute_by_name("title").value, "url": occurrence.resource_ref, }) elif occurrence.instance_of == "note": markdown = mistune.create_markdown( renderer=HighlightRenderer(escape=False), plugins=[ "strikethrough", "footnotes", "table", ], ) occurrences["notes"].append({ "identifier": occurrence.identifier, "title": occurrence.get_attribute_by_name("title").value, "timestamp": maya.parse( occurrence.get_attribute_by_name( "modification-timestamp").value), "text": markdown(occurrence.resource_data.decode()), }) if scope_filtered: associations = topic_store.get_association_groups( map_identifier, topic_identifier, scope=session["current_scope"]) else: associations = topic_store.get_association_groups( map_identifier, topic_identifier) is_knowledge_path_topic = (("navigation", "up") in associations or ("navigation", "down") in associations or ("navigation", "previous") in associations or ("navigation", "next") in associations) creation_date = maya.parse( topic.get_attribute_by_name("creation-timestamp").value) modification_date_attribute = topic.get_attribute_by_name( "modification-timestamp") modification_date = maya.parse( modification_date_attribute.value ) if modification_date_attribute else "Undefined" # Breadcrumbs if "map_identifier" not in session: session["map_identifier"] = topic_map.identifier elif session["map_identifier"] != topic_map.identifier: session["breadcrumbs"] = [] session["map_identifier"] = topic_map.identifier if "breadcrumbs" not in session: session["breadcrumbs"] = [] breadcrumbs = deque(session["breadcrumbs"], BREADCRUMBS_COUNT) if topic_identifier in breadcrumbs: breadcrumbs.remove(topic_identifier) breadcrumbs.append(topic_identifier) session["breadcrumbs"] = list(breadcrumbs) from contextualise import api associations_state = api.get_association_groups(map_identifier, topic_identifier, session["current_scope"], scope_filtered) associations_state = (associations_state[RESPONSE].data.decode("utf-8") if associations_state[STATUS_CODE] == 200 else None) map_notes_count = topic_store.get_topic_occurrences_statistics( map_identifier, "notes")["note"] return render_template( "topic/view.html", topic_map=topic_map, topic=topic, occurrences=occurrences, associations=associations, associations_state=associations_state, is_knowledge_path_topic=is_knowledge_path_topic, creation_date=creation_date, modification_date=modification_date, breadcrumbs=breadcrumbs, collaboration_mode=collaboration_mode, is_map_owner=is_map_owner, map_notes_count=map_notes_count, )
def edit(map_identifier, topic_identifier): topic_store = get_topic_store() topic_map = topic_store.get_map(map_identifier, current_user.id) if topic_map is None: current_app.logger.warning( f"Topic map not found: user identifier: [{current_user.id}], topic map identifier: [{map_identifier}]" ) abort(404) # If the map doesn't belong to the user and they don't have the right # collaboration mode on the map, then abort if not topic_map.owner and topic_map.collaboration_mode is not CollaborationMode.EDIT: abort(403) topic = topic_store.get_topic( map_identifier, topic_identifier, scope=session["current_scope"], resolve_attributes=RetrievalMode.RESOLVE_ATTRIBUTES, ) if topic is None: current_app.logger.warning( f"Topic not found: user identifier: [{current_user.id}], topic map identifier: [{map_identifier}], topic identifier: [{topic_identifier}]" ) abort(404) occurrences = topic_store.get_topic_occurrences( map_identifier, topic_identifier, scope=session["current_scope"], inline_resource_data=RetrievalMode.INLINE_RESOURCE_DATA, ) texts = [ occurrence for occurrence in occurrences if occurrence.instance_of == "text" and occurrence.scope == session["current_scope"] ] form_topic_name = topic.first_base_name.name form_topic_text = texts[0].resource_data.decode( ) if len(texts) > 0 and texts[0].resource_data else "" form_topic_instance_of = topic.instance_of form_topic_text_scope = texts[0].scope if len( texts) > 0 else session["current_scope"] map_notes_count = topic_store.get_topic_occurrences_statistics( map_identifier, "notes")["note"] error = 0 if request.method == "POST": form_topic_name = request.form["topic-name"].strip() form_topic_text = request.form["topic-text"].strip() form_topic_instance_of = request.form["topic-instance-of"].strip() form_topic_text_scope = request.form["topic-text-scope"].strip() # If no values have been provided set their default values if not form_topic_instance_of: form_topic_instance_of = "topic" if not form_topic_text_scope: form_topic_text_scope = session["current_scope"] # Validate form inputs if not topic_store.topic_exists(topic_map.identifier, form_topic_instance_of): error = error | 1 if not topic_store.topic_exists(topic_map.identifier, form_topic_text_scope): error = error | 2 if error != 0: flash( "An error occurred when submitting the form. Please review the warnings and fix accordingly.", "warning", ) else: if topic.get_base_name_by_scope(session["current_scope"]): # Update the topic's base name if it has changed if topic.first_base_name.name != form_topic_name: topic_store.update_base_name( map_identifier, topic.first_base_name.identifier, form_topic_name, form_topic_text_scope, ) else: base_name = BaseName(form_topic_name, session["current_scope"]) topic_store.create_base_name(map_identifier, topic.identifier, base_name) # Update topic's 'instance of' if it has changed if topic.instance_of != form_topic_instance_of: topic_store.update_topic_instance_of(map_identifier, topic.identifier, form_topic_instance_of) # If the topic has an existing text occurrence update it, otherwise create a new text occurrence # and persist it if len(texts ) > 0 and form_topic_text_scope == session["current_scope"]: topic_store.update_occurrence_data(map_identifier, texts[0].identifier, form_topic_text) else: text_occurrence = Occurrence( instance_of="text", topic_identifier=topic.identifier, scope=form_topic_text_scope, resource_data=form_topic_text, ) topic_store.create_occurrence(topic_map.identifier, text_occurrence) # Update the topic's modification (timestamp) attribute timestamp = str(datetime.now()) if topic.get_attribute_by_name("modification-timestamp"): topic_store.update_attribute_value( topic_map.identifier, topic.get_attribute_by_name( "modification-timestamp").identifier, timestamp, ) else: modification_attribute = Attribute( "modification-timestamp", timestamp, topic.identifier, data_type=DataType.TIMESTAMP, ) topic_store.create_attribute(topic_map.identifier, modification_attribute) flash("Topic successfully updated.", "success") return redirect( url_for( "topic.view", map_identifier=topic_map.identifier, topic_identifier=topic.identifier, )) return render_template( "topic/edit.html", error=error, topic_map=topic_map, topic=topic, topic_name=form_topic_name, topic_identifier=topic.identifier, topic_text=form_topic_text, topic_instance_of=form_topic_instance_of, topic_text_scope=form_topic_text_scope, collaboration_mode=topic_map.collaboration_mode, map_notes_count=map_notes_count, )
def edit_identifier(map_identifier, topic_identifier): topic_store = get_topic_store() topic_map = topic_store.get_map(map_identifier, current_user.id) if topic_map is None: current_app.logger.warning( f"Topic map not found: user identifier: [{current_user.id}], topic map identifier: [{map_identifier}]" ) abort(404) # If the map doesn't belong to the user and they don't have the right # collaboration mode on the map, then abort if not topic_map.owner and topic_map.collaboration_mode is not CollaborationMode.EDIT: abort(403) topic = topic_store.get_topic( map_identifier, topic_identifier, resolve_attributes=RetrievalMode.RESOLVE_ATTRIBUTES, ) if topic is None: current_app.logger.warning( f"Topic not found: user identifier: [{current_user.id}], topic map identifier: [{map_identifier}], topic identifier: [{topic_identifier}]" ) abort(404) form_topic_identifier = topic.identifier map_notes_count = topic_store.get_topic_occurrences_statistics( map_identifier, "notes")["note"] error = 0 if request.method == "POST": form_topic_identifier = request.form["topic-identifier"].strip() # Validate form inputs if not form_topic_identifier: error = error | 1 if topic_store.topic_exists(topic_map.identifier, form_topic_identifier): error = error | 2 if error != 0: flash( "An error occurred when submitting the form. Please review the warnings and fix accordingly.", "warning", ) else: # Update topic identifier try: topic_store.update_topic_identifier(map_identifier, topic_identifier, form_topic_identifier) except TopicDbError: flash( "Topic identifier not updated. Certain predefined topics cannot be modified.", "warning", ) return redirect( url_for( "topic.edit_identifier", map_identifier=topic_map.identifier, topic_identifier=topic_identifier, )) flash("Identifier successfully updated.", "success") return redirect( url_for( "topic.view", map_identifier=topic_map.identifier, topic_identifier=form_topic_identifier, )) return render_template( "topic/edit_identifier.html", error=error, topic_map=topic_map, topic=topic, topic_identifier=form_topic_identifier, map_notes_count=map_notes_count, )
def change_scope(map_identifier, topic_identifier, scope_identifier): topic_store = get_topic_store() topic_map = topic_store.get_map(map_identifier, current_user.id) if topic_map is None: current_app.logger.warning( f"Topic map not found: user identifier: [{current_user.id}], topic map identifier: [{map_identifier}]" ) abort(404) topic = topic_store.get_topic( map_identifier, topic_identifier, resolve_attributes=RetrievalMode.RESOLVE_ATTRIBUTES, ) if topic is None: current_app.logger.warning( f"Topic not found: user identifier: [{current_user.id}], topic map identifier: [{map_identifier}], topic identifier: [{topic_identifier}]" ) abort(404) form_scope = scope_identifier map_notes_count = topic_store.get_topic_occurrences_statistics( map_identifier, "notes")["note"] error = 0 if request.method == "POST": form_scope = request.form["new-scope"].strip() # If no values have been provided set their default values if not form_scope: form_scope = UNIVERSAL_SCOPE # Validate form inputs if not topic_store.topic_exists(topic_map.identifier, form_scope): error = error | 1 if error != 0: flash( "An error occurred when submitting the form. Please review the warnings and fix accordingly.", "warning", ) else: session["current_scope"] = form_scope flash("Scope successfully changed.", "success") return redirect( url_for( "topic.view", map_identifier=topic_map.identifier, topic_identifier=topic.identifier, )) return render_template( "topic/change_scope.html", error=error, topic_map=topic_map, topic=topic, scope_identifier=form_scope, map_notes_count=map_notes_count, )
def upload(map_identifier, topic_identifier): topic_store = get_topic_store() topic_map = topic_store.get_map(map_identifier, current_user.id) if topic_map is None: abort(404) # If the map doesn't belong to the user and they don't have the right # collaboration mode on the map, then abort if not topic_map.owner and topic_map.collaboration_mode is not CollaborationMode.EDIT: abort(403) topic = topic_store.get_topic( map_identifier, topic_identifier, resolve_attributes=RetrievalMode.RESOLVE_ATTRIBUTES, ) if topic is None: abort(404) map_notes_count = topic_store.get_topic_occurrences_statistics( map_identifier, "notes")["note"] error = 0 if request.method == "POST": form_file_title = request.form["file-title"].strip() form_file_scope = request.form["file-scope"].strip() form_upload_file = request.files[ "file-file"] if "file-file" in request.files else None # If no values have been provided set their default values if not form_file_scope: form_file_scope = session["current_scope"] # Validate form inputs if not form_file_title: error = error | 1 if not form_upload_file: error = error | 2 else: if form_upload_file.filename == "": error = error | 4 if not topic_store.topic_exists(topic_map.identifier, form_file_scope): error = error | 8 if error != 0: flash( "An error occurred when uploading the 3D content. Please review the warnings and fix accordingly.", "warning", ) else: file_extension = get_file_extension(form_upload_file.filename) file_file_name = f"{str(uuid.uuid4())}.{file_extension}" # Create the file directory for this topic map if it doesn't already exist file_directory = os.path.join(current_app.static_folder, RESOURCES_DIRECTORY, str(map_identifier)) if not os.path.isdir(file_directory): os.makedirs(file_directory) file_path = os.path.join(file_directory, file_file_name) form_upload_file.save(file_path) file_occurrence = Occurrence( instance_of="3d-scene", topic_identifier=topic.identifier, scope=form_file_scope, resource_ref=file_file_name, ) title_attribute = Attribute( "title", form_file_title, file_occurrence.identifier, data_type=DataType.STRING, ) # Persist objects to the topic store topic_store.create_occurrence(topic_map.identifier, file_occurrence) topic_store.create_attribute(topic_map.identifier, title_attribute) flash("3D content successfully uploaded.", "success") return redirect( url_for( "three_d.index", map_identifier=topic_map.identifier, topic_identifier=topic.identifier, )) return render_template( "three_d/upload.html", error=error, topic_map=topic_map, topic=topic, file_title=form_file_title, file_scope=form_file_scope, map_notes_count=map_notes_count, ) return render_template( "three_d/upload.html", error=error, topic_map=topic_map, topic=topic, map_notes_count=map_notes_count, )
def edit_note(map_identifier, topic_identifier, note_identifier): topic_store = get_topic_store() topic_map = topic_store.get_topic_map(map_identifier) if topic_map is None: abort(404) if current_user.id != topic_map.user_identifier: abort(403) topic = topic_store.get_topic( map_identifier, topic_identifier, resolve_attributes=RetrievalMode.RESOLVE_ATTRIBUTES, ) if topic is None: abort(404) note_occurrence = topic_store.get_occurrence( map_identifier, note_identifier, inline_resource_data=RetrievalMode.INLINE_RESOURCE_DATA, resolve_attributes=RetrievalMode.RESOLVE_ATTRIBUTES, ) form_note_title = note_occurrence.get_attribute_by_name("title").value form_note_text = note_occurrence.resource_data.decode() form_note_scope = note_occurrence.scope error = 0 if request.method == "POST": form_note_title = request.form["note-title"].strip() form_note_text = request.form["note-text"].strip() form_note_scope = request.form["note-scope"].strip() # If no values have been provided set their default values if not form_note_scope: form_note_scope = "*" # Universal scope # Validate form inputs if not form_note_title: error = error | 1 if not form_note_text: error = error | 2 if not topic_store.topic_exists(topic_map.identifier, form_note_scope): error = error | 4 if error != 0: flash( "An error occurred when submitting the form. Please review the warnings and fix accordingly.", "warning", ) else: # Update note's title if it has changed if note_occurrence.get_attribute_by_name( "title").value != form_note_title: topic_store.update_attribute_value( topic_map.identifier, note_occurrence.get_attribute_by_name("title").identifier, form_note_title, ) # Update the note's modification (timestamp) attribute timestamp = str(datetime.now()) topic_store.update_attribute_value( topic_map.identifier, note_occurrence.get_attribute_by_name( "modification-timestamp").identifier, timestamp, ) # Update note (occurrence) topic_store.update_occurrence_data(map_identifier, note_occurrence.identifier, form_note_text) # Update note's scope if it has changed if note_occurrence.scope != form_note_scope: topic_store.update_occurrence_scope(map_identifier, note_occurrence.identifier, form_note_scope) flash("Note successfully updated.", "success") return redirect( url_for( "topic.view", map_identifier=topic_map.identifier, topic_identifier=topic.identifier, )) return render_template( "topic/edit_note.html", error=error, topic_map=topic_map, topic=topic, note_identifier=note_occurrence.identifier, note_title=form_note_title, note_text=form_note_text, note_scope=form_note_scope, )
def edit_note(map_identifier, topic_identifier, note_identifier): topic_store = get_topic_store() topic_map = topic_store.get_map(map_identifier, current_user.id) if topic_map is None: current_app.logger.warning( f"Topic map not found: user identifier: [{current_user.id}], topic map identifier: [{map_identifier}]" ) abort(404) # If the map doesn't belong to the user and they don't have the right # collaboration mode on the map, then abort if not topic_map.owner and ( topic_map.collaboration_mode is not CollaborationMode.EDIT and topic_map.collaboration_mode is not CollaborationMode.COMMENT): abort(403) topic = topic_store.get_topic( map_identifier, topic_identifier, resolve_attributes=RetrievalMode.RESOLVE_ATTRIBUTES, ) if topic is None: current_app.logger.warning( f"Topic not found: user identifier: [{current_user.id}], topic map identifier: [{map_identifier}], topic identifier: [{topic_identifier}]" ) abort(404) note_occurrence = topic_store.get_occurrence( map_identifier, note_identifier, inline_resource_data=RetrievalMode.INLINE_RESOURCE_DATA, resolve_attributes=RetrievalMode.RESOLVE_ATTRIBUTES, ) form_note_title = note_occurrence.get_attribute_by_name("title").value form_note_text = note_occurrence.resource_data.decode() form_note_scope = note_occurrence.scope map_notes_count = topic_store.get_topic_occurrences_statistics( map_identifier, "notes")["note"] error = 0 if request.method == "POST": form_note_title = request.form["note-title"].strip() form_note_text = request.form["note-text"].strip() form_note_scope = request.form["note-scope"].strip() # If no values have been provided set their default values if not form_note_scope: form_note_scope = session["current_scope"] # Validate form inputs if not form_note_title: error = error | 1 if not form_note_text: error = error | 2 if not topic_store.topic_exists(topic_map.identifier, form_note_scope): error = error | 4 if error != 0: flash( "An error occurred when submitting the form. Please review the warnings and fix accordingly.", "warning", ) else: # Update note's title if it has changed if note_occurrence.get_attribute_by_name( "title").value != form_note_title: topic_store.update_attribute_value( topic_map.identifier, note_occurrence.get_attribute_by_name("title").identifier, form_note_title, ) # Update the note's modification (timestamp) attribute timestamp = str(datetime.now()) topic_store.update_attribute_value( topic_map.identifier, note_occurrence.get_attribute_by_name( "modification-timestamp").identifier, timestamp, ) # Update note (occurrence) topic_store.update_occurrence_data(map_identifier, note_occurrence.identifier, form_note_text) # Update note's scope if it has changed if note_occurrence.scope != form_note_scope: topic_store.update_occurrence_scope(map_identifier, note_occurrence.identifier, form_note_scope) flash("Note successfully updated.", "success") return redirect( url_for( "topic.view", map_identifier=topic_map.identifier, topic_identifier=topic.identifier, )) return render_template( "topic/edit_note.html", error=error, topic_map=topic_map, topic=topic, note_identifier=note_occurrence.identifier, note_title=form_note_title, note_text=form_note_text, note_scope=form_note_scope, map_notes_count=map_notes_count, )
def entity_add(map_identifier, topic_identifier, entity_identifier, entity_type): topic_store = get_topic_store() topic_map = topic_store.get_topic_map(map_identifier) if topic_map is None: abort(404) if current_user.id != topic_map.user_identifier: abort(403) if "admin" not in current_user.roles: abort(403) topic = topic_store.get_topic( map_identifier, topic_identifier, resolve_attributes=RetrievalMode.RESOLVE_ATTRIBUTES, ) if topic is None: abort(404) entity = topic_store.get_association( map_identifier, entity_identifier, resolve_attributes=RetrievalMode.RESOLVE_ATTRIBUTES, ) if entity is None: entity = topic_store.get_occurrence(map_identifier, entity_identifier) if entity is None: abort(404) form_attribute_name = "" form_attribute_value = "" form_attribute_type = "" form_attribute_scope = "*" error = 0 if request.method == "POST": form_attribute_name = request.form["attribute-name"].strip() form_attribute_value = request.form["attribute-value"].strip() form_attribute_type = request.form["attribute-type"] form_attribute_scope = request.form["attribute-scope"].strip() # If no values have been provided set their default values if not form_attribute_scope: form_attribute_scope = "*" # Universal scope # Validate form inputs if not form_attribute_name: error = error | 1 if not form_attribute_value: error = error | 2 if not topic_store.topic_exists(topic_map.identifier, form_attribute_scope): error = error | 4 if error != 0: flash( "An error occurred when submitting the form. Please review the warnings and fix accordingly.", "warning", ) else: attribute = Attribute( form_attribute_name, form_attribute_value, entity.identifier, data_type=DataType[form_attribute_type], ) # Persist objects to the topic store topic_store.set_attribute(topic_map.identifier, attribute) flash("Attribute successfully added.", "success") return redirect( url_for( "attribute.entity_index", map_identifier=topic_map.identifier, topic_identifier=topic.identifier, entity_identifier=entity.identifier, entity_type=entity_type, )) post_url = "attribute.entity_add" cancel_url = "attribute.entity_index" return render_template( "attribute/add.html", error=error, topic_map=topic_map, topic=topic, entity=entity, entity_type=entity_type, post_url=post_url, cancel_url=cancel_url, attribute_name=form_attribute_name, attribute_value=form_attribute_value, attribute_type=form_attribute_type, attribute_scope=form_attribute_scope, )
def delete_note(map_identifier, topic_identifier, note_identifier): topic_store = get_topic_store() topic_map = topic_store.get_map(map_identifier, current_user.id) if topic_map is None: current_app.logger.warning( f"Topic map not found: user identifier: [{current_user.id}], topic map identifier: [{map_identifier}]" ) abort(404) # If the map doesn't belong to the user and they don't have the right # collaboration mode on the map, then abort if not topic_map.owner and ( topic_map.collaboration_mode is not CollaborationMode.EDIT and topic_map.collaboration_mode is not CollaborationMode.COMMENT): abort(403) topic = topic_store.get_topic( map_identifier, topic_identifier, resolve_attributes=RetrievalMode.RESOLVE_ATTRIBUTES, ) if topic is None: current_app.logger.warning( f"Topic not found: user identifier: [{current_user.id}], topic map identifier: [{map_identifier}], topic identifier: [{topic_identifier}]" ) abort(404) note_occurrence = topic_store.get_occurrence( map_identifier, note_identifier, inline_resource_data=RetrievalMode.INLINE_RESOURCE_DATA, resolve_attributes=RetrievalMode.RESOLVE_ATTRIBUTES, ) form_note_title = note_occurrence.get_attribute_by_name("title").value markdown = mistune.create_markdown( renderer=HighlightRenderer(escape=False), plugins=[ "strikethrough", "footnotes", "table", ], ) form_note_text = markdown(note_occurrence.resource_data.decode()) form_note_scope = note_occurrence.scope map_notes_count = topic_store.get_topic_occurrences_statistics( map_identifier, "notes")["note"] if request.method == "POST": topic_store.delete_occurrence(map_identifier, note_occurrence.identifier) flash("Note successfully deleted.", "warning") return redirect( url_for( "topic.view", map_identifier=topic_map.identifier, topic_identifier=topic.identifier, )) return render_template( "topic/delete_note.html", topic_map=topic_map, topic=topic, note_identifier=note_occurrence.identifier, note_title=form_note_title, note_text=form_note_text, note_scope=form_note_scope, map_notes_count=map_notes_count, )
def entity_delete( map_identifier, topic_identifier, entity_identifier, attribute_identifier, entity_type, ): topic_store = get_topic_store() topic_map = topic_store.get_topic_map(map_identifier) if topic_map is None: abort(404) if current_user.id != topic_map.user_identifier: abort(403) if "admin" not in current_user.roles: abort(403) topic = topic_store.get_topic( map_identifier, topic_identifier, resolve_attributes=RetrievalMode.RESOLVE_ATTRIBUTES, ) if topic is None: abort(404) entity = topic_store.get_association( map_identifier, entity_identifier, resolve_attributes=RetrievalMode.RESOLVE_ATTRIBUTES, ) if entity is None: entity = topic_store.get_occurrence(map_identifier, entity_identifier) if entity is None: abort(404) attribute = topic_store.get_attribute(map_identifier, attribute_identifier) if attribute is None: abort(404) form_attribute_name = attribute.name form_attribute_value = attribute.value form_attribute_type = str(attribute.data_type).capitalize() form_attribute_scope = attribute.scope if request.method == "POST": # Delete attribute from topic store topic_store.delete_attribute(map_identifier, attribute.identifier) flash("Attribute successfully deleted.", "warning") return redirect( url_for( "attribute.entity_index", map_identifier=topic_map.identifier, topic_identifier=topic.identifier, entity_identifier=entity.identifier, entity_type=entity_type, )) post_url = "attribute.entity_delete" cancel_url = "attribute.entity_index" return render_template( "attribute/delete.html", topic_map=topic_map, topic=topic, entity=entity, attribute=attribute, entity_type=entity_type, post_url=post_url, cancel_url=cancel_url, attribute_name=form_attribute_name, attribute_value=form_attribute_value, attribute_type=form_attribute_type, attribute_scope=form_attribute_scope, )
def add_name(map_identifier, topic_identifier): topic_store = get_topic_store() topic_map = topic_store.get_map(map_identifier, current_user.id) if topic_map is None: current_app.logger.warning( f"Topic map not found: user identifier: [{current_user.id}], topic map identifier: [{map_identifier}]" ) abort(404) # If the map doesn't belong to the user and they don't have the right # collaboration mode on the map, then abort if not topic_map.owner and topic_map.collaboration_mode is not CollaborationMode.EDIT: abort(403) topic = topic_store.get_topic( map_identifier, topic_identifier, resolve_attributes=RetrievalMode.RESOLVE_ATTRIBUTES, ) if topic is None: current_app.logger.warning( f"Topic not found: user identifier: [{current_user.id}], topic map identifier: [{map_identifier}], topic identifier: [{topic_identifier}]" ) abort(404) map_notes_count = topic_store.get_topic_occurrences_statistics( map_identifier, "notes")["note"] error = 0 if request.method == "POST": form_topic_name = request.form["topic-name"].strip() form_topic_name_scope = request.form["topic-name-scope"].strip() # If no values have been provided set their default values if not form_topic_name_scope: form_topic_name_scope = session["current_scope"] # Validate form inputs if not form_topic_name: error = error | 1 if not topic_store.topic_exists(topic_map.identifier, form_topic_name_scope): error = error | 2 if error != 0: flash( "An error occurred when submitting the form. Please review the warnings and fix accordingly.", "warning", ) else: base_name = BaseName(form_topic_name, scope=form_topic_name_scope) topic_store.create_base_name(map_identifier, topic.identifier, base_name) flash("Name successfully added.", "success") return redirect( url_for( "topic.view_names", map_identifier=topic_map.identifier, topic_identifier=topic.identifier, )) return render_template( "topic/add_name.html", error=error, topic_map=topic_map, topic=topic, topic_name=form_topic_name, topic_name_scope=form_topic_name_scope, map_notes_count=map_notes_count, ) return render_template( "topic/add_name.html", error=error, topic_map=topic_map, topic=topic, map_notes_count=map_notes_count, )
def add_reference(map_identifier, topic_identifier, association_identifier, member_identifier): topic_store = get_topic_store() topic_map = topic_store.get_topic_map(map_identifier, current_user.id) if topic_map is None: abort(404) # If the map doesn't belong to the user and they don't have the right # collaboration mode on the map, then abort if not topic_map.owner and topic_map.collaboration_mode is not CollaborationMode.EDIT: abort(403) topic = topic_store.get_topic( map_identifier, topic_identifier, resolve_attributes=RetrievalMode.RESOLVE_ATTRIBUTES, ) if topic is None: abort(404) association = topic_store.get_association(map_identifier, association_identifier) if association is None: abort(404) member = association.get_member(member_identifier) error = 0 if request.method == "POST": form_topic_reference = request.form["topic-reference"].strip() # Validate form inputs if not form_topic_reference: error = error | 1 if not topic_store.topic_exists(topic_map.identifier, form_topic_reference): error = error | 2 if error != 0: flash( "An error occurred when submitting the form. Please review the warnings and fix accordingly.", "warning", ) else: member.add_topic_ref(form_topic_reference) topic_store.delete_association(map_identifier, association_identifier) topic_store.set_association(map_identifier, association) flash("Topic reference successfully added.", "success") return redirect( url_for( "association.view_member", map_identifier=topic_map.identifier, topic_identifier=topic.identifier, association_identifier=association_identifier, member_identifier=member_identifier, )) return render_template( "association/add_reference.html", error=error, topic_map=topic_map, topic=topic, association=association, member=member, topic_reference=form_topic_reference, ) return render_template( "association/add_reference.html", error=error, topic_map=topic_map, topic=topic, association=association, member=member, )
def create(map_identifier, topic_identifier): topic_store = get_topic_store() topic_map = topic_store.get_topic_map(map_identifier) if topic_map is None: abort(404) if current_user.id != topic_map.user_identifier: abort(403) topic = topic_store.get_topic( map_identifier, topic_identifier, resolve_attributes=RetrievalMode.RESOLVE_ATTRIBUTES, ) if topic is None: abort(404) form_topic_name = "" form_topic_identifier = "" form_topic_text = "" form_topic_instance_of = "topic" form_topic_text_scope = session["current_scope"] error = 0 if request.method == "POST": form_topic_identifier = request.form["topic-identifier"].strip() form_topic_name = request.form["topic-name"].strip() form_topic_text = request.form["topic-text"].strip() form_topic_instance_of = request.form["topic-instance-of"].strip() form_topic_text_scope = request.form["topic-text-scope"].strip() # If no values have been provided set their default values if not form_topic_instance_of: form_topic_instance_of = "topic" # Validate form inputs if not form_topic_name: error = error | 1 if topic_store.topic_exists(topic_map.identifier, form_topic_identifier): error = error | 2 if not form_topic_identifier: error = error | 4 if not topic_store.topic_exists(topic_map.identifier, form_topic_instance_of): error = error | 8 if not topic_store.topic_exists(topic_map.identifier, form_topic_text_scope): error = error | 16 if error != 0: flash( "An error occurred when submitting the form. Please review the warnings and fix accordingly.", "warning", ) else: new_topic = Topic(form_topic_identifier, form_topic_instance_of, form_topic_name) text_occurrence = Occurrence( instance_of="text", topic_identifier=new_topic.identifier, scope=form_topic_text_scope, resource_data=form_topic_text, ) timestamp = str(datetime.now()) modification_attribute = Attribute( "modification-timestamp", timestamp, new_topic.identifier, data_type=DataType.TIMESTAMP, ) query_attribute = Attribute( "knowledge-graph-query", form_topic_name.lower(), new_topic.identifier, data_type=DataType.STRING, ) # Persist objects to the topic store topic_store.set_topic(topic_map.identifier, new_topic) topic_store.set_occurrence(topic_map.identifier, text_occurrence) topic_store.set_attribute(topic_map.identifier, modification_attribute) topic_store.set_attribute(topic_map.identifier, query_attribute) flash("Topic successfully created.", "success") return redirect( url_for( "topic.view", map_identifier=topic_map.identifier, topic_identifier=new_topic.identifier, )) return render_template( "topic/create.html", error=error, topic_map=topic_map, topic=topic, topic_name=form_topic_name, topic_identifier=form_topic_identifier, topic_text=form_topic_text, topic_instance_of=form_topic_instance_of, topic_text_scope=form_topic_text_scope, )
def create(map_identifier, topic_identifier): topic_store = get_topic_store() topic_map = topic_store.get_topic_map(map_identifier, current_user.id) if topic_map is None: abort(404) # If the map doesn't belong to the user and they don't have the right # collaboration mode on the map, then abort if not topic_map.owner and topic_map.collaboration_mode is not CollaborationMode.EDIT: abort(403) topic = topic_store.get_topic( map_identifier, topic_identifier, resolve_attributes=RetrievalMode.RESOLVE_ATTRIBUTES, ) if topic is None: abort(404) error = 0 flash( "Take a look at this page to see practical examples of <a href='https://brettkromkamp.com/posts/semantically-meaningful-relationships/'>semantically meaningful relationships</a>.", "info", ) if request.method == "POST": form_association_dest_topic_ref = request.form[ "association-dest-topic-ref"].strip() form_association_dest_role_spec = request.form[ "association-dest-role-spec"].strip() form_association_src_topic_ref = topic_identifier form_association_src_role_spec = request.form[ "association-src-role-spec"].strip() form_association_instance_of = request.form[ "association-instance-of"].strip() form_association_scope = request.form["association-scope"].strip() form_association_name = request.form["association-name"].strip() form_association_identifier = request.form[ "association-identifier"].strip() # If no values have been provided set their default values if not form_association_dest_role_spec: form_association_dest_role_spec = "related" if not form_association_src_role_spec: form_association_src_role_spec = "related" if not form_association_instance_of: form_association_instance_of = "association" if not form_association_scope: form_association_scope = session["current_scope"] if not form_association_name: form_association_name = "Undefined" if not form_association_identifier: form_association_identifier = "" # Validate form inputs if not topic_store.topic_exists(topic_map.identifier, form_association_dest_topic_ref): error = error | 1 if form_association_dest_role_spec != "related" and not topic_store.topic_exists( topic_map.identifier, form_association_dest_role_spec): error = error | 2 if form_association_src_role_spec != "related" and not topic_store.topic_exists( topic_map.identifier, form_association_src_role_spec): error = error | 4 if form_association_instance_of != "association" and not topic_store.topic_exists( topic_map.identifier, form_association_instance_of): error = error | 8 if form_association_scope != UNIVERSAL_SCOPE and not topic_store.topic_exists( topic_map.identifier, form_association_scope): error = error | 16 if form_association_identifier and topic_store.topic_exists( topic_map.identifier, form_association_identifier): error = error | 32 # TODO: Flag an error to prevent the user from creating an association with the reserved # 'navigation' or 'categorization' types # If role identifier topics are missing then create them if error & 2: # Destination role spec pass if error & 4: # Source role spec pass if error != 0: flash( "An error occurred when submitting the form. Please review the warnings and fix accordingly.", "warning", ) else: association = Association( identifier=form_association_identifier, instance_of=form_association_instance_of, name=form_association_name, scope=form_association_scope, src_topic_ref=form_association_src_topic_ref, dest_topic_ref=form_association_dest_topic_ref, src_role_spec=form_association_src_role_spec, dest_role_spec=form_association_dest_role_spec, ) # Persist association object to the topic store topic_store.set_association(map_identifier, association) flash("Association successfully created.", "success") return redirect( url_for( "association.index", map_identifier=topic_map.identifier, topic_identifier=topic_identifier, )) return render_template( "association/create.html", error=error, topic_map=topic_map, topic=topic, association_instance_of=form_association_instance_of, association_src_topic_ref=form_association_src_topic_ref, association_src_role_spec=form_association_src_role_spec, association_dest_topic_ref=form_association_dest_topic_ref, association_dest_role_spec=form_association_dest_role_spec, association_scope=form_association_scope, association_name=form_association_name, association_identifier=form_association_identifier, ) return render_template( "association/create.html", error=error, topic_map=topic_map, topic=topic, )
def view(map_identifier, topic_identifier): topic_store = get_topic_store() topic_map = topic_store.get_topic_map(map_identifier) if topic_map is None: abort(404) else: if topic_map.shared: if current_user.is_authenticated: # User is logged in if (current_user.id != topic_map.user_identifier and topic_identifier == "home"): flash( "You are accessing a shared topic map of another user.", "primary", ) else: if current_user.is_authenticated: # User is logged in if current_user.id != topic_map.user_identifier: abort(403) else: # User is *not* logged in abort(403) topic = topic_store.get_topic( map_identifier, topic_identifier, resolve_attributes=RetrievalMode.RESOLVE_ATTRIBUTES, ) if topic is None: session["inexistent_topic_identifier"] = topic_identifier abort(404) else: session.pop("inexistent_topic_identifier", None) # Determine if (active) scope filtering has been specified in the URL scope_filtered = request.args.get("filter", type=int) if scope_filtered is not None: session["scope_filter"] = scope_filtered scope_filtered = session["scope_filter"] # If a context has been specified in the URL, then use that to set the context scope_identifier = request.args.get("context", type=str) if scope_identifier and topic_store.topic_exists(map_identifier, scope_identifier): session["current_scope"] = scope_identifier if scope_filtered: topic_occurrences = topic_store.get_topic_occurrences( map_identifier, topic_identifier, scope=session["current_scope"], inline_resource_data=RetrievalMode.INLINE_RESOURCE_DATA, resolve_attributes=RetrievalMode.RESOLVE_ATTRIBUTES, ) else: topic_occurrences = topic_store.get_topic_occurrences( map_identifier, topic_identifier, inline_resource_data=RetrievalMode.INLINE_RESOURCE_DATA, resolve_attributes=RetrievalMode.RESOLVE_ATTRIBUTES, ) occurrences = { "text": None, "images": [], "3d-scenes": [], "files": [], "links": [], "videos": [], "notes": [], } for occurrence in topic_occurrences: if occurrence.instance_of == "text": if occurrence.resource_data: occurrences["text"] = mistune.markdown( occurrence.resource_data.decode()) elif occurrence.instance_of == "image": occurrences["images"].append({ "title": occurrence.get_attribute_by_name("title").value, "url": occurrence.resource_ref, }) elif occurrence.instance_of == "3d-scene": occurrences["3d-scenes"].append({ "title": occurrence.get_attribute_by_name("title").value, "url": occurrence.resource_ref, }) elif occurrence.instance_of == "file": occurrences["files"].append({ "title": occurrence.get_attribute_by_name("title").value, "url": occurrence.resource_ref, }) elif occurrence.instance_of == "url": occurrences["links"].append({ "title": occurrence.get_attribute_by_name("title").value, "url": occurrence.resource_ref, }) elif occurrence.instance_of == "video": occurrences["videos"].append({ "title": occurrence.get_attribute_by_name("title").value, "url": occurrence.resource_ref, }) elif occurrence.instance_of == "note": occurrences["notes"].append({ "identifier": occurrence.identifier, "title": occurrence.get_attribute_by_name("title").value, "timestamp": maya.parse( occurrence.get_attribute_by_name( "modification-timestamp").value), "text": mistune.markdown(occurrence.resource_data.decode()), }) if scope_filtered: associations = topic_store.get_association_groups( map_identifier, topic_identifier, scope=session["current_scope"]) else: associations = topic_store.get_association_groups( map_identifier, topic_identifier) creation_date = maya.parse( topic.get_attribute_by_name("creation-timestamp").value) modification_date_attribute = topic.get_attribute_by_name( "modification-timestamp") modification_date = (maya.parse(modification_date_attribute.value) if modification_date_attribute else "Undefined") # Breadcrumbs if "breadcrumbs" not in session: session["breadcrumbs"] = [] breadcrumbs = deque(session["breadcrumbs"], BREADCRUMBS_COUNT) if topic_identifier in breadcrumbs: breadcrumbs.remove(topic_identifier) breadcrumbs.append(topic_identifier) session["breadcrumbs"] = list(breadcrumbs) knowledge_graph_query = ( topic.get_attribute_by_name("knowledge-graph-query").value if topic.get_attribute_by_name("knowledge-graph-query") else None) return render_template("topic/view.html", topic_map=topic_map, topic=topic, occurrences=occurrences, associations=associations, creation_date=creation_date, modification_date=modification_date, breadcrumbs=breadcrumbs, knowledge_graph_query=knowledge_graph_query)
def delete(map_identifier, topic_identifier, image_identifier): topic_store = get_topic_store() topic_map = topic_store.get_topic_map(map_identifier, current_user.id) if topic_map is None: abort(404) # If the map doesn't belong to the user and they don't have the right collaboration mode on the map, then abort if not topic_map.owner and topic_map.collaboration_mode is not CollaborationMode.EDIT: abort(403) topic = topic_store.get_topic( map_identifier, topic_identifier, resolve_attributes=RetrievalMode.RESOLVE_ATTRIBUTES, ) if topic is None: abort(404) image_occurrence = topic_store.get_occurrence( map_identifier, image_identifier, resolve_attributes=RetrievalMode.RESOLVE_ATTRIBUTES, ) form_image_title = image_occurrence.get_attribute_by_name("title").value form_image_resource_ref = image_occurrence.resource_ref form_image_scope = image_occurrence.scope if request.method == "POST": # Delete image occurrence from topic store topic_store.delete_occurrence(map_identifier, image_occurrence.identifier) # Delete image from file system image_file_path = os.path.join( bp.root_path, RESOURCES_DIRECTORY, str(map_identifier), topic_identifier, image_occurrence.resource_ref, ) if os.path.exists(image_file_path): os.remove(image_file_path) flash("Image successfully deleted.", "warning") return redirect( url_for( "image.index", map_identifier=topic_map.identifier, topic_identifier=topic.identifier, )) return render_template( "image/delete.html", topic_map=topic_map, topic=topic, image_identifier=image_occurrence.identifier, image_title=form_image_title, image_resource_ref=form_image_resource_ref, image_scope=form_image_scope, )
def edit(map_identifier, topic_identifier): topic_store = get_topic_store() topic_map = topic_store.get_topic_map(map_identifier) if topic_map is None: abort(404) if current_user.id != topic_map.user_identifier: abort(403) topic = topic_store.get_topic( map_identifier, topic_identifier, resolve_attributes=RetrievalMode.RESOLVE_ATTRIBUTES, ) if topic is None: abort(404) occurrences = topic_store.get_topic_occurrences( map_identifier, topic_identifier, scope=session["current_scope"], inline_resource_data=RetrievalMode.INLINE_RESOURCE_DATA, ) texts = [ occurrence for occurrence in occurrences if occurrence.instance_of == "text" ] form_topic_name = topic.first_base_name.name form_topic_text = (texts[0].resource_data.decode() if len(texts) > 0 and texts[0].resource_data else "") form_topic_instance_of = topic.instance_of form_topic_text_scope = ( texts[0].scope if len(texts) > 0 else session["current_scope"] ) # Should it be '*'? error = 0 if request.method == "POST": form_topic_name = request.form["topic-name"].strip() form_topic_text = request.form["topic-text"].strip() form_topic_instance_of = request.form["topic-instance-of"].strip() form_topic_text_scope = request.form["topic-text-scope"].strip() # If no values have been provided set their default values if not form_topic_instance_of: form_topic_instance_of = "topic" # Validate form inputs if not topic_store.topic_exists(topic_map.identifier, form_topic_instance_of): error = error | 1 if not topic_store.topic_exists(topic_map.identifier, form_topic_text_scope): error = error | 2 if error != 0: flash( "An error occurred when submitting the form. Please review the warnings and fix accordingly.", "warning", ) else: # Update topic's first base name if it has changed if topic.first_base_name.name != form_topic_name: topic_store.update_basename(map_identifier, topic.first_base_name.identifier, form_topic_name) # Update topic's 'instance of' if it has changed if topic.instance_of != form_topic_instance_of: topic_store.update_topic_instance_of(map_identifier, topic.identifier, form_topic_instance_of) # If the topic has an existing text occurrence update it, otherwise create a new text occurrence # and persist it if len(texts) > 0: topic_store.update_occurrence_data(map_identifier, texts[0].identifier, form_topic_text) else: text_occurrence = Occurrence( instance_of="text", topic_identifier=topic.identifier, scope=form_topic_text_scope, resource_data=form_topic_text, ) topic_store.set_occurrence(topic_map.identifier, text_occurrence) # Update the topic's modification (timestamp) attribute timestamp = str(datetime.now()) if topic.get_attribute_by_name("modification-timestamp"): topic_store.update_attribute_value( topic_map.identifier, topic.get_attribute_by_name( "modification-timestamp").identifier, timestamp, ) else: modification_attribute = Attribute( "modification-timestamp", timestamp, topic.identifier, data_type=DataType.TIMESTAMP, ) topic_store.set_attribute(topic_map.identifier, modification_attribute) flash("Topic successfully updated.", "success") return redirect( url_for( "topic.view", map_identifier=topic_map.identifier, topic_identifier=topic.identifier, )) return render_template( "topic/edit.html", error=error, topic_map=topic_map, topic=topic, topic_name=form_topic_name, topic_identifier=topic.identifier, topic_text=form_topic_text, topic_instance_of=form_topic_instance_of, topic_text_scope=form_topic_text_scope, )
def add(map_identifier, topic_identifier): topic_store = get_topic_store() topic_map = topic_store.get_map(map_identifier, current_user.id) if topic_map is None: current_app.logger.warning( f"Topic map not found: user identifier: [{current_user.id}], topic map identifier: [{map_identifier}]" ) abort(404) # If the map doesn't belong to the user and they don't have the right # collaboration mode on the map, then abort if not topic_map.owner and topic_map.collaboration_mode is not CollaborationMode.EDIT: abort(403) topic = topic_store.get_topic( map_identifier, topic_identifier, resolve_attributes=RetrievalMode.RESOLVE_ATTRIBUTES, ) if topic is None: current_app.logger.warning( f"Topic not found: user identifier: [{current_user.id}], topic map identifier: [{map_identifier}], topic identifier: [{topic_identifier}]" ) abort(404) form_tags = None map_notes_count = topic_store.get_topic_occurrences_statistics( map_identifier, "notes")["note"] error = 0 if request.method == "POST": form_tags = request.form["topic-tags"].strip() # Validate form inputs if not form_tags: error = error | 1 if error != 0: flash( "An error occurred when submitting the form. Please review the warnings and fix accordingly.", "warning", ) else: for form_tag in form_tags.split(","): topic_store.create_tag(map_identifier, topic.identifier, form_tag) flash("Tags successfully added.", "success") return redirect( url_for( "topic.view", map_identifier=topic_map.identifier, topic_identifier=topic.identifier, )) return render_template( "tag/add.html", error=error, topic_map=topic_map, topic=topic, topic_name=form_tags, map_notes_count=map_notes_count, )
def add_note(map_identifier, topic_identifier): topic_store = get_topic_store() topic_map = topic_store.get_topic_map(map_identifier) if topic_map is None: abort(404) if current_user.id != topic_map.user_identifier: abort(403) topic = topic_store.get_topic( map_identifier, topic_identifier, resolve_attributes=RetrievalMode.RESOLVE_ATTRIBUTES, ) if topic is None: abort(404) form_note_title = "" form_note_text = "" form_note_scope = "*" error = 0 if request.method == "POST": form_note_title = request.form["note-title"].strip() form_note_text = request.form["note-text"].strip() form_note_scope = request.form["note-scope"].strip() # If no values have been provided set their default values if not form_note_scope: form_note_scope = "*" # Universal scope # Validate form inputs if not form_note_title: error = error | 1 if not form_note_text: error = error | 2 if not topic_store.topic_exists(topic_map.identifier, form_note_scope): error = error | 4 if error != 0: flash( "An error occurred when submitting the form. Please review the warnings and fix accordingly.", "warning", ) else: note_occurrence = Occurrence( instance_of="note", topic_identifier=topic.identifier, scope=form_note_scope, resource_data=form_note_text, ) title_attribute = Attribute( "title", form_note_title, note_occurrence.identifier, data_type=DataType.STRING, ) timestamp = str(datetime.now()) modification_attribute = Attribute( "modification-timestamp", timestamp, note_occurrence.identifier, data_type=DataType.TIMESTAMP, ) # Persist objects to the topic store topic_store.set_occurrence(topic_map.identifier, note_occurrence) topic_store.set_attribute(topic_map.identifier, title_attribute) topic_store.set_attribute(topic_map.identifier, modification_attribute) flash("Note successfully added.", "success") return redirect( url_for( "topic.view", map_identifier=topic_map.identifier, topic_identifier=topic.identifier, )) return render_template( "topic/add_note.html", error=error, topic_map=topic_map, topic=topic, note_title=form_note_title, note_text=form_note_text, note_scope=form_note_scope, )
def create(): topic_store = get_topic_store() form_map_name = "" form_map_description = "" form_map_shared = False error = 0 if request.method == "POST": form_map_name = request.form["map-name"].strip() form_map_description = request.form["map-description"].strip() form_map_published = True if request.form.get("map-published") == "1" else False form_upload_file = request.files["map-image-file"] if "map-image-file" in request.files else None # Validate form inputs if not form_map_name: error = error | 1 if not form_upload_file: error = error | 2 else: if form_upload_file.filename == "": error = error | 4 elif not allowed_file(form_upload_file.filename): error = error | 8 if error != 0: flash( "An error occurred when submitting the form. Please review the warnings and fix accordingly.", "warning", ) else: image_file_name = f"{str(uuid.uuid4())}.{get_file_extension(form_upload_file.filename)}" # Create and initialise the topic map map_identifier = topic_store.set_topic_map( current_user.id, form_map_name, form_map_description, image_file_name, initialised=False, published=form_map_published, promoted=False, ) if map_identifier: topic_store.initialise_topic_map(map_identifier, current_user.id) # Create the directory for this topic map topic_map_directory = os.path.join(bp.root_path, RESOURCES_DIRECTORY, str(map_identifier)) if not os.path.isdir(topic_map_directory): os.makedirs(topic_map_directory) # Upload the image for the topic map to the map's directory file_path = os.path.join(topic_map_directory, image_file_name) form_upload_file.save(file_path) flash("Map successfully created.", "success") else: flash( "An error occurred while creating the topic map. Get in touch with Support if the problem persists.", "danger", ) return redirect(url_for("map.index")) return render_template( "map/create.html", error=error, map_name=form_map_name, map_description=form_map_description, map_shared=form_map_shared, )
def edit_name(map_identifier, topic_identifier, name_identifier): topic_store = get_topic_store() topic_map = topic_store.get_topic_map(map_identifier) if topic_map is None: abort(404) if current_user.id != topic_map.user_identifier: abort(403) if "admin" not in current_user.roles: abort(403) topic = topic_store.get_topic( map_identifier, topic_identifier, resolve_attributes=RetrievalMode.RESOLVE_ATTRIBUTES, ) if topic is None: abort(404) form_topic_name = topic.get_base_name(name_identifier).name form_topic_name_scope = topic.get_base_name(name_identifier).scope error = 0 if request.method == "POST": form_topic_name = request.form["topic-name"].strip() form_topic_name_scope = request.form["topic-name-scope"].strip() # Validate form inputs if not form_topic_name: error = error | 1 if not topic_store.topic_exists(topic_map.identifier, form_topic_name_scope): error = error | 2 if error != 0: flash( "An error occurred when submitting the form. Please review the warnings and fix accordingly.", "warning", ) else: # Update name if required if form_topic_name != topic.get_base_name( name_identifier ).name or form_topic_name_scope != topic.get_base_name( name_identifier).scope: topic_store.update_basename(map_identifier, name_identifier, form_topic_name, scope=form_topic_name_scope) flash("Name successfully updated.", "success") return redirect( url_for( "topic.view_names", map_identifier=topic_map.identifier, topic_identifier=topic.identifier, )) return render_template( "topic/edit_name.html", error=error, topic_map=topic_map, topic=topic, topic_name=form_topic_name, topic_name_scope=form_topic_name_scope, name_identifier=name_identifier, )