From 5aa40c66be794e890626388d93c45175d679a8ca Mon Sep 17 00:00:00 2001 From: Misha K Date: Mon, 9 Dec 2019 14:37:16 +0100 Subject: [PATCH] Add django 3.0 to the test matrix and drop six (#395) * - add django 3.0 to the test matrix - drop six * add entry in CHANGES * remove context kwarg * fix test with DeferredAttribute * rename StringyDescriptor's name to attname * Fix flake8 * Drop support for Django 1.11 because the API are not compatibles anymore with Django 3.0 * Try to fix tests. * Define model for the field mock. * Simplifies the code. * Properly mock the field. * Typo * Use the new API field name. * Call it attname * Grab the field instance from the model. * Use postgres in travis tests. * Django 2.0.1 minimum is needed. * Update Changelog to tell about breaking Django 1.11. * Update changelog to tell about Django 3.0 support. * @natim review. --- CHANGES.rst | 117 +++++++++--------- README.rst | 2 +- model_utils/__init__.py | 2 +- model_utils/fields.py | 1 - model_utils/managers.py | 24 ++-- model_utils/tracker.py | 2 +- setup.py | 6 +- tests/fields.py | 3 +- tests/models.py | 10 +- tests/test_fields/test_field_tracker.py | 1 - .../test_managers/test_inheritance_manager.py | 4 - tests/test_models/test_deferred_fields.py | 1 - tests/test_models/test_timestamped_model.py | 1 - tox.ini | 12 +- 14 files changed, 89 insertions(+), 97 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index fa854386..5a2486fa 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,11 +1,14 @@ CHANGES ======= -3.4.0 (unreleased) +4.0.0 (unreleased) ------------------ - Remove hacks for previously supported Django versions. (Fixes GH-390) - Dropped support for Python 2.7. (Fixes GH-393) +- Dropped usage of `six` +- Drop support for `Django 1.11` +- Add support for `Django 3.0` 3.3.0 (2019.08.19) ------------------ @@ -32,10 +35,10 @@ CHANGES 3.1.2 (2018.05.09) ------------------ -* Update InheritanceIterable to inherit from +- Update InheritanceIterable to inherit from ModelIterable instead of BaseIterable, fixes GH-277. -* Add all_objects Manager for 'SoftDeletableModel' to include soft +- Add all_objects Manager for 'SoftDeletableModel' to include soft deleted objects on queries as per issue GH-255 3.1.1 (2017.12.17) @@ -53,33 +56,33 @@ CHANGES 3.0.0 (2017.04.13) ------------------ -* Drop support for Python 2.6. -* Drop support for Django 1.4, 1.5, 1.6, 1.7. -* Exclude tests from the distribution, fixes GH-258. -* Add support for Django 1.11 GH-269 -* Add a new model to disable pre_save/post_save signals +- Drop support for Python 2.6. +- Drop support for Django 1.4, 1.5, 1.6, 1.7. +- Exclude tests from the distribution, fixes GH-258. +- Add support for Django 1.11 GH-269 +- Add a new model to disable pre_save/post_save signals 2.6.1 (2017.01.11) ------------------ -* Fix infinite recursion with multiple `MonitorField` and `defer()` or `only()` +- Fix infinite recursion with multiple `MonitorField` and `defer()` or `only()` on Django 1.10+. Thanks Romain Garrigues. Merge of GH-242, fixes GH-241. -* Fix `InheritanceManager` and `SoftDeletableManager` to respect +- Fix `InheritanceManager` and `SoftDeletableManager` to respect `self._queryset_class` instead of hardcoding the queryset class. Merge of GH-250, fixes GH-249. -* Add mixins for `SoftDeletableQuerySet` and `SoftDeletableManager`, as stated +- Add mixins for `SoftDeletableQuerySet` and `SoftDeletableManager`, as stated in the the documentation. -* Fix `SoftDeletableModel.delete()` to use the correct database connection. +- Fix `SoftDeletableModel.delete()` to use the correct database connection. Merge of GH-239. -* Added boolean keyword argument `soft` to `SoftDeletableModel.delete()` that +- Added boolean keyword argument `soft` to `SoftDeletableModel.delete()` that revert to default behavior when set to `False`. Merge of GH-240. -* Enforced default manager in `StatusModel` to avoid manager order issues when +- Enforced default manager in `StatusModel` to avoid manager order issues when using abstract models that redefine `objects` manager. Merge of GH-253, fixes GH-251. @@ -87,75 +90,75 @@ CHANGES 2.6 (2016.09.19) ---------------- -* Added `SoftDeletableModel` abstract class, its manageer +- Added `SoftDeletableModel` abstract class, its manageer `SoftDeletableManager` and queryset `SoftDeletableQuerySet`. -* Fix issue with field tracker and deferred FileField for Django 1.10. +- Fix issue with field tracker and deferred FileField for Django 1.10. 2.5.2 (2016.08.09) ------------------ -* Include `runtests.py` in sdist. +- Include `runtests.py` in sdist. 2.5.1 (2016.08.03) ------------------ -* Fix `InheritanceQuerySet` raising an `AttributeError` exception +- Fix `InheritanceQuerySet` raising an `AttributeError` exception under Django 1.9. -* Django 1.10 support regressed with changes between pre-alpha and final +- Django 1.10 support regressed with changes between pre-alpha and final release; 1.10 currently not supported. 2.5 (2016.04.18) ---------------- -* Drop support for Python 3.2. +- Drop support for Python 3.2. -* Add support for Django 1.10 pre-alpha. +- Add support for Django 1.10 pre-alpha. -* Track foreign keys on parent models properly when a tracker +- Track foreign keys on parent models properly when a tracker is defined on a child model. Fixes GH-214. 2.4 (2015.12.03) ---------------- -* Remove `PassThroughManager`. Use Django's built-in `QuerySet.as_manager()` +- Remove `PassThroughManager`. Use Django's built-in `QuerySet.as_manager()` and/or `Manager.from_queryset()` utilities instead. -* Add support for Django 1.9. +- Add support for Django 1.9. 2.3.1 (2015-07-20) ------------------ -* Remove all translation-related automation in `setup.py`. Fixes GH-178 and +- Remove all translation-related automation in `setup.py`. Fixes GH-178 and GH-179. Thanks Joe Weiss, Matt Molyneaux, and others for the reports. 2.3 (2015.07.17) ---------------- -* Keep track of deferred fields on model instance instead of on +- Keep track of deferred fields on model instance instead of on FieldInstanceTracker instance. Fixes accessing deferred fields for multiple instances of a model from the same queryset. Thanks Bram Boogaard. Merge of GH-151. -* Fix Django 1.7 migrations compatibility for SplitField. Thanks ad-m. Merge of +- Fix Django 1.7 migrations compatibility for SplitField. Thanks ad-m. Merge of GH-157; fixes GH-156. -* Add German translations. +- Add German translations. -* Django 1.8 compatibility. +- Django 1.8 compatibility. 2.2 (2014.07.31) ---------------- -* Revert GH-130, restoring ability to access ``FieldTracker`` changes in +- Revert GH-130, restoring ability to access ``FieldTracker`` changes in overridden ``save`` methods or ``post_save`` handlers. This reopens GH-83 (inability to pickle models with ``FieldTracker``) until a solution can be found that doesn't break behavior otherwise. Thanks Brian May for the @@ -165,7 +168,7 @@ CHANGES 2.1.1 (2014.07.28) ------------------ -* ASCII-fold all non-ASCII characters in changelog; again. Argh. Apologies to +- ASCII-fold all non-ASCII characters in changelog; again. Argh. Apologies to those whose names are mangled by this change. It seems that distutils makes it impossible to handle non-ASCII content reliably under Python 3 in a setup.py long_description, when the system encoding may be ASCII. Thanks @@ -175,14 +178,14 @@ CHANGES 2.1.0 (2014.07.25) ------------------ -* Add support for Django's built-in migrations to ``MonitorField`` and +- Add support for Django's built-in migrations to ``MonitorField`` and ``StatusField``. -* ``PassThroughManager`` now has support for seeing exposed methods via +- ``PassThroughManager`` now has support for seeing exposed methods via ``dir``, allowing `IPython`_ tab completion to be useful. Merge of GH-104, fixes GH-55. -* Add pickle support for models using ``FieldTracker``. Thanks Ondrej Slintak +- Add pickle support for models using ``FieldTracker``. Thanks Ondrej Slintak for the report. Thanks Matthew Schinckel for the fix. Merge of GH-130, fixes GH-83. @@ -192,21 +195,21 @@ CHANGES 2.0.3 (2014.03.19) ------------------- -* Fix ``get_query_set`` vs ``get_queryset`` in ``PassThroughManager`` for +- Fix ``get_query_set`` vs ``get_queryset`` in ``PassThroughManager`` for Django <1.6. Fixes issues with related managers not filtering by relation properly. Thanks whop, Bojan Mihelac, Daniel Shapiro, and Matthew Schinckel for the report; Matthew for the fix. Merge of GH-121. -* Fix ``FieldTracker`` with deferred model attributes. Thanks Michael van +- Fix ``FieldTracker`` with deferred model attributes. Thanks Michael van Tellingen. Merge of GH-115. -* Fix ``InheritanceManager`` with self-referential FK; avoid infinite +- Fix ``InheritanceManager`` with self-referential FK; avoid infinite recursion. Thanks rsenkbeil. Merge of GH-114. 2.0.2 (2014.02.19) ------------------- -* ASCII-fold all non-ASCII characters in changelog. Apologies to those whose +- ASCII-fold all non-ASCII characters in changelog. Apologies to those whose names are mangled by this change. It seems that distutils makes it impossible to handle non-ASCII content reliably under Python 3 in a setup.py long_description, when the system encoding may be ASCII. Thanks Simone Dalla @@ -216,17 +219,17 @@ CHANGES 2.0.1 (2014.02.11) ------------------- -* Fix dependency to be on "Django" rather than "django", which plays better +- Fix dependency to be on "Django" rather than "django", which plays better with static PyPI mirrors. Thanks Travis Swicegood. -* Fix issue with attempt to access ``__slots__`` when copying +- Fix issue with attempt to access ``__slots__`` when copying ``PassThroughManager``. Thanks Patryk Zawadzki. Merge of GH-105. -* Improve ``InheritanceManager`` so any attributes added by using extra(select) +- Improve ``InheritanceManager`` so any attributes added by using extra(select) will be propagated onto children. Thanks Curtis Maloney. Merge of GH-101, fixes GH-34. -* Added ``InheritanceManagerMixin``, ``InheritanceQuerySetMixin``, +- Added ``InheritanceManagerMixin``, ``InheritanceQuerySetMixin``, ``PassThroughManagerMixin``, and ``QueryManagerMixin`` to allow composing their functionality with other custom manager/queryset subclasses (e.g. those in GeoDjango). Thanks Douglas Meehan! @@ -235,54 +238,54 @@ CHANGES 2.0 (2014.01.06) ---------------- -* BACKWARDS-INCOMPATIBLE: Indexing into a ``Choices`` instance now translates +- BACKWARDS-INCOMPATIBLE: Indexing into a ``Choices`` instance now translates database representations to human-readable choice names, rather than simply indexing into an array of choice tuples. (Indexing into ``Choices`` was previously not documented.) If you have code that is relying on indexing or slicing ``Choices``, the simplest workaround is to change e.g. ``STATUS[1:]`` to ``list(STATUS)[1:]``. -* Fixed bug with checking for field name conflicts for added query managers on +- Fixed bug with checking for field name conflicts for added query managers on `StatusModel`. -* Can pass `choices_name` to `StatusField` to use a different name for +- Can pass `choices_name` to `StatusField` to use a different name for choices class attribute. ``STATUS`` is used by default. -* Can pass model subclasses, rather than strings, into +- Can pass model subclasses, rather than strings, into `select_subclasses()`. Thanks Keryn Knight. Merge of GH-79. -* Deepcopying a `Choices` instance no longer fails with infinite recursion in +- Deepcopying a `Choices` instance no longer fails with infinite recursion in `getattr`. Thanks Leden. Merge of GH-75. -* `get_subclass()` method is now available on both managers and +- `get_subclass()` method is now available on both managers and querysets. Thanks Travis Swicegood. Merge of GH-82. -* Fix bug in `InheritanceManager` with grandchild classes on Django 1.6+; +- Fix bug in `InheritanceManager` with grandchild classes on Django 1.6+; `select_subclasses('child', 'child__grandchild')` would only ever get to the child class. Thanks Keryn Knight for report and proposed fix. -* MonitorField now accepts a 'when' parameter. It will update only when the field +- MonitorField now accepts a 'when' parameter. It will update only when the field changes to one of the values specified. 1.5.0 (2013.08.29) ------------------ -* `Choices` now accepts option-groupings. Fixes GH-14. +- `Choices` now accepts option-groupings. Fixes GH-14. -* `Choices` can now be added to other `Choices` or to any iterable, and can be +- `Choices` can now be added to other `Choices` or to any iterable, and can be compared for equality with itself. Thanks Tony Aldridge. (Merge of GH-76.) -* `Choices` now `__contains__` its Python identifier values. Thanks Keryn +- `Choices` now `__contains__` its Python identifier values. Thanks Keryn Knight. (Merge of GH-69). -* Fixed a bug causing ``KeyError`` when saving with the parameter +- Fixed a bug causing ``KeyError`` when saving with the parameter ``update_fields`` in which there are untracked fields. Thanks Mikhail Silonov. (Merge of GH-70, fixes GH-71). -* Fixed ``FieldTracker`` usage on inherited models. Fixes GH-57. +- Fixed ``FieldTracker`` usage on inherited models. Fixes GH-57. -* Added mutable field support to ``FieldTracker`` (Merge of GH-73, fixes GH-74) +- Added mutable field support to ``FieldTracker`` (Merge of GH-73, fixes GH-74) 1.4.0 (2013.06.03) @@ -423,4 +426,4 @@ CHANGES 0.3.0 ----- -* Added ``QueryManager`` +- Added ``QueryManager`` diff --git a/README.rst b/README.rst index 490e1706..7a789b11 100644 --- a/README.rst +++ b/README.rst @@ -14,7 +14,7 @@ django-model-utils Django model mixins and utilities. -``django-model-utils`` supports `Django`_ 1.11 and 2.1+. +``django-model-utils`` supports `Django`_ 2.1+ and 3.0+. .. _Django: http://www.djangoproject.com/ diff --git a/model_utils/__init__.py b/model_utils/__init__.py index 9a87de59..5661ac1d 100644 --- a/model_utils/__init__.py +++ b/model_utils/__init__.py @@ -1,4 +1,4 @@ from .choices import Choices # noqa:F401 from .tracker import FieldTracker, ModelTracker # noqa:F401 -__version__ = '3.3.0' +__version__ = '4.0.0' diff --git a/model_utils/fields.py b/model_utils/fields.py index 0f943e5c..a82c64ca 100644 --- a/model_utils/fields.py +++ b/model_utils/fields.py @@ -1,4 +1,3 @@ -import django import uuid from django.db import models from django.conf import settings diff --git a/model_utils/managers.py b/model_utils/managers.py index 34f8012b..105794bc 100644 --- a/model_utils/managers.py +++ b/model_utils/managers.py @@ -1,13 +1,11 @@ import django +from django.core.exceptions import ObjectDoesNotExist +from django.db import connection from django.db import models +from django.db.models.constants import LOOKUP_SEP from django.db.models.fields.related import OneToOneField, OneToOneRel -from django.db.models.query import QuerySet from django.db.models.query import ModelIterable -from django.core.exceptions import ObjectDoesNotExist - -from django.db.models.constants import LOOKUP_SEP - -from django.db import connection +from django.db.models.query import QuerySet from django.db.models.sql.datastructures import Join @@ -185,20 +183,17 @@ def get_subclass(self, *args, **kwargs): class InheritanceQuerySet(InheritanceQuerySetMixin, QuerySet): - pass - - def instance_of(self, *models): """ Fetch only objects that are instances of the provided model(s). """ # If we aren't already selecting the subclasess, we need # to in order to get this to work. - + # How can we tell if we are not selecting subclasses? - + # Is it safe to just apply .select_subclasses(*models)? - + # Due to https://code.djangoproject.com/ticket/16572, we # can't really do this for anything other than children (ie, # no grandchildren+). @@ -207,10 +202,10 @@ def instance_of(self, *models): where_queries.append('(' + ' AND '.join([ '"{}"."{}" IS NOT NULL'.format( model._meta.db_table, - field.attname, # Should this be something else? + field.attname, # Should this be something else? ) for field in model._meta.parents.values() ]) + ')') - + return self.select_subclasses(*models).extra(where=[' OR '.join(where_queries)]) @@ -229,6 +224,7 @@ def get_subclass(self, *args, **kwargs): def instance_of(self, *models): return self.get_queryset().instance_of(*models) + class InheritanceManager(InheritanceManagerMixin, models.Manager): pass diff --git a/model_utils/tracker.py b/model_utils/tracker.py index d5c840ac..5e7bb575 100644 --- a/model_utils/tracker.py +++ b/model_utils/tracker.py @@ -200,7 +200,7 @@ def get_field_map(self, cls): field_map = {field: field for field in self.fields} all_fields = {f.name: f.attname for f in cls._meta.fields} field_map.update(**{k: v for (k, v) in all_fields.items() - if k in field_map}) + if k in field_map}) return field_map def contribute_to_class(self, cls, name): diff --git a/setup.py b/setup.py index c70f0d29..0b518484 100644 --- a/setup.py +++ b/setup.py @@ -33,7 +33,7 @@ def get_version(root_path): maintainer='JazzBand', url='https://github.com/jazzband/django-model-utils/', packages=find_packages(exclude=['tests*']), - install_requires=['Django>=1.11'], + install_requires=['Django>=2.0.1'], classifiers=[ 'Development Status :: 5 - Production/Stable', 'Environment :: Web Environment', @@ -46,12 +46,12 @@ def get_version(root_path): 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', 'Framework :: Django', - 'Framework :: Django :: 1.11', 'Framework :: Django :: 2.1', 'Framework :: Django :: 2.2', + 'Framework :: Django :: 3.0', ], zip_safe=False, - tests_require=['Django>=1.1.11'], + tests_require=['Django>=2.1'], package_data={ 'model_utils': [ 'locale/*/LC_MESSAGES/django.po', 'locale/*/LC_MESSAGES/django.mo' diff --git a/tests/fields.py b/tests/fields.py index 87eb22d0..fa302039 100644 --- a/tests/fields.py +++ b/tests/fields.py @@ -1,4 +1,3 @@ -import django from django.db import models @@ -25,7 +24,7 @@ class MutableField(models.TextField): def to_python(self, value): return mutable_from_db(value) - def from_db_value(self, value, expression, connection, context): + def from_db_value(self, value, expression, connection): return mutable_from_db(value) def get_db_prep_save(self, value, connection): diff --git a/tests/models.py b/tests/models.py index c06d8830..7225cd28 100644 --- a/tests/models.py +++ b/tests/models.py @@ -373,7 +373,6 @@ class StringyDescriptor: """ Descriptor that returns a string version of the underlying integer value. """ - def __init__(self, name): self.name = name @@ -382,10 +381,13 @@ def __get__(self, obj, cls=None): return self if self.name in obj.get_deferred_fields(): # This queries the database, and sets the value on the instance. - if django.VERSION < (2, 1): - DeferredAttribute(field_name=self.name, model=cls).__get__(obj, cls) - else: + if django.VERSION < (3, 0): DeferredAttribute(field_name=self.name).__get__(obj, cls) + else: + # Since Django 3.0, DeferredAttribute wants a field argument. + fields_map = {f.name: f for f in cls._meta.fields} + field = fields_map[self.name] + DeferredAttribute(field=field).__get__(obj, cls) return str(obj.__dict__[self.name]) def __set__(self, obj, value): diff --git a/tests/test_fields/test_field_tracker.py b/tests/test_fields/test_field_tracker.py index 0be34186..0a789f2f 100644 --- a/tests/test_fields/test_field_tracker.py +++ b/tests/test_fields/test_field_tracker.py @@ -1,5 +1,4 @@ from unittest import skip -import django from django.core.exceptions import FieldError from django.test import TestCase from django.core.cache import cache diff --git a/tests/test_managers/test_inheritance_manager.py b/tests/test_managers/test_inheritance_manager.py index d142220d..2481b5c9 100644 --- a/tests/test_managers/test_inheritance_manager.py +++ b/tests/test_managers/test_inheritance_manager.py @@ -1,6 +1,3 @@ -from unittest import skipUnless - -import django from django.db import models from django.test import TestCase @@ -471,7 +468,6 @@ def test_select_subclasses_interaction_with_instance_of(self): self.assertEqual({child3}, set(results)) - class InheritanceManagerRelatedTests(InheritanceManagerTests): def setUp(self): self.related = InheritanceManagerTestRelated.objects.create() diff --git a/tests/test_models/test_deferred_fields.py b/tests/test_models/test_deferred_fields.py index 97801a8f..7a839fab 100644 --- a/tests/test_models/test_deferred_fields.py +++ b/tests/test_models/test_deferred_fields.py @@ -1,4 +1,3 @@ -import django from django.test import TestCase from tests.models import ModelWithCustomDescriptor diff --git a/tests/test_models/test_timestamped_model.py b/tests/test_models/test_timestamped_model.py index 8bd226ee..9c07a678 100644 --- a/tests/test_models/test_timestamped_model.py +++ b/tests/test_models/test_timestamped_model.py @@ -39,7 +39,6 @@ def test_overriding_created_via_object_creation_also_uses_creation_date_for_modi self.assertEqual(t1.created, different_date) self.assertEqual(t1.modified, different_date) - def test_overriding_modified_via_object_creation(self): """ Setting the modified date explicitly should be possible when diff --git a/tox.ini b/tox.ini index 2b024ac7..150b063a 100644 --- a/tox.ini +++ b/tox.ini @@ -1,17 +1,17 @@ [tox] envlist = - py37-django{202,201} - py36-django{111,202,201,trunk} - py38-django{202,201,trunk} + py36-django{202,201,30,trunk} + py37-django{202,201,30} + py38-django{202,201,30,trunk} flake8 [testenv] deps = - django111: Django==1.11.* django202: Django==2.2.* django201: Django==2.1.* + django30: Django==3.0.* djangotrunk: https://github.com/django/django/archive/master.tar.gz - freezegun == 0.3.8 + freezegun == 0.3.12 -rrequirements-test.txt ignore_outcome = djangotrunk: True @@ -26,7 +26,7 @@ commands = [testenv:flake8] basepython = - python3.6 + python3.7 deps = flake8 commands =