Coverage for src/backend/InvenTree/part/helpers.py: 82%
34 statements
« prev ^ index » next coverage.py v7.15.2, created at 2026-10-07 17:47 +0000
« prev ^ index » next coverage.py v7.15.2, created at 2026-10-07 17:47 +0000
1"""Various helper functions for the part app."""
3import os
5from django.conf import settings
7import structlog
8from jinja2.sandbox import SandboxedEnvironment
10from common.settings import get_global_setting
12logger = structlog.get_logger('inventree')
15# Compiled template for rendering the 'full_name' attribute of a Part
16_part_full_name_template = None
17_part_full_name_template_string = ''
20def compile_full_name_template(*args, **kwargs):
21 """Recompile the template for rendering the 'full_name' attribute of a Part.
23 This function is called whenever the 'PART_NAME_FORMAT' setting is changed.
24 """
25 global _part_full_name_template
26 global _part_full_name_template_string
28 template_string = get_global_setting('PART_NAME_FORMAT', cache=True)
30 # Skip if the template string has not changed
31 if (
32 template_string == _part_full_name_template_string
33 and _part_full_name_template is not None
34 ):
35 return _part_full_name_template
37 # Cache the template string
38 _part_full_name_template_string = template_string
40 env = SandboxedEnvironment(variable_start_string='{{', variable_end_string='}}')
42 # Compile the template
43 try:
44 _part_full_name_template = env.from_string(template_string)
45 except Exception:
46 _part_full_name_template = None
48 return _part_full_name_template
51def render_part_full_name(part) -> str:
52 """Render the 'full_name' attribute of a Part.
54 To improve render efficiency, we re-compile the template whenever the setting is changed
56 Args:
57 part: The Part object to render
58 """
59 template = compile_full_name_template()
61 if template: 61 ↛ 72line 61 didn't jump to line 72 because the condition on line 61 was always true
62 try:
63 return template.render(part=part)
64 except Exception as e:
65 logger.warning(
66 'exception while trying to create full name for part %s: %s',
67 part.name,
68 e,
69 )
71 # Fallback to the default format
72 elements = [el for el in [part.IPN, part.name, part.revision] if el]
73 return ' | '.join(elements)
76# Subdirectory for storing part images
77PART_IMAGE_DIR = 'part_images'
80def get_part_image_directory() -> str:
81 """Return the directory where part images are stored.
83 Returns:
84 str: Directory where part images are stored
86 TODO: Future work may be needed here to support other storage backends, such as S3
87 """
88 part_image_directory = os.path.abspath(
89 os.path.join(settings.MEDIA_ROOT, PART_IMAGE_DIR)
90 )
92 # Create the directory if it does not exist
93 if not os.path.exists(part_image_directory):
94 os.makedirs(part_image_directory)
96 return part_image_directory