From a9a87ca5d5a8306c4988e399732b5c353516d12d Mon Sep 17 00:00:00 2001 From: SKairinos Date: Fri, 10 Nov 2023 19:02:28 +0000 Subject: [PATCH] models helpers --- codeforlife/models/__init__.py | 4 ++ codeforlife/models/signals.py | 69 ------------------------- codeforlife/models/signals/__init__.py | 16 ++++++ codeforlife/models/signals/post_save.py | 21 ++++++++ codeforlife/models/signals/pre_save.py | 66 +++++++++++++++++++++++ 5 files changed, 107 insertions(+), 69 deletions(-) delete mode 100644 codeforlife/models/signals.py create mode 100644 codeforlife/models/signals/__init__.py create mode 100644 codeforlife/models/signals/post_save.py create mode 100644 codeforlife/models/signals/pre_save.py diff --git a/codeforlife/models/__init__.py b/codeforlife/models/__init__.py index 551957ba..9322030d 100644 --- a/codeforlife/models/__init__.py +++ b/codeforlife/models/__init__.py @@ -1,3 +1,7 @@ +"""Helpers for module "django.db.models". +https://docs.djangoproject.com/en/3.2/ref/models/ +""" + import typing as t from django.db.models import Model as _Model diff --git a/codeforlife/models/signals.py b/codeforlife/models/signals.py deleted file mode 100644 index a0e0b8d9..00000000 --- a/codeforlife/models/signals.py +++ /dev/null @@ -1,69 +0,0 @@ -import typing as t - -from . import AnyModel - -UpdateFields = t.Optional[t.FrozenSet[str]] - - -def check_post_save( - update_fields: UpdateFields, - expected_update_fields: UpdateFields, -): - """https://docs.djangoproject.com/en/3.2/ref/signals/#post-save - - Check if a model was updated with the expected fields. - - Args: - update_fields: The fields that were updated. - expected_update_fields: A subset of the fields that were expected to be - updated. If no fields were expected to be updated, set to None. - - Returns: - If the model instance was updated as expected. - """ - - if expected_update_fields is None: - return update_fields is None - elif update_fields is None: - return False - - return all( - update_field in update_fields for update_field in expected_update_fields - ) - - -def check_pre_save( - update_fields: UpdateFields = None, - expected_update_fields: UpdateFields = None, - instance: t.Optional[AnyModel] = None, - created: bool = False, - created_only: bool = False, -): - """https://docs.djangoproject.com/en/3.2/ref/signals/#pre-save - - Check if a model was created or updated with the expected fields. - - Args: - update_fields: The fields that were updated. - expected_update_fields: A subset of the fields that were expected to be - updated. If no fields were expected to be updated, set to None. - instance: Any model instance. - created: Check if the model was created. - created_only: Only check if the model was created. - - Raises: - ValueError: If arg 'created' is True and arg 'instance' is None. - - Returns: - If the model instance was created or updated as expected. - """ - - if created: - if instance is None: - raise ValueError("Arg 'instance' cannot be None.") - if created_only: - return instance.pk is None - if instance.pk is None: - return True - - return check_post_save(update_fields, expected_update_fields) diff --git a/codeforlife/models/signals/__init__.py b/codeforlife/models/signals/__init__.py new file mode 100644 index 00000000..1be564c9 --- /dev/null +++ b/codeforlife/models/signals/__init__.py @@ -0,0 +1,16 @@ +"""Helpers for module "django.db.models.signals". +https://docs.djangoproject.com/en/3.2/ref/signals/#module-django.db.models.signals +""" + +import typing as t + +UpdateFields = t.Optional[t.FrozenSet[str]] + + +def _has_update_fields(actual: UpdateFields, expected: UpdateFields): + if expected is None: + return actual is None + if actual is None: + return False + + return all(update_field in actual for update_field in expected) diff --git a/codeforlife/models/signals/post_save.py b/codeforlife/models/signals/post_save.py new file mode 100644 index 00000000..df7989ca --- /dev/null +++ b/codeforlife/models/signals/post_save.py @@ -0,0 +1,21 @@ +"""Helpers for module "django.db.models.signals.post_save". +https://docs.djangoproject.com/en/3.2/ref/signals/#post-save +""" + +from . import UpdateFields, _has_update_fields + + +def has_update_fields(actual: UpdateFields, expected: UpdateFields): + """Check if the expected fields were updated. + + Args: + actual: The fields that were updated. + expected: A subset of the fields that were expected to be updated. If no + fields were expected to be updated, set to None. + + Returns: + If the fields that were expected to be updated are a subset of the + fields that were updated. + """ + + return _has_update_fields(actual, expected) diff --git a/codeforlife/models/signals/pre_save.py b/codeforlife/models/signals/pre_save.py new file mode 100644 index 00000000..75d43a9d --- /dev/null +++ b/codeforlife/models/signals/pre_save.py @@ -0,0 +1,66 @@ +"""Helpers for module "django.db.models.signals.pre_save". +https://docs.djangoproject.com/en/3.2/ref/signals/#pre-save +""" + +import typing as t + +from .. import AnyModel +from . import UpdateFields, _has_update_fields + + +def was_created(instance: AnyModel): + """Check if the instance was created. + + Args: + instance: The current instance. + + Returns: + If the instance was created. + """ + + return instance.pk is not None + + +def has_update_fields(actual: UpdateFields, expected: UpdateFields): + """Check if the expected fields are going to be updated. + + Args: + actual: The fields that are going to be updated. + expected: A subset of the fields that are expected to be updated. If no + fields are expected to be updated, set to None. + + Returns: + If the fields that are expected to be updated are a subset of the + fields that are going to be updated. + """ + + return _has_update_fields(actual, expected) + + +def has_previous_values( + instance: AnyModel, + predicates: t.Dict[str, t.Callable[[t.Any, t.Any], bool]], +): + """Check if the previous values are as expected. + + Args: + instance: The current instance. + predicates: A predicate for each field. It accepts the arguments + (previous_value, value) and returns True if the values are as expected. + + Raises: + ValueError: If arg 'instance' has not been created yet. + + Returns: + If all the previous values are as expected. + """ + + if not was_created(instance): + raise ValueError("Arg 'instance' has not been created yet.") + + previous_instance = instance.__class__.objects.get(pk=instance.pk) + + return all( + predicate(previous_instance[field], instance[field]) + for field, predicate in predicates.items() + )