commit
e164026b43
17
.travis.yml
17
.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:
|
||||
|
|
|
|||
|
|
@ -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 <https://django-polymorphic.readthedocs.io/>`_.
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
------------------------------
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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]),
|
||||
'<Model2A: id 1, field1 (CharField), deferred[field1]>')
|
||||
self.assertEqual(repr(objects_deferred[1]),
|
||||
'<Model2B: id 2, field1 (CharField), field2 (CharField), deferred[field1]>')
|
||||
self.assertEqual(repr(objects_deferred[2]),
|
||||
'<Model2C: id 3, field1 (CharField), field2 (CharField), field3 (CharField), deferred[field1]>')
|
||||
self.assertEqual(repr(objects_deferred[3]),
|
||||
'<Model2D: id 4, field1 (CharField), field2 (CharField), field3 (CharField), field4 (CharField), deferred[field1]>')
|
||||
self.assertRegex(repr(objects_deferred[0]),
|
||||
'<Model2A: id \d+, field1 \(CharField\), deferred\[field1\]>')
|
||||
self.assertRegex(repr(objects_deferred[1]),
|
||||
'<Model2B: id \d+, field1 \(CharField\), field2 \(CharField\), deferred\[field1\]>')
|
||||
self.assertRegex(repr(objects_deferred[2]),
|
||||
'<Model2C: id \d+, field1 \(CharField\), field2 \(CharField\), field3 \(CharField\), deferred\[field1\]>')
|
||||
self.assertRegex(repr(objects_deferred[3]),
|
||||
'<Model2D: id \d+, field1 \(CharField\), field2 \(CharField\), field3 \(CharField\), field4 \(CharField\), deferred\[field1\]>')
|
||||
|
||||
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]),
|
||||
'<Model2A: id 1, field1 (CharField)>')
|
||||
self.assertEqual(repr(objects_only[1]),
|
||||
'<Model2B: id 2, field1 (CharField), field2 (CharField), deferred[field2]>')
|
||||
self.assertEqual(repr(objects_only[2]),
|
||||
'<Model2C: id 3, field1 (CharField), field2 (CharField), field3 (CharField), '
|
||||
'deferred[field2,field3,model2a_ptr_id]>')
|
||||
self.assertEqual(repr(objects_only[3]),
|
||||
'<Model2D: id 4, field1 (CharField), field2 (CharField), field3 (CharField), field4 (CharField), '
|
||||
'deferred[field2,field3,field4,model2a_ptr_id,model2b_ptr_id]>')
|
||||
self.assertRegex(repr(objects_only[0]),
|
||||
'<Model2A: id \d+, field1 \(CharField\)>')
|
||||
self.assertRegex(repr(objects_only[1]),
|
||||
'<Model2B: id \d+, field1 \(CharField\), field2 \(CharField\), deferred\[field2\]>')
|
||||
self.assertRegex(repr(objects_only[2]),
|
||||
'<Model2C: id \d+, field1 \(CharField\), field2 \(CharField\), field3 \(CharField\), '
|
||||
'deferred\[field2,field3,model2a_ptr_id\]>')
|
||||
self.assertRegex(repr(objects_only[3]),
|
||||
'<Model2D: id \d+, field1 \(CharField\), field2 \(CharField\), field3 \(CharField\), field4 \(CharField\), '
|
||||
'deferred\[field2,field3,field4,model2a_ptr_id,model2b_ptr_id\]>')
|
||||
|
||||
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]),
|
||||
'<ModelX: id 3, field_b (CharField), field_x (CharField)>')
|
||||
self.assertEqual(repr(objects_deferred[1]),
|
||||
'<ModelY: id 4, field_b (CharField), field_y (CharField), deferred[field_y]>')
|
||||
self.assertRegex(repr(objects_deferred[0]),
|
||||
'<ModelX: id \d+, field_b \(CharField\), field_x \(CharField\)>')
|
||||
self.assertRegex(repr(objects_deferred[1]),
|
||||
'<ModelY: id \d+, field_b \(CharField\), field_y \(CharField\), deferred\[field_y\]>')
|
||||
|
||||
objects_only = Base.objects.only(
|
||||
'polymorphic_ctype', 'ModelY___field_y', 'ModelX___field_x',
|
||||
)
|
||||
self.assertEqual(repr(objects_only[0]),
|
||||
'<ModelX: id 3, field_b (CharField), field_x (CharField), deferred[field_b]>')
|
||||
self.assertEqual(repr(objects_only[1]),
|
||||
'<ModelY: id 4, field_b (CharField), field_y (CharField), deferred[field_b]>')
|
||||
self.assertRegex(repr(objects_only[0]),
|
||||
'<ModelX: id \d+, field_b \(CharField\), field_x \(CharField\), deferred\[field_b\]>')
|
||||
self.assertRegex(repr(objects_only[1]),
|
||||
'<ModelY: id \d+, field_b \(CharField\), field_y \(CharField\), deferred\[field_b\]>')
|
||||
|
||||
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 "<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
|
||||
Environment :: Web Environment
|
||||
Framework :: Django
|
||||
Framework :: Django :: 1.10
|
||||
Framework :: Django :: 1.11
|
||||
Framework :: Django :: 2.0
|
||||
Intended Audience :: Developers
|
||||
License :: OSI Approved :: BSD License
|
||||
Operating System :: OS Independent
|
||||
|
|
@ -30,7 +30,7 @@ classifiers =
|
|||
packages = find:
|
||||
include_package_data = True
|
||||
install_requires =
|
||||
Django >= 1.10
|
||||
Django >= 1.11
|
||||
|
||||
[options.packages.find]
|
||||
exclude =
|
||||
|
|
|
|||
10
tox.ini
10
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 =
|
||||
|
|
|
|||
Loading…
Reference in New Issue