From d49d6b8f66feb07b4e184c79c5f6a3f68d8419da Mon Sep 17 00:00:00 2001 From: Todd Wolfson Date: Mon, 31 Oct 2022 16:02:46 -0700 Subject: [PATCH 1/4] Documented how to automatically update a timestamp via bulk_update --- docs/common_issues.rst | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/docs/common_issues.rst b/docs/common_issues.rst index 3537b5061..669eab9bc 100644 --- a/docs/common_issues.rst +++ b/docs/common_issues.rst @@ -275,3 +275,33 @@ arguments using ``excluded_field_kwargs`` as follows: history = HistoricalRecords( excluded_field_kwargs={"organizer": set(["custom_argument"])} ) + + +Updated auto_now inside a bulk_update_with_history +-------------------------------------------------- + +If you are tracking update timestamps (e.g. ``updated_at``, ``modified_at``), these are only tracked when +``Model.save()`` is called. As a result, ``bulk_update_with_history`` won't update them. + +As a convention, you can define a custom manager to update these timestamps automatically: + +.. code-block:: python + + from django.utils import timezone + + class UpdatedAtFriendlyQuerySet(models.QuerySet): + def bulk_update(self, objs, fields, *args, **kwargs): + timestamp = timezone.now() + for obj in objs: + assert hasattr(obj, "updated_at"), "Expected bulk_update object to have `updated_at`" + obj.updated_at = timestamp + assert "updated_at" not in fields, "Encountered unexpected scenario where we set `updated_at` via `bulk_update`" + new_fields = tuple(fields) + ("updated_at",) + super().bulk_update(objs, new_fields, *args, **kwargs) + + class UpdatedAtFriendlyManager(models.manager.BaseManager.from_queryset(UpdatedAtFriendlyQuerySet)): + pass + + class Poll(models.Model): + # This must be the first Manager declared in this class, otherwise it won't be detected as the `default_manager` + objects = UpdatedAtFriendlyManager() From 855f3af0793f48812f824551f67f0cd10f794eea Mon Sep 17 00:00:00 2001 From: Todd Wolfson Date: Mon, 31 Oct 2022 16:02:53 -0700 Subject: [PATCH 2/4] Added missing example fields --- docs/common_issues.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/common_issues.rst b/docs/common_issues.rst index 669eab9bc..13b9ee6b9 100644 --- a/docs/common_issues.rst +++ b/docs/common_issues.rst @@ -303,5 +303,8 @@ As a convention, you can define a custom manager to update these timestamps auto pass class Poll(models.Model): + created_at = models.DateTimeField(auto_now_add=True) + updated_at = models.DateTimeField(auto_now=True) + # This must be the first Manager declared in this class, otherwise it won't be detected as the `default_manager` objects = UpdatedAtFriendlyManager() From b3749f469e43eb4192ff1011c00ddc89e5588e54 Mon Sep 17 00:00:00 2001 From: Todd Wolfson Date: Mon, 31 Oct 2022 16:15:02 -0700 Subject: [PATCH 3/4] Added to CHANGES.rst --- CHANGES.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES.rst b/CHANGES.rst index d4e51f023..3014fa39a 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -12,6 +12,7 @@ Unreleased - Added ``tracked_fields`` attribute to historical models (gh-1038) - Fixed ``KeyError`` when running ``clean_duplicate_history`` on models with ``excluded_fields`` (gh-1038) - Added support for Python 3.11 (gh-1053) +- Documented ``auto_now`` solution for ``bulk_update_with_history`` (gh-1054) 3.1.1 (2022-04-23) ------------------ From dd38fb0ae7734149fd0df8b8d815926e746d1330 Mon Sep 17 00:00:00 2001 From: Todd Wolfson Date: Tue, 1 Nov 2022 15:26:24 -0700 Subject: [PATCH 4/4] Corrected improper models.Manager reference --- docs/common_issues.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/common_issues.rst b/docs/common_issues.rst index 13b9ee6b9..eb3c2545f 100644 --- a/docs/common_issues.rst +++ b/docs/common_issues.rst @@ -299,7 +299,7 @@ As a convention, you can define a custom manager to update these timestamps auto new_fields = tuple(fields) + ("updated_at",) super().bulk_update(objs, new_fields, *args, **kwargs) - class UpdatedAtFriendlyManager(models.manager.BaseManager.from_queryset(UpdatedAtFriendlyQuerySet)): + class UpdatedAtFriendlyManager(models.Manager.from_queryset(UpdatedAtFriendlyQuerySet)): pass class Poll(models.Model):