diff --git a/.travis.yml b/.travis.yml index a621d1d..9a3175a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,17 +5,18 @@ language: python python: "3.6" env: - - TOXENV=py27-django110 - TOXENV=py27-django111 - - TOXENV=py34-django110 - TOXENV=py34-django111 - - TOXENV=py35-django110 + - TOXENV=py34-django200 - TOXENV=py35-django111 + - TOXENV=py35-django200 - TOXENV=py35-djangomaster - TOXENV=py36-django111 + - TOXENV=py36-django200 - TOXENV=py36-djangomaster # XXX: Use a matrix to build these? - TOXENV=py36-django111-postgres DB=postgres + - TOXENV=py36-django200-postgres DB=postgres - TOXENV=py36-djangomaster-postgres DB=postgres services: @@ -24,17 +25,21 @@ services: matrix: fast_finish: true include: - - python: "3.5" - env: TOXENV=py35-django110 + - python: "2.7" + env: TOXENV=py27-django111 - python: "3.5" env: TOXENV=py35-django111 + - python: "3.5" + env: TOXENV=py35-django200 - python: "3.5" env: TOXENV=py35-djangomaster exclude: - python: "3.6" - env: TOXENV=py35-django110 + env: TOXENV=py27-django111 - python: "3.6" env: TOXENV=py35-django111 + - python: "3.6" + env: TOXENV=py35-django200 - python: "3.6" env: TOXENV=py35-djangomaster allow_failures: diff --git a/README.rst b/README.rst index 5b67d04..3d96553 100644 --- a/README.rst +++ b/README.rst @@ -60,8 +60,8 @@ Django to perform an ``INNER JOIN`` to fetch the model fields from the database. While taking this in mind, there are valid reasons for using subclassed models. That's what this library is designed for! -The current release of *django-polymorphic* supports Django 1.8, 1.10, 1.11 and Python 2.7 and 3.4+ is supported. -For older Django versions, install *django-polymorphic==1.2*. +The current release of *django-polymorphic* supports Django 1.11, 2.0 and Python 2.7 and 3.4+ is supported. +For older Django versions, install *django-polymorphic==1.3*. For more information, see the `documentation at Read the Docs `_. diff --git a/docs/changelog.rst b/docs/changelog.rst index bc242ab..ded13c1 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -4,13 +4,14 @@ Changelog Changes in git -------------- -* **BACKWARDS INCOMPATIBILITY:** Dropped Django 1.8 support. +* **BACKWARDS INCOMPATIBILITY:** Dropped Django 1.8 and 1.10 support. * **BACKWARDS INCOMPATIBILITY:** Removed old deprecated code from 1.0, thus: * Import managers from ``polymorphic.managers`` (plural), not ``polymorphic.manager``. * Register child models to the admin as well using ``@admin.register()`` or ``admin.site.register()``, as this is no longer done automatically. +* Django 2.0 support. * Added ``PolymorphicTypeUndefined`` exception for incomplete imported models. When a data migration or import creates an polymorphic model, the ``polymorphic_ctype_id`` field should be filled in manually too. diff --git a/docs/quickstart.rst b/docs/quickstart.rst index 3df803f..91ca7b3 100644 --- a/docs/quickstart.rst +++ b/docs/quickstart.rst @@ -12,8 +12,8 @@ Update the settings file:: 'django.contrib.contenttypes', ) -The current release of *django-polymorphic* supports Django 1.8, 1.10, 1.11 and Python 2.7 and 3.4+ is supported. -For older Django versions, use *django-polymorphic==1.2*. +The current release of *django-polymorphic* supports Django 1.11, 2.0 and Python 2.7 and 3.4+ is supported. +For older Django versions, use *django-polymorphic==1.3*. Making Your Models Polymorphic ------------------------------ diff --git a/polymorphic/admin/parentadmin.py b/polymorphic/admin/parentadmin.py index 00dcb34..32fb3a9 100644 --- a/polymorphic/admin/parentadmin.py +++ b/polymorphic/admin/parentadmin.py @@ -11,16 +11,22 @@ from django.core.exceptions import PermissionDenied, ImproperlyConfigured from django.db import models from django.http import Http404, HttpResponseRedirect from django.template.response import TemplateResponse -from django.urls import RegexURLResolver from django.utils.encoding import force_text from django.utils.http import urlencode from django.utils.safestring import mark_safe from django.utils.translation import ugettext_lazy as _ - from polymorphic.utils import get_base_polymorphic_model from .forms import PolymorphicModelChoiceForm +try: + # Django 2.0+ + from django.urls import URLResolver +except ImportError: + # Django < 2.0 + from django.urls import RegexURLResolver as URLResolver + + if sys.version_info[0] >= 3: long = int @@ -265,7 +271,7 @@ class PolymorphicParentModelAdmin(admin.ModelAdmin): ct_id = self.model.objects.values_list('polymorphic_ctype_id', flat=True).get(pk=object_id) real_admin = self._get_real_admin_by_ct(ct_id) - resolver = RegexURLResolver('^', real_admin.urls) + resolver = URLResolver('^', real_admin.urls) resolvermatch = resolver.resolve(path) # May raise Resolver404 if not resolvermatch: raise Http404("No match for path '{0}' in admin subclass.".format(path)) diff --git a/polymorphic/formsets/utils.py b/polymorphic/formsets/utils.py index 3d55f8b..5dac6a7 100644 --- a/polymorphic/formsets/utils.py +++ b/polymorphic/formsets/utils.py @@ -1,11 +1,17 @@ """ Internal utils """ +import django def add_media(dest, media): """ Optimized version of django.forms.Media.__add__() that doesn't create new objects. + + Only required for Django < 2.0 """ - dest.add_css(media._css) - dest.add_js(media._js) + if django.VERSION >= (2, 0): + dest += media + else: + dest.add_css(media._css) + dest.add_js(media._js) diff --git a/polymorphic/models.py b/polymorphic/models.py index 6c7a9b4..893dddd 100644 --- a/polymorphic/models.py +++ b/polymorphic/models.py @@ -6,6 +6,7 @@ from __future__ import absolute_import from django.contrib.contenttypes.models import ContentType from django.db import models +from django.db.models.fields.related import ReverseOneToOneDescriptor, ForwardManyToOneDescriptor from django.db.utils import DEFAULT_DB_ALIAS from django.utils import six @@ -178,14 +179,6 @@ class PolymorphicModel(six.with_metaclass(PolymorphicModelBase, models.Model)): subclasses_and_superclasses_accessors = self._get_inheritance_relation_fields_and_models() - try: - from django.db.models.fields.related import ReverseOneToOneDescriptor, ForwardManyToOneDescriptor - except ImportError: - # django < 1.9 - from django.db.models.fields.related import ( - SingleRelatedObjectDescriptor as ReverseOneToOneDescriptor, - ReverseSingleRelatedObjectDescriptor as ForwardManyToOneDescriptor, - ) for name, model in subclasses_and_superclasses_accessors.items(): # Here be dragons. orig_accessor = getattr(self.__class__, name, None) diff --git a/polymorphic/tests/admintestcase.py b/polymorphic/tests/admintestcase.py index fb2e560..618d8fc 100644 --- a/polymorphic/tests/admintestcase.py +++ b/polymorphic/tests/admintestcase.py @@ -49,7 +49,7 @@ class AdminTestCase(TestCase): # Make sure the URLs are reachable by reverse() clear_url_caches() set_urlconf(tuple([ - url('^tmp-admin/', include(self.admin_site.urls)) + url('^tmp-admin/', self.admin_site.urls) ])) def get_admin_instance(self, model): diff --git a/polymorphic/tests/models.py b/polymorphic/tests/models.py index 47f21e4..60fc619 100644 --- a/polymorphic/tests/models.py +++ b/polymorphic/tests/models.py @@ -237,21 +237,6 @@ class PlainChildModelWithManager(models.Model): objects = PlainMyManager() -class MgrInheritA(models.Model): - mgrA = models.Manager() - mgrA2 = models.Manager() - field1 = models.CharField(max_length=10) - - -class MgrInheritB(MgrInheritA): - mgrB = models.Manager() - field2 = models.CharField(max_length=10) - - -class MgrInheritC(ShowFieldTypeAndContent, MgrInheritB): - pass - - class BlogBase(ShowFieldTypeAndContent, PolymorphicModel): name = models.CharField(max_length=10) diff --git a/polymorphic/tests/test_orm.py b/polymorphic/tests/test_orm.py index 441877e..335d450 100644 --- a/polymorphic/tests/test_orm.py +++ b/polymorphic/tests/test_orm.py @@ -228,14 +228,14 @@ class PolymorphicTests(TransactionTestCase): objects_deferred = Model2A.objects.defer('field1') self.assertNotIn('field1', objects_deferred[0].__dict__, 'field1 was not deferred (using defer())') - self.assertEqual(repr(objects_deferred[0]), - '') - self.assertEqual(repr(objects_deferred[1]), - '') - self.assertEqual(repr(objects_deferred[2]), - '') - self.assertEqual(repr(objects_deferred[3]), - '') + self.assertRegex(repr(objects_deferred[0]), + '') + self.assertRegex(repr(objects_deferred[1]), + '') + self.assertRegex(repr(objects_deferred[2]), + '') + self.assertRegex(repr(objects_deferred[3]), + '') objects_only = Model2A.objects.only('pk', 'polymorphic_ctype', 'field1') @@ -246,33 +246,33 @@ class PolymorphicTests(TransactionTestCase): ' on a child model') self.assertNotIn('field4', objects_only[3].__dict__, 'field4 was not deferred (using only())') - self.assertEqual(repr(objects_only[0]), - '') - self.assertEqual(repr(objects_only[1]), - '') - self.assertEqual(repr(objects_only[2]), - '') - self.assertEqual(repr(objects_only[3]), - '') + self.assertRegex(repr(objects_only[0]), + '') + self.assertRegex(repr(objects_only[1]), + '') + self.assertRegex(repr(objects_only[2]), + '') + self.assertRegex(repr(objects_only[3]), + '') ModelX.objects.create(field_b="A1", field_x="A2") ModelY.objects.create(field_b="B1", field_y="B2") objects_deferred = Base.objects.defer('ModelY___field_y') - self.assertEqual(repr(objects_deferred[0]), - '') - self.assertEqual(repr(objects_deferred[1]), - '') + self.assertRegex(repr(objects_deferred[0]), + '') + self.assertRegex(repr(objects_deferred[1]), + '') objects_only = Base.objects.only( 'polymorphic_ctype', 'ModelY___field_y', 'ModelX___field_x', ) - self.assertEqual(repr(objects_only[0]), - '') - self.assertEqual(repr(objects_only[1]), - '') + self.assertRegex(repr(objects_only[0]), + '') + self.assertRegex(repr(objects_only[1]), + '') def test_defer_related_fields(self): self.create_model2abcd() @@ -997,17 +997,3 @@ class PolymorphicTests(TransactionTestCase): MultiTableDerived.objects.bulk_create([ MultiTableDerived(field1='field1', field2='field2') ]) - - -def qrepr(data): - """ - Ensure consistent repr() output for the QuerySet object. - """ - if isinstance(data, QuerySet): - if django.VERSION < (1, 11): - # Django 1.10 still shows "= 1.10 + Django >= 1.11 [options.packages.find] exclude = diff --git a/tox.ini b/tox.ini index f8e95a8..6c96640 100644 --- a/tox.ini +++ b/tox.ini @@ -1,9 +1,9 @@ [tox] envlist = - py27-django{110,111} - py34-django{110,111} - py35-django{110,111,master} - py36-django{111,master} + py27-django{111} + py34-django{111,200} + py35-django{111,200,master} + py36-django{111,200,master} docs [testenv] @@ -14,8 +14,8 @@ setenv = deps = coverage dj-database-url - django110: Django >= 1.10, < 1.11 django111: Django >= 1.11, < 2.0 + django200: Django ~= 2.0.0 djangomaster: https://github.com/django/django/archive/master.tar.gz postgres: psycopg2 commands =