From 9ecaf2c932a0c80bc156d0948d37f3821492c9d1 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Tue, 26 May 2026 13:52:59 +0000 Subject: [PATCH] Render note to HTML - Automatically replace images --- src/backend/InvenTree/common/models.py | 6 --- .../InvenTree/report/templatetags/report.py | 41 +++++++++++++++++-- 2 files changed, 38 insertions(+), 9 deletions(-) diff --git a/src/backend/InvenTree/common/models.py b/src/backend/InvenTree/common/models.py index bb9cf5a8dc..92fd58e8b7 100644 --- a/src/backend/InvenTree/common/models.py +++ b/src/backend/InvenTree/common/models.py @@ -3128,12 +3128,6 @@ class Note( if image.image and image.image.url not in self.content: image.delete() - def render_html(self) -> str: - """Return the content of the note rendered as HTML.""" - from django.utils.safestring import mark_safe - - return mark_safe(self.content) - model_type = models.ForeignKey(ContentType, on_delete=models.CASCADE) model_id = models.PositiveIntegerField() diff --git a/src/backend/InvenTree/report/templatetags/report.py b/src/backend/InvenTree/report/templatetags/report.py index f62abeeb82..9667967b4e 100644 --- a/src/backend/InvenTree/report/templatetags/report.py +++ b/src/backend/InvenTree/report/templatetags/report.py @@ -10,6 +10,7 @@ from typing import Any, Optional from django import template from django.apps.registry import apps +from django.conf import settings as django_settings from django.contrib.staticfiles.storage import staticfiles_storage from django.core.exceptions import SuspiciousFileOperation, ValidationError from django.core.files.storage import default_storage @@ -18,6 +19,7 @@ from django.db.models.query import QuerySet from django.utils.safestring import SafeString, mark_safe from django.utils.translation import gettext_lazy as _ +import lxml.html from djmoney.contrib.exchange.exceptions import MissingRate from djmoney.contrib.exchange.models import convert_money from djmoney.money import Money @@ -514,10 +516,43 @@ def note(instance: Model, title: Optional[str] = None) -> str: Note: If the 'title' argument is not provided, the first Note object associated with the instance will be returned (if any). """ - if note := note_instance(instance, title): - return note.render_html() + note = note_instance(instance, title) - return '' + if not note or not note.content: + return '' + + content = note.content + media_prefix = django_settings.MEDIA_URL + + # Replace any embedded image references with the actual image data + root = lxml.html.fragment_fromstring(content, create_parent='div') + + for img in root.iter('img'): + src = img.get('src') + if not src: + continue + + if not src.startswith(media_prefix): + continue + + img_src = src[len(media_prefix) :] + + # Extract img size attributes + img_data = uploaded_image( + img_src, + replace_missing=True, + width=img.get('width', None), + height=img.get('height', None), + ) + + # Replace the src attribute + img.set('src', img_data) + + content = lxml.html.tostring(root, encoding='unicode') + # fragment_fromstring wraps in a
— strip it back off + content = content.removeprefix('
').removesuffix('
') + + return mark_safe(content) @register.simple_tag()