From ff23d392f29ec05a4b0f0816f367b727b0946002 Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Wed, 8 Jan 2025 22:29:57 +0100 Subject: [PATCH 01/14] reduce tags more --- src/backend/InvenTree/InvenTree/api.py | 2 +- src/backend/InvenTree/InvenTree/helpers.py | 29 +- .../templatetags/inventree_extras.py | 317 +----------------- src/backend/InvenTree/part/test_part.py | 73 +--- 4 files changed, 23 insertions(+), 398 deletions(-) diff --git a/src/backend/InvenTree/InvenTree/api.py b/src/backend/InvenTree/InvenTree/api.py index 04ecbad05b64..041fae1d4967 100644 --- a/src/backend/InvenTree/InvenTree/api.py +++ b/src/backend/InvenTree/InvenTree/api.py @@ -21,11 +21,11 @@ import InvenTree.version import users.models from InvenTree.mixins import ListCreateAPI -from InvenTree.templatetags.inventree_extras import plugins_info from part.models import Part from plugin.serializers import MetadataSerializer from users.models import ApiToken +from .helpers import plugins_info from .helpers_email import is_email_configured from .mixins import ListAPI, RetrieveUpdateAPI from .status import check_system_health, is_worker_running diff --git a/src/backend/InvenTree/InvenTree/helpers.py b/src/backend/InvenTree/InvenTree/helpers.py index 11110c30ced7..01811a0cb68c 100644 --- a/src/backend/InvenTree/InvenTree/helpers.py +++ b/src/backend/InvenTree/InvenTree/helpers.py @@ -181,18 +181,6 @@ def getLogoImage(as_file=False, custom=True): return getStaticUrl('img/inventree.png') -def getSplashScreen(custom=True): - """Return the InvenTree splash screen, or a custom splash if available.""" - static_storage = StaticFilesStorage() - - if custom and settings.CUSTOM_SPLASH: - if static_storage.exists(settings.CUSTOM_SPLASH): - return static_storage.url(settings.CUSTOM_SPLASH) - - # No custom splash screen - return static_storage.url('img/inventree_splash.jpg') - - def TestIfImageURL(url): """Test if an image URL (or filename) looks like a valid image format. @@ -1061,3 +1049,20 @@ def pui_url(subpath: str) -> str: if not subpath.startswith('/'): subpath = '/' + subpath return f'/{settings.FRONTEND_URL_BASE}{subpath}' + + +def plugins_info(*args, **kwargs): + """Return information about activated plugins.""" + from plugin.registry import registry + + # Check if plugins are even enabled + if not settings.PLUGINS_ENABLED: + return False + + # Fetch plugins + plug_list = [plg for plg in registry.plugins.values() if plg.plugin_config().active] + # Format list + return [ + {'name': plg.name, 'slug': plg.slug, 'version': plg.version} + for plg in plug_list + ] diff --git a/src/backend/InvenTree/InvenTree/templatetags/inventree_extras.py b/src/backend/InvenTree/InvenTree/templatetags/inventree_extras.py index 228b5aeb39fa..b1c4491a34c5 100644 --- a/src/backend/InvenTree/InvenTree/templatetags/inventree_extras.py +++ b/src/backend/InvenTree/InvenTree/templatetags/inventree_extras.py @@ -1,31 +1,18 @@ """This module provides template tags for extra functionality, over and above the built-in Django tags.""" -from datetime import date, datetime - from django import template -from django.conf import settings as djangosettings -from django.urls import NoReverseMatch, reverse -from django.utils.safestring import mark_safe -from django.utils.translation import gettext_lazy as _ import common.models import InvenTree.helpers import InvenTree.helpers_model import plugin.models -from common.currency import currency_code_default from common.settings import get_global_setting -from InvenTree import settings, version -from plugin import registry +from InvenTree import version from plugin.plugin import InvenTreePlugin register = template.Library() -import structlog - -logger = structlog.get_logger('inventree') - - @register.simple_tag() def define(value, *args, **kwargs): """Shortcut function to overcome the shortcomings of the django templating language. @@ -43,58 +30,6 @@ def decimal(x, *args, **kwargs): return InvenTree.helpers.decimal2string(x) -@register.simple_tag(takes_context=True) -def render_date(context, date_object): - """Renders a date according to the preference of the provided user. - - Note that the user preference is stored using the formatting adopted by moment.js, - which differs from the python formatting! - """ - if date_object is None: - return None - - if isinstance(date_object, str): - date_object = date_object.strip() - - # Check for empty string - if len(date_object) == 0: - return None - - # If a string is passed, first convert it to a datetime - try: - date_object = date.fromisoformat(date_object) - except ValueError: - logger.warning('Tried to convert invalid date string: %s', date_object) - return None - - # We may have already pre-cached the date format by calling this already! - user_date_format = context.get('user_date_format', None) - - if user_date_format is None: - user = context.get('user', None) - - if user and user.is_authenticated: - # User is specified - look for their date display preference - user_date_format = common.models.InvenTreeUserSetting.get_setting( - 'DATE_DISPLAY_FORMAT', user=user - ) - else: - user_date_format = 'YYYY-MM-DD' - - # Convert the format string to Pythonic equivalent - replacements = [('YYYY', '%Y'), ('MMM', '%b'), ('MM', '%m'), ('DD', '%d')] - - for o, n in replacements: - user_date_format = user_date_format.replace(o, n) - - # Update the context cache - context['user_date_format'] = user_date_format - - if isinstance(date_object, (datetime, date)): - return date_object.strftime(user_date_format) - return date_object - - @register.simple_tag def render_currency(money, **kwargs): """Render a currency / Money object.""" @@ -119,40 +54,6 @@ def to_list(*args): return args -@register.simple_tag() -def plugins_enabled(*args, **kwargs): - """Return True if plugins are enabled for the server instance.""" - return djangosettings.PLUGINS_ENABLED - - -@register.simple_tag() -def plugins_install_disabled(*args, **kwargs): - """Return True if plugin install is disabled for the server instance.""" - return djangosettings.PLUGINS_INSTALL_DISABLED - - -@register.simple_tag() -def plugins_info(*args, **kwargs): - """Return information about activated plugins.""" - # Check if plugins are even enabled - if not djangosettings.PLUGINS_ENABLED: - return False - - # Fetch plugins - plug_list = [plg for plg in registry.plugins.values() if plg.plugin_config().active] - # Format list - return [ - {'name': plg.name, 'slug': plg.slug, 'version': plg.version} - for plg in plug_list - ] - - -@register.simple_tag() -def inventree_db_engine(*args, **kwargs): - """Return the InvenTree database backend e.g. 'postgresql'.""" - return version.inventreeDatabase() or _('Unknown database') - - @register.simple_tag() def inventree_instance_name(*args, **kwargs): """Return the InstanceName associated with the current database.""" @@ -165,33 +66,6 @@ def inventree_title(*args, **kwargs): return version.inventreeInstanceTitle() -@register.simple_tag() -def inventree_logo(**kwargs): - """Return the InvenTree logo, *or* a custom logo if the user has provided one. - - Returns a path to an image file, which can be rendered in the web interface - """ - return InvenTree.helpers.getLogoImage(**kwargs) - - -@register.simple_tag() -def inventree_splash(**kwargs): - """Return the URL for the InvenTree splash screen, *or* a custom screen if the user has provided one.""" - return InvenTree.helpers.getSplashScreen(**kwargs) - - -@register.simple_tag() -def inventree_base_url(*args, **kwargs): - """Return the base URL of the InvenTree server.""" - return InvenTree.helpers_model.get_base_url() - - -@register.simple_tag() -def python_version(*args, **kwargs): - """Return the current python version.""" - return version.inventreePythonVersion() - - @register.simple_tag() def inventree_version(shortstring=False, *args, **kwargs): """Return InvenTree version string.""" @@ -200,36 +74,6 @@ def inventree_version(shortstring=False, *args, **kwargs): return version.inventreeVersion() -@register.simple_tag() -def inventree_is_development(*args, **kwargs): - """Returns True if this is a development version of InvenTree.""" - return version.isInvenTreeDevelopmentVersion() - - -@register.simple_tag() -def inventree_is_release(*args, **kwargs): - """Returns True if this is a release version of InvenTree.""" - return not version.isInvenTreeDevelopmentVersion() - - -@register.simple_tag() -def inventree_docs_version(*args, **kwargs): - """Returns the InvenTree documentation version.""" - return version.inventreeDocsVersion() - - -@register.simple_tag() -def inventree_api_version(*args, **kwargs): - """Return InvenTree API version.""" - return version.inventreeApiVersion() - - -@register.simple_tag() -def django_version(*args, **kwargs): - """Return Django version string.""" - return version.inventreeDjangoVersion() - - @register.simple_tag() def inventree_commit_hash(*args, **kwargs): """Return InvenTree git commit hash string.""" @@ -242,60 +86,6 @@ def inventree_commit_date(*args, **kwargs): return version.inventreeCommitDate() -@register.simple_tag() -def inventree_installer(*args, **kwargs): - """Return InvenTree package installer string.""" - return version.inventreeInstaller() - - -@register.simple_tag() -def inventree_branch(*args, **kwargs): - """Return InvenTree git branch string.""" - return version.inventreeBranch() - - -@register.simple_tag() -def inventree_target(*args, **kwargs): - """Return InvenTree target string.""" - return version.inventreeTarget() - - -@register.simple_tag() -def inventree_platform(*args, **kwargs): - """Return InvenTree platform string.""" - return version.inventreePlatform() - - -@register.simple_tag() -def inventree_github_url(*args, **kwargs): - """Return URL for InvenTree github site.""" - return version.inventreeGithubUrl() - - -@register.simple_tag() -def inventree_docs_url(*args, **kwargs): - """Return URL for InvenTree documentation site.""" - return version.inventreeDocUrl() - - -@register.simple_tag() -def inventree_app_url(*args, **kwargs): - """Return URL for InvenTree app site.""" - return version.inventreeAppUrl() - - -@register.simple_tag() -def inventree_credits_url(*args, **kwargs): - """Return URL for InvenTree credits site.""" - return version.inventreeCreditsUrl() - - -@register.simple_tag() -def default_currency(*args, **kwargs): - """Returns the default currency code.""" - return currency_code_default() - - @register.simple_tag() def setting_object(key, *args, **kwargs): """Return a setting object specified by the given key. @@ -346,24 +136,6 @@ def settings_value(key, *args, **kwargs): return get_global_setting(key) -@register.simple_tag() -def user_settings(user, *args, **kwargs): - """Return all USER settings as a key:value dict.""" - return common.models.InvenTreeUserSetting.allValues(user=user) - - -@register.simple_tag() -def global_settings(*args, **kwargs): - """Return all GLOBAL InvenTree settings as a key:value dict.""" - return common.models.InvenTreeSetting.allValues() - - -@register.simple_tag() -def visible_global_settings(*args, **kwargs): - """Return any global settings which are not marked as 'hidden'.""" - return common.models.InvenTreeSetting.allValues(exclude_hidden=True) - - @register.filter def keyvalue(dict, key): """Access to key of supplied dict. @@ -372,90 +144,3 @@ def keyvalue(dict, key): {% mydict|keyvalue:mykey %} """ return dict.get(key) - - -@register.simple_tag() -def authorized_owners(group): - """Return authorized owners.""" - owners = [] - - try: - for owner in group.get_related_owners(include_group=True): - owners.append(owner.owner) - except AttributeError: - # group is None - pass - except TypeError: - # group.get_users returns None - pass - - return owners - - -@register.simple_tag() -def object_link(url_name, pk, ref): - """Return highlighted link to object.""" - try: - ref_url = reverse(url_name, kwargs={'pk': pk}) - return mark_safe(f'{ref}') - except NoReverseMatch: - return None - - -@register.simple_tag() -def mail_configured(): - """Return if mail is configured.""" - return bool(settings.EMAIL_HOST) - - -@register.simple_tag() -def inventree_customize(reference, *args, **kwargs): - """Return customization values for the user interface.""" - return djangosettings.CUSTOMIZE.get(reference, '') - - -@register.simple_tag() -def admin_index(user): - """Return a URL for the admin interface.""" - if not djangosettings.INVENTREE_ADMIN_ENABLED: - return '' - - if not user.is_staff: - return '' - - return reverse('admin:index') - - -@register.simple_tag() -def admin_url(user, table, pk): - """Generate a link to the admin site for the given model instance. - - - If the admin site is disabled, an empty URL is returned - - If the user is not a staff user, an empty URL is returned - - If the user does not have the correct permission, an empty URL is returned - """ - app, model = table.strip().split('.') - - from django.urls import reverse - - if not djangosettings.INVENTREE_ADMIN_ENABLED: - return '' - - if not user.is_staff: - return '' - - # Check the user has the correct permission - perm_string = f'{app}.change_{model}' - if not user.has_perm(perm_string): - return '' - - # Fallback URL - url = reverse(f'admin:{app}_{model}_changelist') - - if pk: - try: - url = reverse(f'admin:{app}_{model}_change', args=(pk,)) - except NoReverseMatch: - pass - - return url diff --git a/src/backend/InvenTree/part/test_part.py b/src/backend/InvenTree/part/test_part.py index 558c460e9734..c55546fbe396 100644 --- a/src/backend/InvenTree/part/test_part.py +++ b/src/backend/InvenTree/part/test_part.py @@ -6,17 +6,11 @@ from django.core.cache import cache from django.core.exceptions import ValidationError from django.test import TestCase -from django.test.utils import override_settings from allauth.account.models import EmailAddress import part.settings -from common.models import ( - InvenTreeSetting, - InvenTreeUserSetting, - NotificationEntry, - NotificationMessage, -) +from common.models import NotificationEntry, NotificationMessage from common.notifications import UIMessageNotification, storage from common.settings import get_global_setting, set_global_setting from InvenTree import version @@ -53,34 +47,14 @@ def test_add(self): """Test that the 'add.""" self.assertEqual(int(inventree_extras.add(3, 5)), 8) - def test_plugins_enabled(self): - """Test the plugins_enabled tag.""" - self.assertEqual(inventree_extras.plugins_enabled(), True) - - def test_plugins_install_disabled(self): - """Test the plugins_install_disabled tag.""" - self.assertEqual(inventree_extras.plugins_install_disabled(), False) - def test_inventree_instance_name(self): """Test the 'instance name' setting.""" self.assertEqual(inventree_extras.inventree_instance_name(), 'InvenTree') - @override_settings(SITE_URL=None) - def test_inventree_base_url(self): - """Test that the base URL tag returns correctly.""" - self.assertEqual(inventree_extras.inventree_base_url(), '') - - def test_inventree_is_release(self): - """Test that the release version check functions as expected.""" + def test_inventree_version(self): + """Test the 'version' setting.""" self.assertEqual( - inventree_extras.inventree_is_release(), - not version.isInvenTreeDevelopmentVersion(), - ) - - def test_inventree_docs_version(self): - """Test that the documentation version template tag returns correctly.""" - self.assertEqual( - inventree_extras.inventree_docs_version(), version.inventreeDocsVersion() + inventree_extras.inventree_version(), version.INVENTREE_VERSION ) def test_hash(self): @@ -103,49 +77,10 @@ def test_date(self): else: self.assertEqual(len(d.split('-')), 3) - def test_github(self): - """Test that the github URL template tag returns correctly.""" - self.assertIn('github.com', inventree_extras.inventree_github_url()) - - def test_docs(self): - """Test that the documentation URL template tag returns correctly.""" - self.assertIn('docs.inventree.org', inventree_extras.inventree_docs_url()) - def test_keyvalue(self): """Test keyvalue template tag.""" self.assertEqual(inventree_extras.keyvalue({'a': 'a'}, 'a'), 'a') - def test_mail_configured(self): - """Test that mail configuration returns False.""" - self.assertEqual(inventree_extras.mail_configured(), False) - - def test_user_settings(self): - """Test user settings.""" - result = inventree_extras.user_settings(self.user) - self.assertEqual(len(result), len(InvenTreeUserSetting.SETTINGS)) - - def test_global_settings(self): - """Test global settings.""" - result = inventree_extras.global_settings() - self.assertEqual(len(result), len(InvenTreeSetting.SETTINGS)) - - def test_visible_global_settings(self): - """Test that hidden global settings are actually hidden.""" - result = inventree_extras.visible_global_settings() - - n = len(result) - - n_hidden = 0 - n_visible = 0 - - for val in InvenTreeSetting.SETTINGS.values(): - if val.get('hidden', False): - n_hidden += 1 - else: - n_visible += 1 - - self.assertEqual(n, n_visible) - class PartTest(TestCase): """Tests for the Part model.""" From 6dd228bc13fab73fffc9434aa191566fbd259307 Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Thu, 16 Jan 2025 01:54:27 +0100 Subject: [PATCH 02/14] remove splashscreen usage --- src/backend/InvenTree/templates/503.html | 2 +- src/backend/InvenTree/templates/account/base.html | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/backend/InvenTree/templates/503.html b/src/backend/InvenTree/templates/503.html index 8630d794a020..5e4b65ea9dba 100644 --- a/src/backend/InvenTree/templates/503.html +++ b/src/backend/InvenTree/templates/503.html @@ -11,7 +11,7 @@ {% trans 'Site is in Maintenance' %} {% endblock page_title %} -{% block body_class %}login-screen' style='background: url({% inventree_splash %}); background-size: cover;{% endblock body_class %} +{% block body_class %}login-screen{% endblock body_class %} {% block body %} diff --git a/src/backend/InvenTree/templates/account/base.html b/src/backend/InvenTree/templates/account/base.html index 0e298e8cc0bc..eee8f0bc5c96 100644 --- a/src/backend/InvenTree/templates/account/base.html +++ b/src/backend/InvenTree/templates/account/base.html @@ -3,7 +3,7 @@ {% load inventree_extras %} {% block body %} - +
From 10bb4315643aeef79029b2bae2c6540c2fee6af0 Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Thu, 16 Jan 2025 01:57:43 +0100 Subject: [PATCH 03/14] fix test --- src/backend/InvenTree/part/test_part.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/backend/InvenTree/part/test_part.py b/src/backend/InvenTree/part/test_part.py index c55546fbe396..b4bd02620c6f 100644 --- a/src/backend/InvenTree/part/test_part.py +++ b/src/backend/InvenTree/part/test_part.py @@ -54,7 +54,11 @@ def test_inventree_instance_name(self): def test_inventree_version(self): """Test the 'version' setting.""" self.assertEqual( - inventree_extras.inventree_version(), version.INVENTREE_VERSION + inventree_extras.inventree_version(), version.inventreeVersion() + ) + self.assertNotEqual( + inventree_extras.inventree_version(shortstring=True), + version.inventreeVersion(), ) def test_hash(self): From 3742b8146f6038905afa1928275912fa7ca42957 Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Sun, 26 Jan 2025 13:50:04 +0100 Subject: [PATCH 04/14] reintroduce inventree_logo --- .../InvenTree/InvenTree/templatetags/inventree_extras.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/backend/InvenTree/InvenTree/templatetags/inventree_extras.py b/src/backend/InvenTree/InvenTree/templatetags/inventree_extras.py index b1c4491a34c5..941f890cd7b6 100644 --- a/src/backend/InvenTree/InvenTree/templatetags/inventree_extras.py +++ b/src/backend/InvenTree/InvenTree/templatetags/inventree_extras.py @@ -60,6 +60,15 @@ def inventree_instance_name(*args, **kwargs): return version.inventreeInstanceName() +@register.simple_tag() +def inventree_logo(**kwargs): + """Return the InvenTree logo, *or* a custom logo if the user has provided one. + + Returns a path to an image file, which can be rendered in the web interface. + """ + return InvenTree.helpers.getLogoImage(**kwargs) + + @register.simple_tag() def inventree_title(*args, **kwargs): """Return the title for the current instance - respecting the settings.""" From c5d29c44403287e5402e439e977a8cfd673b8744 Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Sun, 26 Jan 2025 13:51:06 +0100 Subject: [PATCH 05/14] re-add splashscreen fnct --- src/backend/InvenTree/InvenTree/helpers.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/backend/InvenTree/InvenTree/helpers.py b/src/backend/InvenTree/InvenTree/helpers.py index b0c36ea540b4..8bdc3ddc187d 100644 --- a/src/backend/InvenTree/InvenTree/helpers.py +++ b/src/backend/InvenTree/InvenTree/helpers.py @@ -181,6 +181,18 @@ def getLogoImage(as_file=False, custom=True): return getStaticUrl('img/inventree.png') +def getSplashScreen(custom=True): + """Return the InvenTree splash screen, or a custom splash if available.""" + static_storage = StaticFilesStorage() + + if custom and settings.CUSTOM_SPLASH: + if static_storage.exists(settings.CUSTOM_SPLASH): + return static_storage.url(settings.CUSTOM_SPLASH) + + # No custom splash screen + return static_storage.url('img/inventree_splash.jpg') + + def TestIfImageURL(url): """Test if an image URL (or filename) looks like a valid image format. From 3fffb3d8a2e0fe40d59fbb8fb9e79796a78cdf8e Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Mon, 27 Jan 2025 23:54:51 +0100 Subject: [PATCH 06/14] re-add needed tag --- .../templatetags/inventree_extras.py | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/src/backend/InvenTree/InvenTree/templatetags/inventree_extras.py b/src/backend/InvenTree/InvenTree/templatetags/inventree_extras.py index 941f890cd7b6..6821914ed264 100644 --- a/src/backend/InvenTree/InvenTree/templatetags/inventree_extras.py +++ b/src/backend/InvenTree/InvenTree/templatetags/inventree_extras.py @@ -1,6 +1,7 @@ """This module provides template tags for extra functionality, over and above the built-in Django tags.""" from django import template +from django.conf import settings as djangosettings import common.models import InvenTree.helpers @@ -60,6 +61,12 @@ def inventree_instance_name(*args, **kwargs): return version.inventreeInstanceName() +@register.simple_tag() +def inventree_title(*args, **kwargs): + """Return the title for the current instance - respecting the settings.""" + return version.inventreeInstanceTitle() + + @register.simple_tag() def inventree_logo(**kwargs): """Return the InvenTree logo, *or* a custom logo if the user has provided one. @@ -69,12 +76,6 @@ def inventree_logo(**kwargs): return InvenTree.helpers.getLogoImage(**kwargs) -@register.simple_tag() -def inventree_title(*args, **kwargs): - """Return the title for the current instance - respecting the settings.""" - return version.inventreeInstanceTitle() - - @register.simple_tag() def inventree_version(shortstring=False, *args, **kwargs): """Return InvenTree version string.""" @@ -153,3 +154,9 @@ def keyvalue(dict, key): {% mydict|keyvalue:mykey %} """ return dict.get(key) + + +@register.simple_tag() +def inventree_customize(reference, *args, **kwargs): + """Return customization values for the user interface.""" + return djangosettings.CUSTOMIZE.get(reference, '') From bbd5ba75885f64a989817a47e329638c9b334a39 Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Wed, 29 Jan 2025 07:59:20 +0100 Subject: [PATCH 07/14] re-add date renderer --- .../templatetags/inventree_extras.py | 59 +++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/src/backend/InvenTree/InvenTree/templatetags/inventree_extras.py b/src/backend/InvenTree/InvenTree/templatetags/inventree_extras.py index 6821914ed264..c78c01b74882 100644 --- a/src/backend/InvenTree/InvenTree/templatetags/inventree_extras.py +++ b/src/backend/InvenTree/InvenTree/templatetags/inventree_extras.py @@ -1,8 +1,12 @@ """This module provides template tags for extra functionality, over and above the built-in Django tags.""" +from datetime import date, datetime + from django import template from django.conf import settings as djangosettings +import structlog + import common.models import InvenTree.helpers import InvenTree.helpers_model @@ -14,6 +18,9 @@ register = template.Library() +logger = structlog.get_logger('inventree') + + @register.simple_tag() def define(value, *args, **kwargs): """Shortcut function to overcome the shortcomings of the django templating language. @@ -31,6 +38,58 @@ def decimal(x, *args, **kwargs): return InvenTree.helpers.decimal2string(x) +@register.simple_tag(takes_context=True) +def render_date(context, date_object): + """Renders a date according to the preference of the provided user. + + Note that the user preference is stored using the formatting adopted by moment.js, + which differs from the python formatting! + """ + if date_object is None: + return None + + if isinstance(date_object, str): + date_object = date_object.strip() + + # Check for empty string + if len(date_object) == 0: + return None + + # If a string is passed, first convert it to a datetime + try: + date_object = date.fromisoformat(date_object) + except ValueError: + logger.warning('Tried to convert invalid date string: %s', date_object) + return None + + # We may have already pre-cached the date format by calling this already! + user_date_format = context.get('user_date_format', None) + + if user_date_format is None: + user = context.get('user', None) + + if user and user.is_authenticated: + # User is specified - look for their date display preference + user_date_format = common.models.InvenTreeUserSetting.get_setting( + 'DATE_DISPLAY_FORMAT', user=user + ) + else: + user_date_format = 'YYYY-MM-DD' + + # Convert the format string to Pythonic equivalent + replacements = [('YYYY', '%Y'), ('MMM', '%b'), ('MM', '%m'), ('DD', '%d')] + + for o, n in replacements: + user_date_format = user_date_format.replace(o, n) + + # Update the context cache + context['user_date_format'] = user_date_format + + if isinstance(date_object, (datetime, date)): + return date_object.strftime(user_date_format) + return date_object + + @register.simple_tag def render_currency(money, **kwargs): """Render a currency / Money object.""" From 644bb53110c53fa19d797584517b35a708acaf34 Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Wed, 29 Jan 2025 07:59:56 +0100 Subject: [PATCH 08/14] simplify away user specific stuff - there are no users in reports --- .../templatetags/inventree_extras.py | 34 ++++--------------- 1 file changed, 7 insertions(+), 27 deletions(-) diff --git a/src/backend/InvenTree/InvenTree/templatetags/inventree_extras.py b/src/backend/InvenTree/InvenTree/templatetags/inventree_extras.py index c78c01b74882..8bd5a0e11ca3 100644 --- a/src/backend/InvenTree/InvenTree/templatetags/inventree_extras.py +++ b/src/backend/InvenTree/InvenTree/templatetags/inventree_extras.py @@ -38,13 +38,8 @@ def decimal(x, *args, **kwargs): return InvenTree.helpers.decimal2string(x) -@register.simple_tag(takes_context=True) -def render_date(context, date_object): - """Renders a date according to the preference of the provided user. - - Note that the user preference is stored using the formatting adopted by moment.js, - which differs from the python formatting! - """ +def render_date(date_object): + """Renders a date object as a string.""" if date_object is None: return None @@ -62,28 +57,13 @@ def render_date(context, date_object): logger.warning('Tried to convert invalid date string: %s', date_object) return None - # We may have already pre-cached the date format by calling this already! - user_date_format = context.get('user_date_format', None) - - if user_date_format is None: - user = context.get('user', None) - - if user and user.is_authenticated: - # User is specified - look for their date display preference - user_date_format = common.models.InvenTreeUserSetting.get_setting( - 'DATE_DISPLAY_FORMAT', user=user - ) - else: - user_date_format = 'YYYY-MM-DD' - - # Convert the format string to Pythonic equivalent - replacements = [('YYYY', '%Y'), ('MMM', '%b'), ('MM', '%m'), ('DD', '%d')] + user_date_format = 'YYYY-MM-DD' - for o, n in replacements: - user_date_format = user_date_format.replace(o, n) + # Convert the format string to Pythonic equivalent + replacements = [('YYYY', '%Y'), ('MMM', '%b'), ('MM', '%m'), ('DD', '%d')] - # Update the context cache - context['user_date_format'] = user_date_format + for o, n in replacements: + user_date_format = user_date_format.replace(o, n) if isinstance(date_object, (datetime, date)): return date_object.strftime(user_date_format) From 3f4559233906c2bcea479e8d1922c748f76577f0 Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Wed, 29 Jan 2025 08:01:46 +0100 Subject: [PATCH 09/14] and simplify a bit more --- .../InvenTree/InvenTree/templatetags/inventree_extras.py | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/src/backend/InvenTree/InvenTree/templatetags/inventree_extras.py b/src/backend/InvenTree/InvenTree/templatetags/inventree_extras.py index 8bd5a0e11ca3..cf21b6892684 100644 --- a/src/backend/InvenTree/InvenTree/templatetags/inventree_extras.py +++ b/src/backend/InvenTree/InvenTree/templatetags/inventree_extras.py @@ -57,14 +57,7 @@ def render_date(date_object): logger.warning('Tried to convert invalid date string: %s', date_object) return None - user_date_format = 'YYYY-MM-DD' - - # Convert the format string to Pythonic equivalent - replacements = [('YYYY', '%Y'), ('MMM', '%b'), ('MM', '%m'), ('DD', '%d')] - - for o, n in replacements: - user_date_format = user_date_format.replace(o, n) - + user_date_format = '%y-%b-%d' if isinstance(date_object, (datetime, date)): return date_object.strftime(user_date_format) return date_object From fe70d03de5c6e65910966286f76363528412d0b7 Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Fri, 31 Jan 2025 00:13:23 +0100 Subject: [PATCH 10/14] increase coverage --- src/backend/InvenTree/part/test_part.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/backend/InvenTree/part/test_part.py b/src/backend/InvenTree/part/test_part.py index b4bd02620c6f..de518bb81a84 100644 --- a/src/backend/InvenTree/part/test_part.py +++ b/src/backend/InvenTree/part/test_part.py @@ -51,6 +51,14 @@ def test_inventree_instance_name(self): """Test the 'instance name' setting.""" self.assertEqual(inventree_extras.inventree_instance_name(), 'InvenTree') + def test_inventree_title(self): + """Test the 'inventree_title' setting.""" + self.assertEqual(inventree_extras.inventree_title(), 'InvenTree') + + def test_inventree_customize(self): + """Test the 'inventree_customize' template tag.""" + self.assertEqual(inventree_extras.inventree_customize('abc'), '') + def test_inventree_version(self): """Test the 'version' setting.""" self.assertEqual( @@ -85,6 +93,18 @@ def test_keyvalue(self): """Test keyvalue template tag.""" self.assertEqual(inventree_extras.keyvalue({'a': 'a'}, 'a'), 'a') + def test_to_list(self): + """Test the 'to_list' template tag.""" + self.assertEqual(inventree_extras.to_list(1, 2, 3), (1, 2, 3)) + + def test_render_date(self): + """Test the 'render_date' template tag.""" + self.assertEqual(inventree_extras.render_date('2021-01-01'), '2021-01-01') + + self.assertEqual(inventree_extras.render_date(' '), None) + self.assertEqual(inventree_extras.render_date('aaaa'), None) + self.assertEqual(inventree_extras.render_date(1234), 1234) + class PartTest(TestCase): """Tests for the Part model.""" From 649233222a9a491d5e6b9f5d96bb0a729ddc46dc Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Fri, 31 Jan 2025 00:13:37 +0100 Subject: [PATCH 11/14] fix format --- .../InvenTree/InvenTree/templatetags/inventree_extras.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/backend/InvenTree/InvenTree/templatetags/inventree_extras.py b/src/backend/InvenTree/InvenTree/templatetags/inventree_extras.py index cf21b6892684..22ca714e960a 100644 --- a/src/backend/InvenTree/InvenTree/templatetags/inventree_extras.py +++ b/src/backend/InvenTree/InvenTree/templatetags/inventree_extras.py @@ -57,7 +57,7 @@ def render_date(date_object): logger.warning('Tried to convert invalid date string: %s', date_object) return None - user_date_format = '%y-%b-%d' + user_date_format = '%Y-%m-%d' if isinstance(date_object, (datetime, date)): return date_object.strftime(user_date_format) return date_object From 13b7f0f28a42286f3b4b30f02d014681f8da9719 Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Fri, 31 Jan 2025 00:30:38 +0100 Subject: [PATCH 12/14] and more coverage --- src/backend/InvenTree/part/test_part.py | 33 +++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/src/backend/InvenTree/part/test_part.py b/src/backend/InvenTree/part/test_part.py index de518bb81a84..6876686941cf 100644 --- a/src/backend/InvenTree/part/test_part.py +++ b/src/backend/InvenTree/part/test_part.py @@ -105,6 +105,39 @@ def test_render_date(self): self.assertEqual(inventree_extras.render_date('aaaa'), None) self.assertEqual(inventree_extras.render_date(1234), 1234) + def test_setting_object(self): + """Test the 'setting_object' template tag.""" + # Normal + self.assertEqual( + inventree_extras.setting_object('PART_ALLOW_DUPLICATE_IPN').value, True + ) + + # User + self.assertEqual( + inventree_extras.setting_object( + 'PART_ALLOW_DUPLICATE_IPN', user=self.user + ).value, + '', + ) + + def test_settings_value(self): + """Test the 'settings_value' template tag.""" + # Normal + self.assertEqual( + inventree_extras.settings_value('PART_ALLOW_DUPLICATE_IPN'), True + ) + + # User + self.assertEqual( + inventree_extras.settings_value('PART_ALLOW_DUPLICATE_IPN', user=self.user), + '', + ) + self.logout() + self.assertEqual( + inventree_extras.settings_value('PART_ALLOW_DUPLICATE_IPN', user=self.user), + '', + ) + class PartTest(TestCase): """Tests for the Part model.""" From 49ff933e4cfba5189d44cecdf5a61296521debdc Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Fri, 31 Jan 2025 01:40:01 +0100 Subject: [PATCH 13/14] fix render_date --- src/backend/InvenTree/InvenTree/templatetags/inventree_extras.py | 1 + src/backend/InvenTree/part/test_part.py | 1 + 2 files changed, 2 insertions(+) diff --git a/src/backend/InvenTree/InvenTree/templatetags/inventree_extras.py b/src/backend/InvenTree/InvenTree/templatetags/inventree_extras.py index 22ca714e960a..ca85a755b377 100644 --- a/src/backend/InvenTree/InvenTree/templatetags/inventree_extras.py +++ b/src/backend/InvenTree/InvenTree/templatetags/inventree_extras.py @@ -38,6 +38,7 @@ def decimal(x, *args, **kwargs): return InvenTree.helpers.decimal2string(x) +@register.simple_tag(takes_context=True) def render_date(date_object): """Renders a date object as a string.""" if date_object is None: diff --git a/src/backend/InvenTree/part/test_part.py b/src/backend/InvenTree/part/test_part.py index 6876686941cf..3a12c5f0701b 100644 --- a/src/backend/InvenTree/part/test_part.py +++ b/src/backend/InvenTree/part/test_part.py @@ -101,6 +101,7 @@ def test_render_date(self): """Test the 'render_date' template tag.""" self.assertEqual(inventree_extras.render_date('2021-01-01'), '2021-01-01') + self.assertEqual(inventree_extras.render_date(None), None) self.assertEqual(inventree_extras.render_date(' '), None) self.assertEqual(inventree_extras.render_date('aaaa'), None) self.assertEqual(inventree_extras.render_date(1234), 1234) From 5193e262bc73f9acd383ab4c8f48fdaae528b46e Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Fri, 31 Jan 2025 01:45:22 +0100 Subject: [PATCH 14/14] more coverage --- src/backend/InvenTree/part/test_part.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/backend/InvenTree/part/test_part.py b/src/backend/InvenTree/part/test_part.py index 3a12c5f0701b..a57504914360 100644 --- a/src/backend/InvenTree/part/test_part.py +++ b/src/backend/InvenTree/part/test_part.py @@ -121,6 +121,14 @@ def test_setting_object(self): '', ) + # Method + self.assertEqual( + inventree_extras.setting_object( + 'PART_ALLOW_DUPLICATE_IPN', method='abc', user=self.user + ).value, + '', + ) + def test_settings_value(self): """Test the 'settings_value' template tag.""" # Normal