From 0bd86cd2adecc1dd64d8e2a887bec11b0d40f887 Mon Sep 17 00:00:00 2001 From: powderflask Date: Sat, 9 Mar 2024 20:37:06 -0800 Subject: [PATCH] allow SIGNOFFS_SIGNET_DEFAULTS to be a dotted import path for lazy evaluation --- signoffs/core/models/signets.py | 6 ++++-- signoffs/core/tests/test_utils.py | 6 ++++++ signoffs/core/utils.py | 17 +++++++++++++++++ signoffs/settings.py | 3 ++- signoffs/templatetags/signoff_tags.py | 1 + 5 files changed, 30 insertions(+), 3 deletions(-) diff --git a/signoffs/core/models/signets.py b/signoffs/core/models/signets.py index 1b6c4ca..7478a66 100644 --- a/signoffs/core/models/signets.py +++ b/signoffs/core/models/signets.py @@ -20,6 +20,7 @@ from django.utils import timezone from signoffs import settings +from signoffs.core.utils import dynamic_import class SignetQuerySet(models.QuerySet): @@ -218,13 +219,14 @@ def update(self, defaults=False, **attrs): def get_signet_defaults(self): """Return dict of default field values for this signet - signet MUST have user relation!""" defaults = settings.SIGNOFFS_SIGNET_DEFAULTS + defaults = dynamic_import(defaults) if isinstance(defaults, str) else defaults return ( get_signet_defaults(self) if defaults is None else defaults(self) if callable(defaults) - else defaults - ) # otherwise, defaults must be a dict-like object + else defaults # otherwise, defaults must be a dict-like object + ) def set_signet_defaults(self): """Set default field values for this signet - signet MUST have user relation!""" diff --git a/signoffs/core/tests/test_utils.py b/signoffs/core/tests/test_utils.py index ecd14bc..ceed330 100644 --- a/signoffs/core/tests/test_utils.py +++ b/signoffs/core/tests/test_utils.py @@ -33,6 +33,12 @@ def test_id_to_camel(self): self.assertEqual(utils.id_to_camel("snake_snake_case"), "SnakeSnakeCase") self.assertEqual(utils.id_to_camel("dot.separated-id"), "DotSeparatedId") + def test_dynamic_import(self): + f = utils.dynamic_import("signoffs.core.utils.dynamic_import") + self.assertEqual(f, utils.dynamic_import) + f = utils.dynamic_import("signoffs.core.utils", "service") + self.assertEqual(f, utils.service) + data = SimpleNamespace( obj1=SimpleNamespace( diff --git a/signoffs/core/utils.py b/signoffs/core/utils.py index 3e77b56..ab7c751 100644 --- a/signoffs/core/utils.py +++ b/signoffs/core/utils.py @@ -2,6 +2,7 @@ Utility functions and classes """ import re +from importlib import import_module from django.core.exceptions import FieldDoesNotExist @@ -22,6 +23,21 @@ def id_to_camel(name): return "".join(el[:1].capitalize() + el[1:] for el in re.split(id_separators, name)) +def dynamic_import(abs_module_path, obj_name=None): + """ + Dynamically import the given object (class, function, constant, etc.) from the given module_path + If obj_name is None, the last dotted item in the module_path will be imported + """ + if not obj_name: + abs_module_path, obj_name = abs_module_path.rsplit(".", 1) + + module_object = import_module(abs_module_path) + + target_obj = getattr(module_object, obj_name) + + return target_obj + + class Accessor(str): """ A string describing a path from one object to another via attributes accesses. @@ -263,6 +279,7 @@ def class_service(service_class, **kwargs): __all__ = [ + "dynamic_import", "service", "ServiceDescriptor", "class_service", diff --git a/signoffs/settings.py b/signoffs/settings.py index e9b2aa8..c6a9d32 100644 --- a/signoffs/settings.py +++ b/signoffs/settings.py @@ -8,7 +8,8 @@ settings, "SIGNOFFS_AUTODISCOVER_MODULE", "signoffs" ) -# dictionary or callable(signet) that returns default values for mutable signet fields. +# dictionary, or callable(signet) that returns one, or a string with dotted import path to either +# A dict that provides default values for mutable signet fields. SIGNOFFS_SIGNET_DEFAULTS = getattr(settings, "SIGNOFFS_SIGNET_DEFAULTS", None) # SIGNOFFS_SETTING = getattr(settings, 'SIGNOFFS_SETTING', 'DEFAULT') diff --git a/signoffs/templatetags/signoff_tags.py b/signoffs/templatetags/signoff_tags.py index 937266a..e67cdc8 100644 --- a/signoffs/templatetags/signoff_tags.py +++ b/signoffs/templatetags/signoff_tags.py @@ -11,6 +11,7 @@ def can_sign(signoff_or_approval_instance, user): """Returns True if the signoff_or_approval_instance can be signed by the given user (in theory)""" return signoff_or_approval_instance.can_sign(user) + @register.filter def can_revoke(signoff_or_approval_instance, user): """Returns True if the signoff_or_approval_instance can be revoked by the given user (in theory)"""