From 317c2666b83c18458aa664648b98d8a5018d9631 Mon Sep 17 00:00:00 2001 From: Oliver Date: Tue, 21 Nov 2023 00:12:25 +1100 Subject: [PATCH] Improvements for part.full_name (#5946) * Improvements for part.full_name - Compile and cache the template - Reduces typical render time from ~20ms to ~0.2ms * Force autoescape --- InvenTree/part/helpers.py | 68 +++++++++++++++++++++++++++++++++++++++ InvenTree/part/models.py | 38 ++-------------------- 2 files changed, 71 insertions(+), 35 deletions(-) create mode 100644 InvenTree/part/helpers.py diff --git a/InvenTree/part/helpers.py b/InvenTree/part/helpers.py new file mode 100644 index 0000000000..08ee86d3b5 --- /dev/null +++ b/InvenTree/part/helpers.py @@ -0,0 +1,68 @@ +"""Various helper functions for the part app""" + +import logging + +from jinja2 import Environment + +logger = logging.getLogger('inventree') + + +# Compiled template for rendering the 'full_name' attribute of a Part +_part_full_name_template = None +_part_full_name_template_string = '' + + +def compile_full_name_template(*args, **kwargs): + """Recompile the template for rendering the 'full_name' attribute of a Part. + + This function is called whenever the 'PART_NAME_FORMAT' setting is changed. + """ + + from common.models import InvenTreeSetting + + global _part_full_name_template + global _part_full_name_template_string + + template_string = InvenTreeSetting.get_setting('PART_NAME_FORMAT', '') + + # Skip if the template string has not changed + if template_string == _part_full_name_template_string and _part_full_name_template is not None: + return _part_full_name_template + + _part_full_name_template_string = template_string + + env = Environment( + autoescape=True, + variable_start_string='{{', + variable_end_string='}}' + ) + + # Compile the template + try: + _part_full_name_template = env.from_string(template_string) + except Exception: + _part_full_name_template = None + + return _part_full_name_template + + +def render_part_full_name(part) -> str: + """Render the 'full_name' attribute of a Part. + + To improve render efficiency, we re-compile the template whenever the setting is changed + + Args: + part: The Part object to render + """ + + template = compile_full_name_template() + + if template: + try: + return template.render(part=part) + except Exception as e: + logger.warning("exception while trying to create full name for part %s: %s", part.name, e) + + # Fallback to the default format + elements = [el for el in [part.IPN, part.name, part.revision] if el] + return ' | '.join(elements) diff --git a/InvenTree/part/models.py b/InvenTree/part/models.py index 745704f580..4af2fc1b61 100644 --- a/InvenTree/part/models.py +++ b/InvenTree/part/models.py @@ -27,7 +27,6 @@ from django_cleanup import cleanup from djmoney.contrib.exchange.exceptions import MissingRate from djmoney.contrib.exchange.models import convert_money from djmoney.money import Money -from jinja2 import Template from mptt.exceptions import InvalidMove from mptt.managers import TreeManager from mptt.models import MPTTModel, TreeForeignKey @@ -40,6 +39,7 @@ import InvenTree.conversion import InvenTree.fields import InvenTree.ready import InvenTree.tasks +import part.helpers as part_helpers import part.settings as part_settings import users.models from build import models as BuildModels @@ -692,41 +692,9 @@ class Part(InvenTreeBarcodeMixin, InvenTreeNotesMixin, MetadataMixin, MPTTModel) @property def full_name(self): - """Format a 'full name' for this Part based on the format PART_NAME_FORMAT defined in InvenTree settings. + """Format a 'full name' for this Part based on the format PART_NAME_FORMAT defined in InvenTree settings""" - As a failsafe option, the following is done: - - - IPN (if not null) - - Part name - - Part variant (if not null) - - Elements are joined by the | character - """ - full_name_pattern = InvenTreeSetting.get_setting('PART_NAME_FORMAT') - - try: - context = {'part': self} - template_string = Template(full_name_pattern) - full_name = template_string.render(context) - - return full_name - - except Exception as attr_err: - - logger.warning("exception while trying to create full name for part %s: %s", self.name, attr_err) - - # Fallback to default format - elements = [] - - if self.IPN: - elements.append(self.IPN) - - elements.append(self.name) - - if self.revision: - elements.append(self.revision) - - return ' | '.join(elements) + return part_helpers.render_part_full_name(self) def get_absolute_url(self): """Return the web URL for viewing this part."""