From c022551427bc065562d5b59d19bc461edcc76c44 Mon Sep 17 00:00:00 2001 From: Jon Date: Fri, 20 Sep 2024 15:26:38 +0930 Subject: [PATCH] feat(core): Add to markdown rendering model references ref: #296 #308 --- app/core/lib/markdown.py | 3 +- .../lib/markdown_plugins/model_reference.py | 142 ++++++++++++++++++ .../centurion_erp/development/models.md | 1 + .../centurion_erp/user/core/markdown.md | 19 +++ 4 files changed, 164 insertions(+), 1 deletion(-) create mode 100644 app/core/lib/markdown_plugins/model_reference.py diff --git a/app/core/lib/markdown.py b/app/core/lib/markdown.py index 1118bc97..d7439ac4 100644 --- a/app/core/lib/markdown.py +++ b/app/core/lib/markdown.py @@ -10,7 +10,7 @@ from pygments.lexers import get_lexer_by_name from django.template.loader import render_to_string -from .markdown_plugins import ticket_number +from .markdown_plugins import ticket_number, model_reference @@ -67,6 +67,7 @@ class Markdown: .use(footnote.footnote_plugin) .use(tasklists.tasklists_plugin) .use(ticket_number.plugin, enabled=True) + .use(model_reference.plugin, enabled=True) ) return md.render(markdown_text) diff --git a/app/core/lib/markdown_plugins/model_reference.py b/app/core/lib/markdown_plugins/model_reference.py new file mode 100644 index 00000000..18321454 --- /dev/null +++ b/app/core/lib/markdown_plugins/model_reference.py @@ -0,0 +1,142 @@ + +import re + +from django.template.loader import render_to_string +from django.urls import reverse + +from markdown_it import MarkdownIt +from markdown_it.rules_core import StateCore +from markdown_it.token import Token + +# Regex string to match a whitespace character, as specified in +# https://github.github.com/gfm/#whitespace-character +# (spec version 0.29-gfm (2019-04-06)) +_GFM_WHITESPACE_RE = r"[ \t\n\v\f\r]" + + +def plugin( + md: MarkdownIt, + enabled: bool = False, +) -> None: + """markdown_it plugin to render model references + + Placing `$-` within markdown will be rendered as a pretty link to the model. + + Args: + md (MarkdownIt): markdown object + enabled (bool, optional): Enable the parsing of model references. Defaults to False. + + Returns: + None: nada + """ + + def main(state: StateCore) -> None: + + tokens = state.tokens + for i in range(0, len(tokens) - 1): + if is_tag_item(tokens, i): + tag_render(tokens[i]) + + + def is_inline(token: Token) -> bool: + return token.type == "inline" + + + def is_tag_item(tokens: list[Token], index: int) -> bool: + + return ( + is_inline(tokens[index]) + and contains_tag_item(tokens[index]) + ) + + + def tag_html(match): + + id = match.group('id') + item_type = match.group('type') + + try: + + if item_type == 'cluster': + + from itim.models.clusters import Cluster + + model = Cluster + + url = reverse('ITIM:_cluster_view', kwargs={'pk': int(id)}) + + elif item_type == 'config_group': + + from config_management.models.groups import ConfigGroups + + model = ConfigGroups + + url = reverse('Config Management:_group_view', kwargs={'pk': int(id)}) + + elif item_type == 'device': + + from itam.models.device import Device + + model = Device + + url = reverse('ITAM:_device_view', kwargs={'pk': int(id)}) + + elif item_type == 'operating_system': + + from itam.models.operating_system import OperatingSystem + + model = OperatingSystem + + url = reverse('ITAM:_operating_system_view', kwargs={'pk': int(id)}) + + elif item_type == 'service': + + from itim.models.services import Service + + model = Service + + url = reverse('ITIM:_service_view', kwargs={'pk': int(id)}) + + elif item_type == 'software': + + from itam.models.software import Software + + model = Software + + url = reverse('ITAM:_software_view', kwargs={'pk': int(id)}) + + + if url: + + item = model.objects.get( + pk = int(id) + ) + + return f'{ item.name }, { item_type }' + + except Exception as e: + + return str(f'${item_type}-{id}') + + + def tag_render(token: Token) -> None: + assert token.children is not None + + checkbox = Token("html_inline", "", 0) + + checkbox.content = tag_replace(token.content) + + token.children[0] = checkbox + + + def tag_replace(text): + + return re.sub('\$(?P[a-z_]+)-(?P\d+)', tag_html, text) + + def contains_tag_item(token: Token) -> bool: + + return re.match(rf"(.+)?\$[a-z_]+-\d+{_GFM_WHITESPACE_RE}?(.+)?", token.content) is not None + + if enabled: + + md.core.ruler.after("inline", "links", fn=main) diff --git a/docs/projects/centurion_erp/development/models.md b/docs/projects/centurion_erp/development/models.md index 85f0a915..ab101d86 100644 --- a/docs/projects/centurion_erp/development/models.md +++ b/docs/projects/centurion_erp/development/models.md @@ -51,6 +51,7 @@ This section details the additional items that may need to be done when adding a - If the model is a primary model, add to model reference rendering in `app/core/lib/markdown_plugins/model_reference.py` function `tag_html` + ## History Currently the adding of history to a model is a manual process. edit the file located at `core.views.history` and within `View.get_object` add the model to the `switch` statement. diff --git a/docs/projects/centurion_erp/user/core/markdown.md b/docs/projects/centurion_erp/user/core/markdown.md index c03bf313..cb6e48b1 100644 --- a/docs/projects/centurion_erp/user/core/markdown.md +++ b/docs/projects/centurion_erp/user/core/markdown.md @@ -62,3 +62,22 @@ Available admonition types are: ## Ticket References Declare a ticket reference in format `#`, and it will be rendered as a link to the ticket. i.e. `#2` + + +## Model + +A Model link is a reference to an item within the database. Supported model link items are: + +- cluster + +- config_group + +- device + +- service + +- software + +- operating system + +To declare a model link use syntax `$-`. i.e. for device 1, it would be `$device-1`