Merge branch 'master' into ptbr-translations

This commit is contained in:
Jelmer 2024-03-21 08:36:00 +01:00 committed by GitHub
commit f39c975e8b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 68 additions and 139 deletions

View file

@ -46,6 +46,7 @@
| Jannis Leidel <jannis@leidel.info>
| Javier Garcia Sogo <jgsogo@gmail.com>
| Jeff Elmore <jelmore@lexile.com>
| Joe Riddle <joeriddles10@gmail.com>
| John Vandenberg <jayvdb@gmail.com>
| Jonathan Sundqvist <sundqvist.jonathan@gmail.com>
| João Amaro <joaoamaro70@gmail.com>
@ -104,3 +105,4 @@
| Éric Araujo <merwok@netwok.org>
| Őry Máté <ory.mate@cloud.bme.hu>
| Nafees Anwar <h.nafees.anwar@gmail.com>
| meanmail <github@meanmail.dev>

View file

@ -2,15 +2,23 @@ Changelog
=========
To be released
--------
--------------
- Add Brazilian Portuguese translation (GH-#578)
- Don't use `post_init` signal for initialize tracker
4.4.0 (2024-02-10)
------------------
- Confirm support for `Django 4.2`
- Add support for `Python 3.11` (GH-#545)
- Add support for `Python 3.12` (GH-#545)
- Drop support for `Python 3.7` (GH-#545)
- Swedish translation (GH-#561)
- Add support for `Django 4.2`
- Add support for `Django 5.0`
- Remove ``SaveSignalHandlingModel``. This model used a modified copy of the internal Django method `Model.save_base()`
and had not been updated for upstream bug fixes changes since its addition.
- Add Swedish translation
- Use proper column name instead of attname (GH-#573)
- Add Brazilian Portuguese translation (GH-#578)
- Fix ValueError when calling prefetch_related for tracked ForeignKey fields
4.3.1 (2022-11-15)
------------------

View file

@ -89,8 +89,8 @@ manager ``available_objects`` are limited to not-deleted instances.
Note that relying on the default ``objects`` manager to filter out not-deleted
instances is deprecated. ``objects`` will include deleted objects in a future
release.
release. Until then, the recommended course of action is to use the manager
``all_objects`` when you want to include all instances.
UUIDModel
------------------
@ -112,21 +112,3 @@ Also you can override the default uuid version. Versions 1,3,4 and 5 are now sup
.. _`UUIDField`: https://github.com/jazzband/django-model-utils/blob/master/docs/fields.rst#uuidfield
SaveSignalHandlingModel
-----------------------
An abstract base class model to pass a parameter ``signals_to_disable``
to ``save`` method in order to disable signals
.. code-block:: python
from model_utils.models import SaveSignalHandlingModel
class SaveSignalTestModel(SaveSignalHandlingModel):
name = models.CharField(max_length=20)
obj = SaveSignalTestModel(name='Test')
# Note: If you use `Model.objects.create`, the signals can't be disabled
obj.save(signals_to_disable=['pre_save'] # disable `pre_save` signal

View file

@ -328,9 +328,9 @@ FieldTracker implementation details
This is how ``FieldTracker`` tracks field changes on ``instance.save`` call.
1. In ``class_prepared`` handler ``FieldTracker`` patches ``save_base`` and
``refresh_from_db`` methods to reset initial state for tracked fields.
2. In ``post_init`` handler ``FieldTracker`` saves initial values for tracked
1. In ``class_prepared`` handler ``FieldTracker`` patches ``save_base``,
``refresh_from_db`` and ``__init__`` methods to reset initial state for tracked fields.
2. In the patched ``__init__`` method ``FieldTracker`` saves initial values for tracked
fields.
3. ``MyModel.save`` changes ``update_fields`` in order to store auto updated
``modified`` timestamp. Complete list of saved fields is now known.

View file

@ -1,7 +1,6 @@
from django.core.exceptions import ImproperlyConfigured
from django.db import models, router, transaction
from django.db import models
from django.db.models.functions import Now
from django.db.models.signals import post_save, pre_save
from django.utils.translation import gettext_lazy as _
from model_utils.fields import (
@ -172,60 +171,3 @@ class UUIDModel(models.Model):
class Meta:
abstract = True
class SaveSignalHandlingModel(models.Model):
"""
An abstract base class model to pass a parameter ``signals_to_disable``
to ``save`` method in order to disable signals
"""
class Meta:
abstract = True
def save(self, signals_to_disable=None, *args, **kwargs):
"""
Add an extra parameters to hold which signals to disable
If empty, nothing will change
"""
self.signals_to_disable = signals_to_disable or []
super().save(*args, **kwargs)
def save_base(self, raw=False, force_insert=False,
force_update=False, using=None, update_fields=None):
"""
Copied from base class for a minor change.
This is an ugly overwriting but since Django's ``save_base`` method
does not differ between versions 1.8 and 1.10,
that way of implementing wouldn't harm the flow
"""
using = using or router.db_for_write(self.__class__, instance=self)
assert not (force_insert and (force_update or update_fields))
assert update_fields is None or len(update_fields) > 0
cls = origin = self.__class__
if cls._meta.proxy:
cls = cls._meta.concrete_model
meta = cls._meta
if not meta.auto_created and 'pre_save' not in self.signals_to_disable:
pre_save.send(
sender=origin, instance=self, raw=raw, using=using,
update_fields=update_fields,
)
with transaction.atomic(using=using, savepoint=False):
if not raw:
self._save_parents(cls, using, update_fields)
updated = self._save_table(raw, cls, force_insert, force_update, using, update_fields)
self._state.db = using
self._state.adding = False
if not meta.auto_created and 'post_save' not in self.signals_to_disable:
post_save.send(
sender=origin, instance=self, created=(not updated),
update_fields=update_fields, raw=raw, using=using,
)
# Empty the signals in case it might be used somewhere else in future
self.signals_to_disable = []

View file

@ -103,6 +103,9 @@ class DescriptorWrapper:
else:
instance.__dict__[self.field_name] = value
def __getattr__(self, attr):
return getattr(self.descriptor, attr)
@staticmethod
def cls_for_descriptor(descriptor):
if hasattr(descriptor, '__delete__'):
@ -340,7 +343,7 @@ class FieldTracker:
wrapped_descriptor = wrapper_cls(field_name, descriptor, self.attname)
setattr(sender, field_name, wrapped_descriptor)
self.field_map = self.get_field_map(sender)
models.signals.post_init.connect(self.initialize_tracker)
self.patch_init(sender)
self.model_class = sender
setattr(sender, self.name, self)
self.patch_save(sender)
@ -353,6 +356,16 @@ class FieldTracker:
tracker.set_saved_fields()
instance._instance_initialized = True
def patch_init(self, model):
original = getattr(model, '__init__')
@wraps(original)
def inner(instance, *args, **kwargs):
original(instance, *args, **kwargs)
self.initialize_tracker(model, instance)
setattr(model, '__init__', inner)
def patch_save(self, model):
self._patch(model, 'save_base', 'update_fields')
self._patch(model, 'refresh_from_db', 'fields')

View file

@ -1,4 +1,4 @@
pytest==6.2.5
pytest-django==3.10.0
psycopg2-binary==2.9.5
pytest-cov==2.10.1
pytest==7.4.3
pytest-django==4.5.2
psycopg2-binary==2.9.9
pytest-cov==4.1.0

View file

@ -49,6 +49,7 @@ setup(
'Framework :: Django :: 4.0',
'Framework :: Django :: 4.1',
'Framework :: Django :: 4.2',
'Framework :: Django :: 5.0',
],
zip_safe=False,
package_data={

View file

@ -7,7 +7,6 @@ from model_utils import Choices
from model_utils.fields import MonitorField, SplitField, StatusField, UUIDField
from model_utils.managers import InheritanceManager, JoinManagerMixin, QueryManager
from model_utils.models import (
SaveSignalHandlingModel,
SoftDeletableModel,
StatusModel,
TimeFramedModel,
@ -446,10 +445,6 @@ class CustomNotPrimaryUUIDModel(models.Model):
uuid = UUIDField(primary_key=False)
class SaveSignalHandlingTestModel(SaveSignalHandlingModel):
name = models.CharField(max_length=20)
class TimeStampWithStatusModel(TimeStampedModel, StatusModel):
STATUS = Choices(
("active", _("active")),

View file

@ -538,6 +538,29 @@ class FieldTrackerForeignKeyTests(FieldTrackerTestCase):
self.assertCurrent(fk=self.instance.fk_id)
class FieldTrackerForeignKeyPrefetchRelatedTests(FieldTrackerTestCase):
"""Test that using `prefetch_related` on a tracked field does not raise a ValueError."""
fk_class = Tracked
tracked_class = TrackedFK
def setUp(self):
model_tracked = self.fk_class.objects.create(name="", number=0)
self.instance = self.tracked_class.objects.create(fk=model_tracked)
def test_default(self):
self.tracker = self.instance.tracker
self.assertIsNotNone(list(self.tracked_class.objects.prefetch_related("fk")))
def test_custom(self):
self.tracker = self.instance.custom_tracker
self.assertIsNotNone(list(self.tracked_class.objects.prefetch_related("fk")))
def test_custom_without_id(self):
self.tracker = self.instance.custom_tracker_without_id
self.assertIsNotNone(list(self.tracked_class.objects.prefetch_related("fk")))
class FieldTrackerTimeStampedTests(FieldTrackerTestCase):
fk_class = Tracked

View file

@ -1,42 +0,0 @@
from django.db.models.signals import post_save, pre_save
from django.test import TestCase
from tests.models import SaveSignalHandlingTestModel
from tests.signals import post_save_test, pre_save_test
class SaveSignalHandlingModelTests(TestCase):
def test_pre_save(self):
pre_save.connect(pre_save_test, sender=SaveSignalHandlingTestModel)
obj = SaveSignalHandlingTestModel.objects.create(name='Test')
delattr(obj, 'pre_save_runned')
obj.name = 'Test A'
obj.save()
self.assertEqual(obj.name, 'Test A')
self.assertTrue(hasattr(obj, 'pre_save_runned'))
obj = SaveSignalHandlingTestModel.objects.create(name='Test')
delattr(obj, 'pre_save_runned')
obj.name = 'Test B'
obj.save(signals_to_disable=['pre_save'])
self.assertEqual(obj.name, 'Test B')
self.assertFalse(hasattr(obj, 'pre_save_runned'))
def test_post_save(self):
post_save.connect(post_save_test, sender=SaveSignalHandlingTestModel)
obj = SaveSignalHandlingTestModel.objects.create(name='Test')
delattr(obj, 'post_save_runned')
obj.name = 'Test A'
obj.save()
self.assertEqual(obj.name, 'Test A')
self.assertTrue(hasattr(obj, 'post_save_runned'))
obj = SaveSignalHandlingTestModel.objects.create(name='Test')
delattr(obj, 'post_save_runned')
obj.name = 'Test B'
obj.save(signals_to_disable=['post_save'])
self.assertEqual(obj.name, 'Test B')
self.assertFalse(hasattr(obj, 'post_save_runned'))

View file

@ -1,7 +1,11 @@
[tox]
envlist =
py{37,38,39,310}-dj32
py{38,39,310,311,312}-dj{40,41,42,main}
py{38,39,310}-dj{40}
py{38,39,310,311}-dj{41}
py{38,39,310,311}-dj{42}
py{310,311,312}-dj{50}
py{310,311,312}-dj{main}
flake8
isort
@ -22,6 +26,7 @@ deps =
dj40: Django==4.0.*
dj41: Django==4.1.*
dj42: Django==4.2.*
dj50: Django==5.0.*
djmain: https://github.com/django/django/archive/main.tar.gz
ignore_outcome =
djmain: True