From 25b502591e99caeee6ecd994ba51a29f5bfc06d5 Mon Sep 17 00:00:00 2001 From: freshavocado7 Date: Wed, 18 Sep 2024 10:25:31 +0200 Subject: [PATCH] refactor: Implement more review comments --- capella_model_explorer/backend/explorer.py | 43 ++++-- capella_model_explorer/backend/model_diff.py | 63 ++++---- frontend/src/App.jsx | 5 +- frontend/src/components/DiffExplorer.jsx | 3 - frontend/src/components/DiffView.jsx | 117 +++++++------- frontend/src/components/ModelDiff.jsx | 21 ++- frontend/src/views/HomeView.jsx | 17 ++- frontend/src/views/ModelComparisonView.jsx | 14 +- pyproject.toml | 2 +- templates/common_macros.html.j2 | 88 +++++++---- templates/object_comparison.html.j2 | 153 +++---------------- 11 files changed, 244 insertions(+), 282 deletions(-) diff --git a/capella_model_explorer/backend/explorer.py b/capella_model_explorer/backend/explorer.py index adb4bdd..a7b2b11 100644 --- a/capella_model_explorer/backend/explorer.py +++ b/capella_model_explorer/backend/explorer.py @@ -15,7 +15,7 @@ import markupsafe import prometheus_client import yaml -from fastapi import APIRouter, FastAPI, Request +from fastapi import APIRouter, FastAPI, HTTPException, Request from fastapi.middleware.cors import CORSMiddleware from fastapi.responses import HTMLResponse from fastapi.staticfiles import StaticFiles @@ -45,11 +45,8 @@ class CommitRange(BaseModel): prev: str -class DiffData(BaseModel): - display_name: str - change: str - attributes: dict[str, t.Any] - children: dict[str, t.Any] +class ObjectDiffID(BaseModel): + uuid: str @dataclasses.dataclass @@ -305,17 +302,18 @@ async def post_compare(commit_range: CommitRange): self.diff = model_diff.get_diff_data( self.model, commit_range.head, commit_range.prev ) + self.diff["lookup"] = create_diff_lookup(self.diff["objects"]) return {"success": True} except Exception as e: return {"success": False, "error": str(e)} @self.app.post("/api/object-diff") - async def post_object_diff(diff_data: DiffData): - try: - self.object_diff = diff_data.model_dump() - return {"success": True} - except Exception as e: - return {"success": False, "error": str(e)} + async def post_object_diff(object_id: ObjectDiffID): + if object_id.uuid not in self.diff["lookup"]: + raise HTTPException(status_code=404, detail="Object not found") + + self.object_diff = self.diff["lookup"][object_id.uuid] + return {"success": True} @self.app.get("/api/commits") async def get_commits(): @@ -360,3 +358,24 @@ def index_templates( template, templates, templates_grouped, filename=idx ) return templates_grouped, templates + + +def create_diff_lookup(data, lookup=None): + if lookup is None: + lookup = {} + try: + if isinstance(data, dict): + for _, obj in data.items(): + if "uuid" in obj: + lookup[obj["uuid"]] = { + "uuid": obj["uuid"], + "display_name": obj["display_name"], + "change": obj["change"], + "attributes": obj["attributes"], + } + if "children" in obj: + if obj["children"]: + create_diff_lookup(obj["children"], lookup) + except Exception: + pass + return lookup diff --git a/capella_model_explorer/backend/model_diff.py b/capella_model_explorer/backend/model_diff.py index e933363..a569578 100644 --- a/capella_model_explorer/backend/model_diff.py +++ b/capella_model_explorer/backend/model_diff.py @@ -11,7 +11,7 @@ import capellambse import capellambse.model as m import capellambse.model.common as c -import diff_match_patch # type: ignore +import diff_match_patch import typing_extensions as te from capellambse.filehandler import git, local @@ -30,6 +30,8 @@ class RevisionInfo(te.TypedDict, total=False): """The time and date of the revision.""" description: str """The description of the revision, i.e. the commit message.""" + subject: str + """The subject of the commit.""" tag: str | None """The tag of the commit.""" @@ -97,40 +99,28 @@ class ChangeSummaryDocument(te.TypedDict): objects: ObjectChanges -def init_model(model: capellambse.MelodyModel): +def init_model(model: capellambse.MelodyModel) -> t.Optional[str]: + """Initialize the model and return the path if it's a git repository.""" file_handler = model.resources["\x00"] - path = str(file_handler.path) - model_data: dict = { - "metadata": {"model": {"path": path, "entrypoint": None}}, - "diagrams": { - "created": {}, - "modified": {}, - "deleted": {}, - }, - "objects": { - "created": {}, - "modified": {}, - "deleted": {}, - }, - } + path = file_handler.path if isinstance(file_handler, git.GitFileHandler): - path = str(file_handler.cache_dir) + path = file_handler.cache_dir elif ( isinstance(file_handler, local.LocalFileHandler) and file_handler.rootdir.joinpath(".git").is_dir() ): pass else: - return {"error": "Not a git repo"}, model_data - return path, model_data + return None + return str(path) def populate_commits(model: capellambse.MelodyModel): - result, _ = init_model(model) - if "error" in result: - return result - commits = get_commit_hashes(result) + path = init_model(model) + if not path: + return path + commits = get_commit_hashes(path) return commits @@ -145,8 +135,10 @@ def _serialize_obj(obj: t.Any) -> t.Any: def get_diff_data(model: capellambse.MelodyModel, head: str, prev: str): - path, _ = init_model(model) - path = pathlib.Path(path).resolve() + path = init_model(model) + if not path: + return None + path = str(pathlib.Path(path).resolve()) old_model = capellambse.MelodyModel(path=f"git+{path}", revision=prev) metadata: Metadata = { @@ -177,25 +169,30 @@ def _get_revision_info( .strip() .split("\x00") ) + subject = description.splitlines()[0] try: tag = subprocess.check_output( - ["git", "describe", "--tags", revision], + ["git", "tag", "--points-at", revision], cwd=repo_path, encoding="utf-8", + stderr=subprocess.DEVNULL, ).strip() except subprocess.CalledProcessError: tag = None + return { "hash": revision, "revision": revision, "author": author, "date": datetime.datetime.fromisoformat(date_str), "description": description.rstrip(), - "tag": tag, + "subject": subject, + "tag": tag if tag else None, } -def get_commit_hashes(path: str): +def get_commit_hashes(path: str) -> list[RevisionInfo]: + """Return the commit hashes of the given model.""" commit_hashes = subprocess.check_output( ["git", "log", "-n", NUM_COMMITS, "--format=%H"], cwd=path, @@ -386,7 +383,7 @@ def _handle_direct_accessors( } -def _traverse_and_diff(data): +def _traverse_and_diff(data) -> dict[str, t.Any]: """Traverse the data and perform diff on text fields. This function recursively traverses the data and performs an HTML @@ -433,14 +430,14 @@ def _traverse_and_diff(data): return data -def _diff_text(previous, current): +def _diff_text(previous, current) -> str: dmp = diff_match_patch.diff_match_patch() diff = dmp.diff_main("\n".join(previous), "\n".join(current)) dmp.diff_cleanupSemantic(diff) return dmp.diff_prettyHtml(diff) -def _diff_objects(previous, current): +def _diff_objects(previous, current) -> str: return ( f"{previous['display_name']} → " if previous else "" ) + f"{current['display_name']}" @@ -466,7 +463,9 @@ def _diff_lists(previous, current): return out -def _diff_description(previous, current): +def _diff_description( + previous, current +) -> t.Tuple[str, str] | t.Tuple[None, None]: if previous == current == None: return None, None dmp = diff_match_patch.diff_match_patch() diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index b64e6e3..456143c 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -21,7 +21,10 @@ function App() { path="/:templateName/:objectID" element={} /> - } /> + } + /> ); diff --git a/frontend/src/components/DiffExplorer.jsx b/frontend/src/components/DiffExplorer.jsx index 3adcfaa..33f792c 100644 --- a/frontend/src/components/DiffExplorer.jsx +++ b/frontend/src/components/DiffExplorer.jsx @@ -17,7 +17,6 @@ import { export const DiffExplorer = ({ node, setObjectID, - setDiffData, searchTerm, filterStatus }) => { @@ -70,7 +69,6 @@ export const DiffExplorer = ({ const handleLeafClick = (node) => { setObjectID(node.uuid); - setDiffData(node); }; const flattenNodes = (node) => { @@ -142,7 +140,6 @@ export const DiffExplorer = ({ key={childId} node={node.children[childId]} setObjectID={setObjectID} - setDiffData={setDiffData} searchTerm={searchTerm} filterStatus={filterStatus} /> diff --git a/frontend/src/components/DiffView.jsx b/frontend/src/components/DiffView.jsx index 089eede..f75580e 100644 --- a/frontend/src/components/DiffView.jsx +++ b/frontend/src/components/DiffView.jsx @@ -5,67 +5,80 @@ import { SVGDisplay } from './SVGDisplay'; import { API_BASE_URL } from '../APIConfig'; import { Spinner } from './Spinner'; -export const DiffView = ({ objectID, endpoint, diffData }) => { +export const DiffView = ({ objectID, endpoint }) => { const [details, setDetails] = useState([]); const [isLoading, setIsLoading] = useState(false); useEffect(() => { - const fetchTemplate = () => { + const fetchTemplate = async () => { setIsLoading(true); - fetch(`${API_BASE_URL}/object-diff`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify(diffData) - }) - .then((response) => response.json()) - .then(() => { - let url; - if (diffData.change) { - url = `${endpoint}object_comparison/${objectID}`; + + try { + const postResponse = await fetch(`${API_BASE_URL}/object-diff`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ uuid: objectID }) + }); + + if (!postResponse.ok) { + throw new Error( + `Failed to post diff data: ${postResponse.statusText}` + ); + } + + let url; + if (objectID) { + url = `${endpoint}object_comparison/${objectID}`; + } else { + throw new Error('Object ID is missing or invalid'); + } + + const getResponse = await fetch(url, { + method: 'GET', + headers: { + 'Content-Type': 'text/html' } - fetch(url, { - method: 'GET', - headers: { - 'Content-Type': 'text/html' - } - }) - .then((response) => response.text()) - .then((data) => { - const parser = new DOMParser(); - const doc = parser.parseFromString(data, 'text/html'); - const contentItems = []; + }); + + if (!getResponse.ok) { + throw new Error( + `Failed to fetch object comparison: ${getResponse.statusText}` + ); + } - doc.body.childNodes.forEach((node) => { - if (node.nodeType === Node.ELEMENT_NODE) { - if (node.tagName === 'svg') { - contentItems.push({ - type: 'SVGDisplay', - content: node.outerHTML - }); - } else { - contentItems.push({ - type: 'HTML', - content: node.outerHTML - }); - } - } + const data = await getResponse.text(); + + const parser = new DOMParser(); + const doc = parser.parseFromString(data, 'text/html'); + const contentItems = []; + + doc.body.childNodes.forEach((node) => { + if (node.nodeType === Node.ELEMENT_NODE) { + if (node.tagName === 'svg') { + contentItems.push({ + type: 'SVGDisplay', + content: node.outerHTML + }); + } else { + contentItems.push({ + type: 'HTML', + content: node.outerHTML }); - setDetails(contentItems); - setIsLoading(false); - }) - .catch((error) => { - setDetails([ - { type: 'Error', content: `Error fetching data: ${error}` } - ]); - setIsLoading(false); - }); - }) - .catch((error) => { - console.error('Error posting diff data:', error); - setIsLoading(false); + } + } }); + + setDetails(contentItems); + } catch (error) { + console.error('Error fetching template:', error); + setDetails([ + { type: 'Error', content: `Error fetching data: ${error.message}` } + ]); + } finally { + setIsLoading(false); + } }; if (objectID) { diff --git a/frontend/src/components/ModelDiff.jsx b/frontend/src/components/ModelDiff.jsx index 9ed73a1..f88e3c0 100644 --- a/frontend/src/components/ModelDiff.jsx +++ b/frontend/src/components/ModelDiff.jsx @@ -1,7 +1,7 @@ // Copyright DB InfraGO AG and contributors // SPDX-License-Identifier: Apache-2.0 -import { API_BASE_URL } from '../APIConfig'; +import { API_BASE_URL, ROUTE_PREFIX } from '../APIConfig'; import React, { useState } from 'react'; import { Spinner } from './Spinner'; @@ -76,13 +76,14 @@ export const ModelDiff = ({ onRefetch, hasDiffed }) => { ); } const data = await response.json(); - if (data.error) { - throw new Error(data.error); + if (data === null) { + alert('No commits found.'); + throw new Error('No commits found.'); } setCommitDetails(data); const options = data.map((commit) => ({ value: JSON.stringify(commit), - label: `${commit.tag} - ${commit.hash.substring(0, 7)} - Created on ${commit.date.substring(0, 10)}` + label: `${commit.hash.substring(0, 7)} ${commit.tag ? `(${commit.tag})` : ''} - ${commit.subject} - Created on ${commit.date.substring(0, 10)}` })); setSelectionOptions(options); @@ -122,7 +123,7 @@ export const ModelDiff = ({ onRefetch, hasDiffed }) => {
{error ? (
-

+

Cannot generate model diff: {error}

@@ -156,7 +157,10 @@ export const ModelDiff = ({ onRefetch, hasDiffed }) => { Select Commit: {selectionOptions.slice(1).map((option) => ( - ))} @@ -202,7 +206,10 @@ export const ModelDiff = ({ onRefetch, hasDiffed }) => { diff --git a/frontend/src/views/HomeView.jsx b/frontend/src/views/HomeView.jsx index b04c674..8a1aa13 100644 --- a/frontend/src/views/HomeView.jsx +++ b/frontend/src/views/HomeView.jsx @@ -56,8 +56,14 @@ export const HomeView = () => { try { const response = await fetch(API_BASE_URL + '/commits'); const data = await response.json(); - setHeadDate(data[0].date.substring(0, 10)); - setHeadTag(data[0].tag); + if (data && data.length > 0) { + setHeadDate(data[0].date.substring(0, 10)); + setHeadTag(data[0].tag); + } else { + console.log('No commits found'); + setHeadDate(''); + setHeadTag(''); + } } catch (err) { console.log('Failed to fetch head date: ' + err.message); setHeadDate(''); @@ -65,7 +71,7 @@ export const HomeView = () => { } }; fetchHeadDate(); - }, {}); + }, []); if (error) { return ( @@ -94,11 +100,6 @@ export const HomeView = () => { {comparedVersionInfo && (

Compared to Version:

- {/*

- Revision:{' '} - {comparedVersionInfo.metadata.old_revision.revision} -

*/} - {/* Revision shall be branch in feature versions. */} {comparedVersionInfo.metadata.old_revision.tag && (

Tag: {comparedVersionInfo.metadata.old_revision.tag}

)} diff --git a/frontend/src/views/ModelComparisonView.jsx b/frontend/src/views/ModelComparisonView.jsx index faa06e7..44515ca 100644 --- a/frontend/src/views/ModelComparisonView.jsx +++ b/frontend/src/views/ModelComparisonView.jsx @@ -9,11 +9,9 @@ import { DiffView } from '../components/DiffView'; import { API_BASE_URL } from '../APIConfig'; import { useMediaQuery } from 'react-responsive'; -export const ModelComparisonView = () => { +export const ModelComparisonView = ({ endpoint }) => { const [modelDiff, setModelDiff] = useState(null); const [objectID, setObjectID] = useState(null); - const endpoint = `${API_BASE_URL}/views/`; - const [diffData, setDiffData] = useState(null); const [searchTerm, setSearchTerm] = useState(''); const [filterStatus, setFilterStatus] = useState('all'); const isSmallScreen = useMediaQuery({ query: '(max-width: 1080px)' }); @@ -52,7 +50,6 @@ export const ModelComparisonView = () => { }, []); useEffect(() => {}, [objectID]); - useEffect(() => {}, [diffData]); useEffect(() => { setIsSidebarVisible(!isSmallScreen); }, [isSmallScreen]); @@ -120,7 +117,6 @@ export const ModelComparisonView = () => { @@ -132,13 +128,7 @@ export const ModelComparisonView = () => { isSmallScreen ? (isSidebarVisible ? 'pl-96' : '') : 'pl-96' }`}>
- {objectID && ( - - )} + {objectID && }
diff --git a/pyproject.toml b/pyproject.toml index 992bc17..a823cf4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -86,7 +86,7 @@ allow_untyped_defs = true [[tool.mypy.overrides]] # Untyped third party libraries module = [ - # ... + "diff_match_patch", ] ignore_missing_imports = true diff --git a/templates/common_macros.html.j2 b/templates/common_macros.html.j2 index 6e07321..92a8166 100644 --- a/templates/common_macros.html.j2 +++ b/templates/common_macros.html.j2 @@ -46,9 +46,19 @@ {% endif %} {% endmacro %} -{% macro show_other_attributes(object, excluded=[], hide_rules_check=False, hide_unset_attrs=False) %} +{% macro show_other_attributes(object, object_diff={}, display_modified_changes=None, excluded=[], hide_rules_check=False, hide_unset_attrs=False) %} {% set empty_attrs = [] %} + {% if object_diff %} + {% set attr_diff = object_diff["attributes"] %} + {% set attr_change = object_diff["change"] %} + {% set attr_lst = [] %} + {% if attr_diff is mapping %} + {%for key, val in attr_diff.items() %} + {{attr_lst.append(key)}} + {% endfor %} + {% endif %} + {%endif%} @@ -58,39 +68,65 @@ {%- set value = object[attr] -%} {% if value %} - - + {% if attr in attr_diff and attr_change == "modified" %} + + + {{ attr_lst.remove(attr) }} + {% else %} + + + {% endif %} + {% else %} {% set _none = empty_attrs.append(attr) %} {% endif %} {% endif %} {% endfor -%} + {% if attr_lst and attr_change == "modified" %} + {% for attr in attr_lst %} + + + {% endfor %} + {% endif %} +
PropertyValue
- {{ attr }} - - {%- if value.as_svg -%} - {{ value.as_svg|safe }} - {%- elif value is iterable and value is not string -%} -
    - {% for item in value %} -
  • {{ linked_name_with_icon(item) | safe }}
  • - {% endfor %} -
- {%- elif value._short_html_ -%} -

{{ linked_name_with_icon(value) | safe }}

- {%- else -%} -

{{ value }}

- {%- endif -%} -
+ {{ attr }} + + {%- if value.as_svg -%} + {{ value.as_svg|safe }} + {%- else -%} + {{ display_modified_changes(attr_diff[attr]) | safe }} + {%- endif -%} + + {{ attr }} + + {%- if value.as_svg -%} + {{ value.as_svg | safe }} + {%- elif value is iterable and value is not string -%} +
    + {% for item in value %} +
  • {{ linked_name_with_icon(item) | safe }}
  • + {% endfor %} +
+ {%- elif value._short_html_ -%} +

{{ linked_name_with_icon(value) | safe }}

+ {%- else -%} +

{{ value }}

+ {%- endif -%} +
+ {{ attr }} + +

{{ display_modified_changes(attr_diff[attr]) | safe }}

+
{% if empty_attrs %} - {% if not hide_unset_attrs %} -

Unset attributes

-

The object has the following attributes in unset state (empty values): {{ ", ".join(empty_attrs) }}

- {% endif %} - {% if not hide_rules_check %} - {{ show_compliance_to_modeling_rules(object) | safe }} - {% endif %} + {% if not hide_unset_attrs %} +

Unset attributes

+

The object has the following attributes in unset state (empty values): {{ ", ".join(empty_attrs) }}

+ {% endif %} + {% if not hide_rules_check %} + {{ show_compliance_to_modeling_rules(object) | safe }} + {% endif %} {% endif %} {% endmacro %} diff --git a/templates/object_comparison.html.j2 b/templates/object_comparison.html.j2 index 04e484c..0727aea 100644 --- a/templates/object_comparison.html.j2 +++ b/templates/object_comparison.html.j2 @@ -2,52 +2,33 @@ Copyright DB InfraGO AG and contributors SPDX-License-Identifier: Apache-2.0 #} -{% from 'common_macros.html.j2' import linked_name_with_icon, draw_icon, show_compliance_to_modeling_rules %} +{% from 'common_macros.html.j2' import linked_name_with_icon, show_other_attributes, draw_icon, show_compliance_to_modeling_rules %} - -{% macro display_modified_changes(attr_diff, attr) %} - {% set diff = attr_diff[attr]["diff"] %} - {% if "diff" in attr_diff[attr] and diff %} - {% if diff is mapping %} - {% set seen = [] %} -
    - {% for i in attr_diff[attr]["current"] %} - {% if i["uuid"] in diff %} - {% set object = model.by_uuid(i["uuid"]) %} - {% set seen = seen.append(i["uuid"]) %} -
  • {{ linked_name_with_color_and_icon(object, diff[i["uuid"]]) | safe }}
  • - {% endif %} - {% endfor %} - {% for uuid, name in diff.items() %} - {% if uuid not in seen %} -
  • {{ name | safe}}
  • - {% endif %} - {% endfor %} -
- {% else %} -

{{diff | safe}}

- {% endif %} +{% macro display_modified_changes(attr_diff) %} + {% set diff = attr_diff["diff"] %} + {% if diff is mapping %} + {% set seen = [] %} +
    + {% for i in attr_diff["current"] %} + {% if i["uuid"] in diff %} + {% set object = model.by_uuid(i["uuid"]) %} + {% set seen = seen.append(i["uuid"]) %} +
  • {{ linked_name_with_color_and_icon(object, diff[i["uuid"]]) | safe }}
  • + {% endif %} + {% endfor %} + {% for uuid, name in diff.items() %} + {% if uuid not in seen %} +
  • {{ name | safe}}
  • + {% endif %} + {% endfor %} +
+ {% elif diff %} +

{{diff | safe}}

{% else %}
    -
  • Previous: {{ attr_diff[attr]["previous"] | safe }}
  • -
  • Current: {{ attr_diff[attr]["current"] | safe }}
  • +
  • Previous: {{ attr_diff["previous"] | safe }}
  • +
  • Current: {{ attr_diff["current"] | safe }}
{% endif %} {% endmacro %} @@ -55,7 +36,7 @@ {% macro linked_name_with_color_and_icon(obj, name) %} {% if obj %} - {% if obj.__class__.__name__ not in [ "Part"]%} + {% if obj.__class__.__name__ != "Part"%} {{ draw_icon(obj, 15) | safe }} {% endif %} {{ name | safe}} @@ -65,90 +46,6 @@ {% endif %} {% endmacro %} -{% macro show_other_attributes(object, object_diff, excluded=[], hide_rules_check=False, hide_unset_attrs=False) %} - {% set empty_attrs = [] %} - - {% if object_diff %} - {% set attr_diff = object_diff["attributes"] %} - {% set attr_change = object_diff["change"] %} - {% set attr_lst = [] %} - {% if attr_diff is mapping %} - {%for key, val in attr_diff.items() %} - {{attr_lst.append(key)}} - {% endfor %} - {% endif %} - {%endif%} - - - - {%- for attr in object.__dir__()|sort if not attr.startswith("_") and attr not in ["from_model", "pvmt"]+excluded -%} - {% if attr in ["validation", "validate"] %} - {% else %} - {%- set value = object[attr] -%} - {% if value %} - - {% if attr in attr_diff and attr_change == "modified" %} - - - {{ attr_lst.remove(attr) }} - {% else %} - - - {% endif %} - - - {% else %} - {% set _none = empty_attrs.append(attr) %} - {% endif %} - {% endif %} - {% endfor -%} - {% if attr_lst and attr_change == "modified" %} - {% for attr in attr_lst %} - - - {% endfor %} - {% endif %} - -
PropertyValue
- {{ attr }} - - {%- if value.as_svg -%} - {{ value.as_svg|safe }} - {%- else -%} - {{ display_modified_changes(attr_diff, attr) | safe }} - {%- endif -%} - - {{ attr }} - - {%- if value.as_svg -%} - {{ value.as_svg | safe }} - {%- elif value is iterable and value is not string -%} -
    - {% for item in value %} -
  • {{ linked_name_with_icon(item) | safe }}
  • - {% endfor %} -
- {%- elif value._short_html_ -%} -

{{ linked_name_with_icon(value) | safe }}

- {%- else -%} -

{{ value }}

- {%- endif -%} -
- {{ attr }} - -

{{ display_modified_changes(attr_diff, attr) | safe }}

-
- {% if empty_attrs %} - {% if not hide_unset_attrs %} -

Unset attributes

-

The object has the following attributes in unset state (empty values): {{ ", ".join(empty_attrs) }}

- {% endif %} - {% if not hide_rules_check %} - {{ show_compliance_to_modeling_rules(object) | safe }} - {% endif %} - {% endif %} -{% endmacro %} - {% if object %} {% if object.name %} @@ -156,7 +53,7 @@ {% else %}

Unnamed ({{ object.__class__.__name__ }})

{% endif %} - {{show_other_attributes(object, object_diff) | safe}} + {{show_other_attributes(object, object_diff, display_modified_changes=display_modified_changes) | safe}} {% else %}

Object not found

{% endif %}