commit
e164026b43
17
.travis.yml
17
.travis.yml
|
|
@ -5,17 +5,18 @@ language: python
|
||||||
python: "3.6"
|
python: "3.6"
|
||||||
|
|
||||||
env:
|
env:
|
||||||
- TOXENV=py27-django110
|
|
||||||
- TOXENV=py27-django111
|
- TOXENV=py27-django111
|
||||||
- TOXENV=py34-django110
|
|
||||||
- TOXENV=py34-django111
|
- TOXENV=py34-django111
|
||||||
- TOXENV=py35-django110
|
- TOXENV=py34-django200
|
||||||
- TOXENV=py35-django111
|
- TOXENV=py35-django111
|
||||||
|
- TOXENV=py35-django200
|
||||||
- TOXENV=py35-djangomaster
|
- TOXENV=py35-djangomaster
|
||||||
- TOXENV=py36-django111
|
- TOXENV=py36-django111
|
||||||
|
- TOXENV=py36-django200
|
||||||
- TOXENV=py36-djangomaster
|
- TOXENV=py36-djangomaster
|
||||||
# XXX: Use a matrix to build these?
|
# XXX: Use a matrix to build these?
|
||||||
- TOXENV=py36-django111-postgres DB=postgres
|
- TOXENV=py36-django111-postgres DB=postgres
|
||||||
|
- TOXENV=py36-django200-postgres DB=postgres
|
||||||
- TOXENV=py36-djangomaster-postgres DB=postgres
|
- TOXENV=py36-djangomaster-postgres DB=postgres
|
||||||
|
|
||||||
services:
|
services:
|
||||||
|
|
@ -24,17 +25,21 @@ services:
|
||||||
matrix:
|
matrix:
|
||||||
fast_finish: true
|
fast_finish: true
|
||||||
include:
|
include:
|
||||||
- python: "3.5"
|
- python: "2.7"
|
||||||
env: TOXENV=py35-django110
|
env: TOXENV=py27-django111
|
||||||
- python: "3.5"
|
- python: "3.5"
|
||||||
env: TOXENV=py35-django111
|
env: TOXENV=py35-django111
|
||||||
|
- python: "3.5"
|
||||||
|
env: TOXENV=py35-django200
|
||||||
- python: "3.5"
|
- python: "3.5"
|
||||||
env: TOXENV=py35-djangomaster
|
env: TOXENV=py35-djangomaster
|
||||||
exclude:
|
exclude:
|
||||||
- python: "3.6"
|
- python: "3.6"
|
||||||
env: TOXENV=py35-django110
|
env: TOXENV=py27-django111
|
||||||
- python: "3.6"
|
- python: "3.6"
|
||||||
env: TOXENV=py35-django111
|
env: TOXENV=py35-django111
|
||||||
|
- python: "3.6"
|
||||||
|
env: TOXENV=py35-django200
|
||||||
- python: "3.6"
|
- python: "3.6"
|
||||||
env: TOXENV=py35-djangomaster
|
env: TOXENV=py35-djangomaster
|
||||||
allow_failures:
|
allow_failures:
|
||||||
|
|
|
||||||
|
|
@ -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.
|
While taking this in mind, there are valid reasons for using subclassed models.
|
||||||
That's what this library is designed for!
|
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.
|
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.2*.
|
For older Django versions, install *django-polymorphic==1.3*.
|
||||||
|
|
||||||
For more information, see the `documentation at Read the Docs <https://django-polymorphic.readthedocs.io/>`_.
|
For more information, see the `documentation at Read the Docs <https://django-polymorphic.readthedocs.io/>`_.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,13 +4,14 @@ Changelog
|
||||||
Changes in git
|
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:
|
* **BACKWARDS INCOMPATIBILITY:** Removed old deprecated code from 1.0, thus:
|
||||||
|
|
||||||
* Import managers from ``polymorphic.managers`` (plural), not ``polymorphic.manager``.
|
* Import managers from ``polymorphic.managers`` (plural), not ``polymorphic.manager``.
|
||||||
* Register child models to the admin as well using ``@admin.register()`` or ``admin.site.register()``,
|
* Register child models to the admin as well using ``@admin.register()`` or ``admin.site.register()``,
|
||||||
as this is no longer done automatically.
|
as this is no longer done automatically.
|
||||||
|
|
||||||
|
* Django 2.0 support.
|
||||||
* Added ``PolymorphicTypeUndefined`` exception for incomplete imported models.
|
* Added ``PolymorphicTypeUndefined`` exception for incomplete imported models.
|
||||||
When a data migration or import creates an polymorphic model,
|
When a data migration or import creates an polymorphic model,
|
||||||
the ``polymorphic_ctype_id`` field should be filled in manually too.
|
the ``polymorphic_ctype_id`` field should be filled in manually too.
|
||||||
|
|
|
||||||
|
|
@ -12,8 +12,8 @@ Update the settings file::
|
||||||
'django.contrib.contenttypes',
|
'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.
|
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.2*.
|
For older Django versions, use *django-polymorphic==1.3*.
|
||||||
|
|
||||||
Making Your Models Polymorphic
|
Making Your Models Polymorphic
|
||||||
------------------------------
|
------------------------------
|
||||||
|
|
|
||||||
|
|
@ -11,16 +11,22 @@ from django.core.exceptions import PermissionDenied, ImproperlyConfigured
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.http import Http404, HttpResponseRedirect
|
from django.http import Http404, HttpResponseRedirect
|
||||||
from django.template.response import TemplateResponse
|
from django.template.response import TemplateResponse
|
||||||
from django.urls import RegexURLResolver
|
|
||||||
from django.utils.encoding import force_text
|
from django.utils.encoding import force_text
|
||||||
from django.utils.http import urlencode
|
from django.utils.http import urlencode
|
||||||
from django.utils.safestring import mark_safe
|
from django.utils.safestring import mark_safe
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from polymorphic.utils import get_base_polymorphic_model
|
from polymorphic.utils import get_base_polymorphic_model
|
||||||
from .forms import PolymorphicModelChoiceForm
|
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:
|
if sys.version_info[0] >= 3:
|
||||||
long = int
|
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)
|
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)
|
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
|
resolvermatch = resolver.resolve(path) # May raise Resolver404
|
||||||
if not resolvermatch:
|
if not resolvermatch:
|
||||||
raise Http404("No match for path '{0}' in admin subclass.".format(path))
|
raise Http404("No match for path '{0}' in admin subclass.".format(path))
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,17 @@
|
||||||
"""
|
"""
|
||||||
Internal utils
|
Internal utils
|
||||||
"""
|
"""
|
||||||
|
import django
|
||||||
|
|
||||||
|
|
||||||
def add_media(dest, media):
|
def add_media(dest, media):
|
||||||
"""
|
"""
|
||||||
Optimized version of django.forms.Media.__add__() that doesn't create new objects.
|
Optimized version of django.forms.Media.__add__() that doesn't create new objects.
|
||||||
|
|
||||||
|
Only required for Django < 2.0
|
||||||
"""
|
"""
|
||||||
|
if django.VERSION >= (2, 0):
|
||||||
|
dest += media
|
||||||
|
else:
|
||||||
dest.add_css(media._css)
|
dest.add_css(media._css)
|
||||||
dest.add_js(media._js)
|
dest.add_js(media._js)
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ from __future__ import absolute_import
|
||||||
|
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
from django.db.models.fields.related import ReverseOneToOneDescriptor, ForwardManyToOneDescriptor
|
||||||
from django.db.utils import DEFAULT_DB_ALIAS
|
from django.db.utils import DEFAULT_DB_ALIAS
|
||||||
from django.utils import six
|
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()
|
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():
|
for name, model in subclasses_and_superclasses_accessors.items():
|
||||||
# Here be dragons.
|
# Here be dragons.
|
||||||
orig_accessor = getattr(self.__class__, name, None)
|
orig_accessor = getattr(self.__class__, name, None)
|
||||||
|
|
|
||||||
|
|
@ -49,7 +49,7 @@ class AdminTestCase(TestCase):
|
||||||
# Make sure the URLs are reachable by reverse()
|
# Make sure the URLs are reachable by reverse()
|
||||||
clear_url_caches()
|
clear_url_caches()
|
||||||
set_urlconf(tuple([
|
set_urlconf(tuple([
|
||||||
url('^tmp-admin/', include(self.admin_site.urls))
|
url('^tmp-admin/', self.admin_site.urls)
|
||||||
]))
|
]))
|
||||||
|
|
||||||
def get_admin_instance(self, model):
|
def get_admin_instance(self, model):
|
||||||
|
|
|
||||||
|
|
@ -237,21 +237,6 @@ class PlainChildModelWithManager(models.Model):
|
||||||
objects = PlainMyManager()
|
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):
|
class BlogBase(ShowFieldTypeAndContent, PolymorphicModel):
|
||||||
name = models.CharField(max_length=10)
|
name = models.CharField(max_length=10)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -228,14 +228,14 @@ class PolymorphicTests(TransactionTestCase):
|
||||||
objects_deferred = Model2A.objects.defer('field1')
|
objects_deferred = Model2A.objects.defer('field1')
|
||||||
|
|
||||||
self.assertNotIn('field1', objects_deferred[0].__dict__, 'field1 was not deferred (using defer())')
|
self.assertNotIn('field1', objects_deferred[0].__dict__, 'field1 was not deferred (using defer())')
|
||||||
self.assertEqual(repr(objects_deferred[0]),
|
self.assertRegex(repr(objects_deferred[0]),
|
||||||
'<Model2A: id 1, field1 (CharField), deferred[field1]>')
|
'<Model2A: id \d+, field1 \(CharField\), deferred\[field1\]>')
|
||||||
self.assertEqual(repr(objects_deferred[1]),
|
self.assertRegex(repr(objects_deferred[1]),
|
||||||
'<Model2B: id 2, field1 (CharField), field2 (CharField), deferred[field1]>')
|
'<Model2B: id \d+, field1 \(CharField\), field2 \(CharField\), deferred\[field1\]>')
|
||||||
self.assertEqual(repr(objects_deferred[2]),
|
self.assertRegex(repr(objects_deferred[2]),
|
||||||
'<Model2C: id 3, field1 (CharField), field2 (CharField), field3 (CharField), deferred[field1]>')
|
'<Model2C: id \d+, field1 \(CharField\), field2 \(CharField\), field3 \(CharField\), deferred\[field1\]>')
|
||||||
self.assertEqual(repr(objects_deferred[3]),
|
self.assertRegex(repr(objects_deferred[3]),
|
||||||
'<Model2D: id 4, field1 (CharField), field2 (CharField), field3 (CharField), field4 (CharField), deferred[field1]>')
|
'<Model2D: id \d+, field1 \(CharField\), field2 \(CharField\), field3 \(CharField\), field4 \(CharField\), deferred\[field1\]>')
|
||||||
|
|
||||||
objects_only = Model2A.objects.only('pk', 'polymorphic_ctype', 'field1')
|
objects_only = Model2A.objects.only('pk', 'polymorphic_ctype', 'field1')
|
||||||
|
|
||||||
|
|
@ -246,33 +246,33 @@ class PolymorphicTests(TransactionTestCase):
|
||||||
' on a child model')
|
' on a child model')
|
||||||
self.assertNotIn('field4', objects_only[3].__dict__,
|
self.assertNotIn('field4', objects_only[3].__dict__,
|
||||||
'field4 was not deferred (using only())')
|
'field4 was not deferred (using only())')
|
||||||
self.assertEqual(repr(objects_only[0]),
|
self.assertRegex(repr(objects_only[0]),
|
||||||
'<Model2A: id 1, field1 (CharField)>')
|
'<Model2A: id \d+, field1 \(CharField\)>')
|
||||||
self.assertEqual(repr(objects_only[1]),
|
self.assertRegex(repr(objects_only[1]),
|
||||||
'<Model2B: id 2, field1 (CharField), field2 (CharField), deferred[field2]>')
|
'<Model2B: id \d+, field1 \(CharField\), field2 \(CharField\), deferred\[field2\]>')
|
||||||
self.assertEqual(repr(objects_only[2]),
|
self.assertRegex(repr(objects_only[2]),
|
||||||
'<Model2C: id 3, field1 (CharField), field2 (CharField), field3 (CharField), '
|
'<Model2C: id \d+, field1 \(CharField\), field2 \(CharField\), field3 \(CharField\), '
|
||||||
'deferred[field2,field3,model2a_ptr_id]>')
|
'deferred\[field2,field3,model2a_ptr_id\]>')
|
||||||
self.assertEqual(repr(objects_only[3]),
|
self.assertRegex(repr(objects_only[3]),
|
||||||
'<Model2D: id 4, field1 (CharField), field2 (CharField), field3 (CharField), field4 (CharField), '
|
'<Model2D: id \d+, field1 \(CharField\), field2 \(CharField\), field3 \(CharField\), field4 \(CharField\), '
|
||||||
'deferred[field2,field3,field4,model2a_ptr_id,model2b_ptr_id]>')
|
'deferred\[field2,field3,field4,model2a_ptr_id,model2b_ptr_id\]>')
|
||||||
|
|
||||||
ModelX.objects.create(field_b="A1", field_x="A2")
|
ModelX.objects.create(field_b="A1", field_x="A2")
|
||||||
ModelY.objects.create(field_b="B1", field_y="B2")
|
ModelY.objects.create(field_b="B1", field_y="B2")
|
||||||
|
|
||||||
objects_deferred = Base.objects.defer('ModelY___field_y')
|
objects_deferred = Base.objects.defer('ModelY___field_y')
|
||||||
self.assertEqual(repr(objects_deferred[0]),
|
self.assertRegex(repr(objects_deferred[0]),
|
||||||
'<ModelX: id 3, field_b (CharField), field_x (CharField)>')
|
'<ModelX: id \d+, field_b \(CharField\), field_x \(CharField\)>')
|
||||||
self.assertEqual(repr(objects_deferred[1]),
|
self.assertRegex(repr(objects_deferred[1]),
|
||||||
'<ModelY: id 4, field_b (CharField), field_y (CharField), deferred[field_y]>')
|
'<ModelY: id \d+, field_b \(CharField\), field_y \(CharField\), deferred\[field_y\]>')
|
||||||
|
|
||||||
objects_only = Base.objects.only(
|
objects_only = Base.objects.only(
|
||||||
'polymorphic_ctype', 'ModelY___field_y', 'ModelX___field_x',
|
'polymorphic_ctype', 'ModelY___field_y', 'ModelX___field_x',
|
||||||
)
|
)
|
||||||
self.assertEqual(repr(objects_only[0]),
|
self.assertRegex(repr(objects_only[0]),
|
||||||
'<ModelX: id 3, field_b (CharField), field_x (CharField), deferred[field_b]>')
|
'<ModelX: id \d+, field_b \(CharField\), field_x \(CharField\), deferred\[field_b\]>')
|
||||||
self.assertEqual(repr(objects_only[1]),
|
self.assertRegex(repr(objects_only[1]),
|
||||||
'<ModelY: id 4, field_b (CharField), field_y (CharField), deferred[field_b]>')
|
'<ModelY: id \d+, field_b \(CharField\), field_y \(CharField\), deferred\[field_b\]>')
|
||||||
|
|
||||||
def test_defer_related_fields(self):
|
def test_defer_related_fields(self):
|
||||||
self.create_model2abcd()
|
self.create_model2abcd()
|
||||||
|
|
@ -997,17 +997,3 @@ class PolymorphicTests(TransactionTestCase):
|
||||||
MultiTableDerived.objects.bulk_create([
|
MultiTableDerived.objects.bulk_create([
|
||||||
MultiTableDerived(field1='field1', field2='field2')
|
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 "<QuerySet [", not taking the actual type into account.
|
|
||||||
return '<{0} {1}'.format(data.__class__.__name__, repr(data)[10:])
|
|
||||||
else:
|
|
||||||
return repr(data)
|
|
||||||
|
|
||||||
return repr(data)
|
|
||||||
|
|
|
||||||
|
|
@ -14,8 +14,8 @@ classifiers =
|
||||||
Development Status :: 5 - Production/Stable
|
Development Status :: 5 - Production/Stable
|
||||||
Environment :: Web Environment
|
Environment :: Web Environment
|
||||||
Framework :: Django
|
Framework :: Django
|
||||||
Framework :: Django :: 1.10
|
|
||||||
Framework :: Django :: 1.11
|
Framework :: Django :: 1.11
|
||||||
|
Framework :: Django :: 2.0
|
||||||
Intended Audience :: Developers
|
Intended Audience :: Developers
|
||||||
License :: OSI Approved :: BSD License
|
License :: OSI Approved :: BSD License
|
||||||
Operating System :: OS Independent
|
Operating System :: OS Independent
|
||||||
|
|
@ -30,7 +30,7 @@ classifiers =
|
||||||
packages = find:
|
packages = find:
|
||||||
include_package_data = True
|
include_package_data = True
|
||||||
install_requires =
|
install_requires =
|
||||||
Django >= 1.10
|
Django >= 1.11
|
||||||
|
|
||||||
[options.packages.find]
|
[options.packages.find]
|
||||||
exclude =
|
exclude =
|
||||||
|
|
|
||||||
10
tox.ini
10
tox.ini
|
|
@ -1,9 +1,9 @@
|
||||||
[tox]
|
[tox]
|
||||||
envlist =
|
envlist =
|
||||||
py27-django{110,111}
|
py27-django{111}
|
||||||
py34-django{110,111}
|
py34-django{111,200}
|
||||||
py35-django{110,111,master}
|
py35-django{111,200,master}
|
||||||
py36-django{111,master}
|
py36-django{111,200,master}
|
||||||
docs
|
docs
|
||||||
|
|
||||||
[testenv]
|
[testenv]
|
||||||
|
|
@ -14,8 +14,8 @@ setenv =
|
||||||
deps =
|
deps =
|
||||||
coverage
|
coverage
|
||||||
dj-database-url
|
dj-database-url
|
||||||
django110: Django >= 1.10, < 1.11
|
|
||||||
django111: Django >= 1.11, < 2.0
|
django111: Django >= 1.11, < 2.0
|
||||||
|
django200: Django ~= 2.0.0
|
||||||
djangomaster: https://github.com/django/django/archive/master.tar.gz
|
djangomaster: https://github.com/django/django/archive/master.tar.gz
|
||||||
postgres: psycopg2
|
postgres: psycopg2
|
||||||
commands =
|
commands =
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue