3.0.0
-----BEGIN PGP SIGNATURE----- iQIzBAABCgAdFiEE543E/4saL+AzhKtGi03RayEzO9sFAl8/oo0ACgkQi03RayEz O9si9w/+KL4NODbR+ql4A1Q8twJjsjyz1iiHHIGMdEkEEIG3PyGai73MqoQSyQZI h4Zw16U1CEVxtwCNLzewdz3NsQ9ioK0ZlKjtyp7b6x+zU2p8cwHoFSleB++HH1Uq M/RM6ZR8mycPSDhtpkHMQVGjzC408Zvx/hipUCk4gjeVjM92svmKXubnhb0wba4P zKpvX7xEqaCLitblWtemKNMqPRApKva/xvntuYWj0DGsIFGvt+AHsZ4dPZCrGdyp Fvt+OZP0ZTDZnyCTADSmg4BM5Sa8qGcs9gntQI9nmnkJWWyq3451NrUKvWPoEZQ+ 7cN4Cmp3nclwu4AiK/LWnQnEX953AdR3xQD1vZ8om6yfndXDOWLu0f0UKyKZMAPn LkmQcPol6Hm4yUT4yGGxzd2pgMJ5ZbIJVGKjn7D0hytb5kf9Ii3SyEEniQTS9JfU I9990UmzFoJI42CfaqhXi1fTk8yPOSfHqo0eyVOw12Q7ALeoAAD/HWgZTp1HO+Cq aN5wtn+oX7OSFSfr6/aXLTyYc17ugj4XIkn7DY69NJPsrMELBcX7ThZ+eimuxwFC ytt8o0nI49jYwA3bcrmIY2wonV5tkipR2EKivzEW7uteEpjIqHAOLreS5ZLHDCjS wFNsKl0Y3xOXGR4RaOrY7oKkRSraeVtbzqpkzL/NT0WsgpROTZ8= =60xM -----END PGP SIGNATURE----- Merge tag '3.0.0' into fix_request_path_info 3.0.0fix_request_path_info
commit
f08f18e3bb
|
|
@ -17,3 +17,4 @@ dist/
|
||||||
docs/_build/
|
docs/_build/
|
||||||
htmlcov/
|
htmlcov/
|
||||||
venv/
|
venv/
|
||||||
|
.venv/
|
||||||
|
|
|
||||||
41
.travis.yml
41
.travis.yml
|
|
@ -1,6 +1,3 @@
|
||||||
# https://travis-ci.org/django-polymorphic/django-polymorphic
|
|
||||||
dist: xenial
|
|
||||||
sudo: false
|
|
||||||
language: python
|
language: python
|
||||||
|
|
||||||
services:
|
services:
|
||||||
|
|
@ -11,32 +8,38 @@ addons:
|
||||||
matrix:
|
matrix:
|
||||||
fast_finish: true
|
fast_finish: true
|
||||||
include:
|
include:
|
||||||
# Django 1.11: Python 2.7, 3.5, or 3.6
|
# Django 2.1: Python 3.5, 3.6, or 3.7
|
||||||
- { env: TOXENV=py27-django111, python: 2.7 }
|
- { env: TOXENV=py35-django21, python: 3.5 }
|
||||||
- { env: TOXENV=py35-django111, python: 3.5 }
|
|
||||||
- { env: TOXENV=py36-django111, python: 3.6 }
|
|
||||||
- { env: TOXENV=py36-django111-postgres DB=postgres, python: 3.6 }
|
|
||||||
# Django 2.0: Python 3.5, or 3.6
|
|
||||||
- { env: TOXENV=py35-django20, python: 3.5 }
|
|
||||||
- { env: TOXENV=py36-django20, python: 3.6 }
|
|
||||||
- { env: TOXENV=py36-django20-postgres DB=postgres, python: 3.6 }
|
|
||||||
# Django 2.1: Python 3.6, or 3.7
|
|
||||||
- { env: TOXENV=py36-django21, python: 3.6 }
|
- { env: TOXENV=py36-django21, python: 3.6 }
|
||||||
- { env: TOXENV=py37-django21, python: 3.7 }
|
- { env: TOXENV=py37-django21, python: 3.7 }
|
||||||
- { env: TOXENV=py37-django21-postgres DB=postgres, python: 3.7 }
|
- { env: TOXENV=py37-django21-postgres DB=postgres, python: 3.7 }
|
||||||
# Django 2.2: Python 3.6, or 3.7
|
# Django 2.2: Python 3.5, 3.6, 3.7 or 3.8
|
||||||
|
- { env: TOXENV=py35-django22, python: 3.5 }
|
||||||
- { env: TOXENV=py36-django22, python: 3.6 }
|
- { env: TOXENV=py36-django22, python: 3.6 }
|
||||||
- { env: TOXENV=py37-django22, python: 3.7 }
|
- { env: TOXENV=py37-django22, python: 3.7 }
|
||||||
- { env: TOXENV=py37-django22-postgres DB=postgres, python: 3.7 }
|
- { env: TOXENV=py38-django22, python: 3.8 }
|
||||||
|
- { env: TOXENV=py38-django22-postgres DB=postgres, python: 3.8 }
|
||||||
|
# Django 3.0: Python 3.6, 3.7 or 3.8
|
||||||
|
- { env: TOXENV=py36-django30, python: 3.6 }
|
||||||
|
- { env: TOXENV=py37-django30, python: 3.7 }
|
||||||
|
- { env: TOXENV=py38-django30, python: 3.8 }
|
||||||
|
- { env: TOXENV=py38-django30-postgres DB=postgres, python: 3.8 }
|
||||||
|
# Django 3.1: Python 3.6, 3.7 or 3.8
|
||||||
|
- { env: TOXENV=py36-django31, python: 3.6 }
|
||||||
|
- { env: TOXENV=py37-django31, python: 3.7 }
|
||||||
|
- { env: TOXENV=py38-django31, python: 3.8 }
|
||||||
|
- { env: TOXENV=py38-django31-postgres DB=postgres, python: 3.8 }
|
||||||
# Django development master (direct from GitHub source):
|
# Django development master (direct from GitHub source):
|
||||||
- { env: TOXENV=py36-djangomaster, python: 3.6 }
|
- { env: TOXENV=py36-djangomaster, python: 3.6 }
|
||||||
- { env: TOXENV=py37-djangomaster, python: 3.7 }
|
- { env: TOXENV=py37-djangomaster, python: 3.7 }
|
||||||
- { env: TOXENV=py37-djangomaster-postgres DB=postgres, python: 3.7 }
|
- { env: TOXENV=py38-djangomaster, python: 3.8 }
|
||||||
|
- { env: TOXENV=py38-djangomaster-postgres DB=postgres, python: 3.8 }
|
||||||
|
|
||||||
allow_failures:
|
allow_failures:
|
||||||
- env: TOXENV=py36-djangomaster
|
- env: TOXENV=py36-djangomaster
|
||||||
- env: TOXENV=py37-djangomaster
|
- env: TOXENV=py37-djangomaster
|
||||||
- env: TOXENV=py37-djangomaster-postgres DB=postgres
|
- env: TOXENV=py38-djangomaster
|
||||||
|
- env: TOXENV=py38-djangomaster-postgres DB=postgres
|
||||||
|
|
||||||
cache:
|
cache:
|
||||||
directories:
|
directories:
|
||||||
|
|
@ -57,7 +60,3 @@ script:
|
||||||
after_success:
|
after_success:
|
||||||
- coverage xml -i
|
- coverage xml -i
|
||||||
- codecov
|
- codecov
|
||||||
|
|
||||||
branches:
|
|
||||||
only:
|
|
||||||
- master
|
|
||||||
|
|
|
||||||
|
|
@ -13,12 +13,14 @@ Contributors
|
||||||
* Abel Daniel
|
* Abel Daniel
|
||||||
* Adam Chainz
|
* Adam Chainz
|
||||||
* Adam Wentz
|
* Adam Wentz
|
||||||
|
* Adam Donaghy
|
||||||
* Andrew Ingram (contributed setup.py)
|
* Andrew Ingram (contributed setup.py)
|
||||||
* Al Johri
|
* Al Johri
|
||||||
* Alex Alvarez
|
* Alex Alvarez
|
||||||
* Andrew Dodd
|
* Andrew Dodd
|
||||||
* Angel Velasquez
|
* Angel Velasquez
|
||||||
* Austin Matsick
|
* Austin Matsick
|
||||||
|
* Bastien Vallet
|
||||||
* Ben Konrath
|
* Ben Konrath
|
||||||
* Bert Constantin
|
* Bert Constantin
|
||||||
* Bertrand Bordage
|
* Bertrand Bordage
|
||||||
|
|
|
||||||
|
|
@ -62,7 +62,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.11, 2.0, 2.1, 2.2 and Python 2.7 and 3.5+ is supported.
|
The current release of *django-polymorphic* supports Django 2.1, 2.2, 3.0, 3.1
|
||||||
|
and Python 3.5+ is supported.
|
||||||
For older Django versions, install *django-polymorphic==1.3*.
|
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/>`_.
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
# for readthedocs
|
# for readthedocs
|
||||||
# Remaining requirements are picked up from setup.py
|
# Remaining requirements are picked up from setup.py
|
||||||
Django == 2.2.3
|
Django == 2.2.13
|
||||||
django-extra-views == 0.12.0
|
django-extra-views == 0.12.0
|
||||||
sphinxcontrib-django == 0.4
|
sphinxcontrib-django == 0.4
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,14 @@
|
||||||
Changelog
|
Changelog
|
||||||
=========
|
=========
|
||||||
|
|
||||||
Changes in 2.1.2 (2019-17-15)
|
Changes in 3.0.0 (2020-08-21)
|
||||||
|
-----------------------------
|
||||||
|
|
||||||
|
* Support for Django 3.X
|
||||||
|
* Dropped support for python 2.X
|
||||||
|
* A lot of various fixes and improvements by various authors. Thanks a lot!
|
||||||
|
|
||||||
|
Changes in 2.1.2 (2019-07-15)
|
||||||
-----------------------------
|
-----------------------------
|
||||||
|
|
||||||
* Fix ``PolymorphicInlineModelAdmin`` media jQuery include for Django 2.0+
|
* Fix ``PolymorphicInlineModelAdmin`` media jQuery include for Django 2.0+
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,6 @@ can be included in a single Django migration. For example:
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
from __future__ import unicode_literals
|
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ Update the settings file::
|
||||||
'django.contrib.contenttypes',
|
'django.contrib.contenttypes',
|
||||||
)
|
)
|
||||||
|
|
||||||
The current release of *django-polymorphic* supports Django 1.11, 2.0 and Python 2.7 and 3.5+ is supported.
|
The current release of *django-polymorphic* supports Django 1.11, 2.0, 2.1, 2.2 and Python 2.7 and 3.5+ is supported.
|
||||||
For older Django versions, use *django-polymorphic==1.3*.
|
For older Django versions, use *django-polymorphic==1.3*.
|
||||||
|
|
||||||
Making Your Models Polymorphic
|
Making Your Models Polymorphic
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,10 @@
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.utils.dates import MONTHS_3
|
from django.utils.dates import MONTHS_3
|
||||||
from django.utils.six import python_2_unicode_compatible
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from polymorphic.models import PolymorphicModel
|
from polymorphic.models import PolymorphicModel
|
||||||
|
|
||||||
|
|
||||||
@python_2_unicode_compatible
|
|
||||||
class Order(models.Model):
|
class Order(models.Model):
|
||||||
"""
|
"""
|
||||||
An example order that has polymorphic relations
|
An example order that has polymorphic relations
|
||||||
|
|
@ -23,7 +21,6 @@ class Order(models.Model):
|
||||||
return self.title
|
return self.title
|
||||||
|
|
||||||
|
|
||||||
@python_2_unicode_compatible
|
|
||||||
class Payment(PolymorphicModel):
|
class Payment(PolymorphicModel):
|
||||||
"""
|
"""
|
||||||
A generic payment model.
|
A generic payment model.
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ import inspect
|
||||||
|
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from django.urls import resolve
|
from django.urls import resolve
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from polymorphic.utils import get_base_polymorphic_model
|
from polymorphic.utils import get_base_polymorphic_model
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from django.core.exceptions import PermissionDenied
|
from django.core.exceptions import PermissionDenied
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
|
||||||
class PolymorphicChildModelFilter(admin.SimpleListFilter):
|
class PolymorphicChildModelFilter(admin.SimpleListFilter):
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
from django import forms
|
from django import forms
|
||||||
from django.contrib.admin.widgets import AdminRadioSelect
|
from django.contrib.admin.widgets import AdminRadioSelect
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
|
||||||
class PolymorphicModelChoiceForm(forms.Form):
|
class PolymorphicModelChoiceForm(forms.Form):
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ import json
|
||||||
from django.contrib.admin.helpers import AdminField, InlineAdminForm, InlineAdminFormSet
|
from django.contrib.admin.helpers import AdminField, InlineAdminForm, InlineAdminFormSet
|
||||||
from django.utils.encoding import force_text
|
from django.utils.encoding import force_text
|
||||||
from django.utils.text import capfirst
|
from django.utils.text import capfirst
|
||||||
from django.utils.translation import ugettext
|
from django.utils.translation import gettext
|
||||||
|
|
||||||
from polymorphic.formsets import BasePolymorphicModelFormSet
|
from polymorphic.formsets import BasePolymorphicModelFormSet
|
||||||
|
|
||||||
|
|
@ -96,7 +96,7 @@ class PolymorphicInlineAdminFormSet(InlineAdminFormSet):
|
||||||
"name": "#%s" % self.formset.prefix,
|
"name": "#%s" % self.formset.prefix,
|
||||||
"options": {
|
"options": {
|
||||||
"prefix": self.formset.prefix,
|
"prefix": self.formset.prefix,
|
||||||
"addText": ugettext("Add another %(verbose_name)s")
|
"addText": gettext("Add another %(verbose_name)s")
|
||||||
% {"verbose_name": capfirst(verbose_name)},
|
% {"verbose_name": capfirst(verbose_name)},
|
||||||
"childTypes": [
|
"childTypes": [
|
||||||
{
|
{
|
||||||
|
|
@ -105,7 +105,7 @@ class PolymorphicInlineAdminFormSet(InlineAdminFormSet):
|
||||||
}
|
}
|
||||||
for model in self.formset.child_forms.keys()
|
for model in self.formset.child_forms.keys()
|
||||||
],
|
],
|
||||||
"deleteText": ugettext("Remove"),
|
"deleteText": gettext("Remove"),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,6 @@
|
||||||
"""
|
"""
|
||||||
The parent admin displays the list view of the base model.
|
The parent admin displays the list view of the base model.
|
||||||
"""
|
"""
|
||||||
import sys
|
|
||||||
|
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from django.contrib.admin.helpers import AdminErrorList, AdminForm
|
from django.contrib.admin.helpers import AdminErrorList, AdminForm
|
||||||
from django.contrib.admin.templatetags.admin_urls import add_preserved_filters
|
from django.contrib.admin.templatetags.admin_urls import add_preserved_filters
|
||||||
|
|
@ -11,26 +9,16 @@ from django.core.exceptions import ImproperlyConfigured, PermissionDenied
|
||||||
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 URLResolver
|
||||||
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 gettext_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:
|
|
||||||
long = int
|
|
||||||
|
|
||||||
|
|
||||||
class RegistrationClosed(RuntimeError):
|
class RegistrationClosed(RuntimeError):
|
||||||
"The admin model can't be registered anymore at this point."
|
"The admin model can't be registered anymore at this point."
|
||||||
|
|
@ -146,13 +134,14 @@ class PolymorphicParentModelAdmin(admin.ModelAdmin):
|
||||||
"""
|
"""
|
||||||
self._lazy_setup()
|
self._lazy_setup()
|
||||||
choices = []
|
choices = []
|
||||||
for model in self.get_child_models():
|
content_types = ContentType.objects.get_for_models(*self.get_child_models(), for_concrete_models=False)
|
||||||
|
|
||||||
|
for model, ct in content_types.items():
|
||||||
perm_function_name = "has_{0}_permission".format(action)
|
perm_function_name = "has_{0}_permission".format(action)
|
||||||
model_admin = self._get_real_admin_by_model(model)
|
model_admin = self._get_real_admin_by_model(model)
|
||||||
perm_function = getattr(model_admin, perm_function_name)
|
perm_function = getattr(model_admin, perm_function_name)
|
||||||
if not perm_function(request):
|
if not perm_function(request):
|
||||||
continue
|
continue
|
||||||
ct = ContentType.objects.get_for_model(model, for_concrete_model=False)
|
|
||||||
choices.append((ct.id, model._meta.verbose_name))
|
choices.append((ct.id, model._meta.verbose_name))
|
||||||
return choices
|
return choices
|
||||||
|
|
||||||
|
|
@ -293,9 +282,9 @@ class PolymorphicParentModelAdmin(admin.ModelAdmin):
|
||||||
try:
|
try:
|
||||||
pos = path.find("/")
|
pos = path.find("/")
|
||||||
if pos == -1:
|
if pos == -1:
|
||||||
object_id = long(path)
|
object_id = int(path)
|
||||||
else:
|
else:
|
||||||
object_id = long(path[0:pos])
|
object_id = int(path[0:pos])
|
||||||
except ValueError:
|
except ValueError:
|
||||||
raise Http404(
|
raise Http404(
|
||||||
"No ct_id parameter, unable to find admin subclass for path '{0}'.".format(
|
"No ct_id parameter, unable to find admin subclass for path '{0}'.".format(
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,6 @@
|
||||||
"""
|
"""
|
||||||
PolymorphicModel Meta Class
|
PolymorphicModel Meta Class
|
||||||
"""
|
"""
|
||||||
from __future__ import absolute_import
|
|
||||||
|
|
||||||
import inspect
|
import inspect
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
|
|
||||||
|
|
@ -1,22 +1,4 @@
|
||||||
"""Compatibility with Python 2 (taken from 'django.utils.six')"""
|
"""Compatibility with Python 2 (taken from 'django.utils.six')"""
|
||||||
import sys
|
|
||||||
|
|
||||||
PY2 = sys.version_info[0] == 2
|
|
||||||
PY3 = sys.version_info[0] == 3
|
|
||||||
|
|
||||||
|
|
||||||
if PY3:
|
|
||||||
string_types = (str,)
|
|
||||||
integer_types = (int,)
|
|
||||||
class_types = (type,)
|
|
||||||
text_type = str
|
|
||||||
binary_type = bytes
|
|
||||||
|
|
||||||
MAXSIZE = sys.maxsize
|
|
||||||
else:
|
|
||||||
string_types = (basestring,)
|
|
||||||
integer_types = (int, long)
|
|
||||||
|
|
||||||
|
|
||||||
def with_metaclass(meta, *bases):
|
def with_metaclass(meta, *bases):
|
||||||
class metaclass(type):
|
class metaclass(type):
|
||||||
|
|
@ -24,22 +6,3 @@ def with_metaclass(meta, *bases):
|
||||||
return meta(name, bases, d)
|
return meta(name, bases, d)
|
||||||
|
|
||||||
return type.__new__(metaclass, "temporary_class", (), {})
|
return type.__new__(metaclass, "temporary_class", (), {})
|
||||||
|
|
||||||
|
|
||||||
def python_2_unicode_compatible(klass):
|
|
||||||
"""
|
|
||||||
A decorator that defines __unicode__ and __str__ methods under Python 2.
|
|
||||||
Under Python 3 it does nothing.
|
|
||||||
|
|
||||||
To support Python 2 and 3 with a single code base, define a __str__ method
|
|
||||||
returning text and apply this decorator to the class.
|
|
||||||
"""
|
|
||||||
if PY2:
|
|
||||||
if "__str__" not in klass.__dict__:
|
|
||||||
raise ValueError(
|
|
||||||
"@python_2_unicode_compatible cannot be applied "
|
|
||||||
"to %s because it doesn't define __str__()." % klass.__name__
|
|
||||||
)
|
|
||||||
klass.__unicode__ = klass.__str__
|
|
||||||
klass.__str__ = lambda self: self.__unicode__().encode("utf-8")
|
|
||||||
return klass
|
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,6 @@ The ``extra_views.advanced`` provides a method to combine that with a create/upd
|
||||||
|
|
||||||
This package provides classes that support both options for polymorphic formsets.
|
This package provides classes that support both options for polymorphic formsets.
|
||||||
"""
|
"""
|
||||||
from __future__ import absolute_import
|
|
||||||
|
|
||||||
import extra_views
|
import extra_views
|
||||||
from django.core.exceptions import ImproperlyConfigured
|
from django.core.exceptions import ImproperlyConfigured
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,17 +2,14 @@
|
||||||
"""
|
"""
|
||||||
The manager class for use in the models.
|
The manager class for use in the models.
|
||||||
"""
|
"""
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
|
||||||
from polymorphic.compat import python_2_unicode_compatible
|
|
||||||
from polymorphic.query import PolymorphicQuerySet
|
from polymorphic.query import PolymorphicQuerySet
|
||||||
|
|
||||||
__all__ = ("PolymorphicManager", "PolymorphicQuerySet")
|
__all__ = ("PolymorphicManager", "PolymorphicQuerySet")
|
||||||
|
|
||||||
|
|
||||||
@python_2_unicode_compatible
|
|
||||||
class PolymorphicManager(models.Manager):
|
class PolymorphicManager(models.Manager):
|
||||||
"""
|
"""
|
||||||
Manager for PolymorphicModel
|
Manager for PolymorphicModel
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,6 @@
|
||||||
"""
|
"""
|
||||||
Seamless Polymorphic Inheritance for Django Models
|
Seamless Polymorphic Inheritance for Django Models
|
||||||
"""
|
"""
|
||||||
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 (
|
from django.db.models.fields.related import (
|
||||||
|
|
|
||||||
|
|
@ -2,16 +2,14 @@
|
||||||
"""
|
"""
|
||||||
QuerySet for PolymorphicModel
|
QuerySet for PolymorphicModel
|
||||||
"""
|
"""
|
||||||
from __future__ import absolute_import
|
|
||||||
|
|
||||||
import copy
|
import copy
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
|
|
||||||
|
from django import get_version as get_django_version
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.db.models import FieldDoesNotExist
|
from django.core.exceptions import FieldDoesNotExist
|
||||||
from django.db.models.query import ModelIterable, Q, QuerySet
|
from django.db.models.query import ModelIterable, Q, QuerySet
|
||||||
|
|
||||||
from . import compat
|
|
||||||
from .query_translate import (
|
from .query_translate import (
|
||||||
translate_polymorphic_field_path,
|
translate_polymorphic_field_path,
|
||||||
translate_polymorphic_filter_definitions_in_args,
|
translate_polymorphic_filter_definitions_in_args,
|
||||||
|
|
@ -160,24 +158,40 @@ class PolymorphicQuerySet(QuerySet):
|
||||||
# Implementation in _translate_polymorphic_filter_defnition."""
|
# Implementation in _translate_polymorphic_filter_defnition."""
|
||||||
return self.filter(not_instance_of=args)
|
return self.filter(not_instance_of=args)
|
||||||
|
|
||||||
def _filter_or_exclude(self, negate, *args, **kwargs):
|
# Makes _filter_or_exclude compatible with the change in signature introduced in django at 9c9a3fe
|
||||||
# We override this internal Django functon as it is used for all filter member functions.
|
if get_django_version() >= "3.2":
|
||||||
q_objects = translate_polymorphic_filter_definitions_in_args(
|
def _filter_or_exclude(self, negate, args, kwargs):
|
||||||
self.model, args, using=self.db
|
# We override this internal Django function as it is used for all filter member functions.
|
||||||
)
|
q_objects = translate_polymorphic_filter_definitions_in_args(
|
||||||
# filter_field='data'
|
queryset_model=self.model, args=args, using=self.db
|
||||||
additional_args = translate_polymorphic_filter_definitions_in_kwargs(
|
)
|
||||||
self.model, kwargs, using=self.db
|
# filter_field='data'
|
||||||
)
|
additional_args = translate_polymorphic_filter_definitions_in_kwargs(
|
||||||
return super(PolymorphicQuerySet, self)._filter_or_exclude(
|
queryset_model=self.model, kwargs=kwargs, using=self.db
|
||||||
negate, *(list(q_objects) + additional_args), **kwargs
|
)
|
||||||
)
|
args = list(q_objects) + additional_args
|
||||||
|
return super(PolymorphicQuerySet, self)._filter_or_exclude(
|
||||||
|
negate=negate, args=args, kwargs=kwargs
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
def _filter_or_exclude(self, negate, *args, **kwargs):
|
||||||
|
# We override this internal Django function as it is used for all filter member functions.
|
||||||
|
q_objects = translate_polymorphic_filter_definitions_in_args(
|
||||||
|
self.model, args, using=self.db
|
||||||
|
)
|
||||||
|
# filter_field='data'
|
||||||
|
additional_args = translate_polymorphic_filter_definitions_in_kwargs(
|
||||||
|
self.model, kwargs, using=self.db
|
||||||
|
)
|
||||||
|
return super(PolymorphicQuerySet, self)._filter_or_exclude(
|
||||||
|
negate, *(list(q_objects) + additional_args), **kwargs
|
||||||
|
)
|
||||||
|
|
||||||
def order_by(self, *field_names):
|
def order_by(self, *field_names):
|
||||||
"""translate the field paths in the args, then call vanilla order_by."""
|
"""translate the field paths in the args, then call vanilla order_by."""
|
||||||
field_names = [
|
field_names = [
|
||||||
translate_polymorphic_field_path(self.model, a)
|
translate_polymorphic_field_path(self.model, a)
|
||||||
if isinstance(a, compat.string_types)
|
if isinstance(a, str)
|
||||||
else a # allow expressions to pass unchanged
|
else a # allow expressions to pass unchanged
|
||||||
for a in field_names
|
for a in field_names
|
||||||
]
|
]
|
||||||
|
|
@ -524,4 +538,4 @@ class PolymorphicQuerySet(QuerySet):
|
||||||
if not self.model.polymorphic_query_multiline_output:
|
if not self.model.polymorphic_query_multiline_output:
|
||||||
return olist
|
return olist
|
||||||
clist = PolymorphicQuerySet._p_list_class(olist)
|
clist = PolymorphicQuerySet._p_list_class(olist)
|
||||||
return clist
|
return clist
|
||||||
|
|
@ -2,14 +2,12 @@
|
||||||
"""
|
"""
|
||||||
PolymorphicQuerySet support functions
|
PolymorphicQuerySet support functions
|
||||||
"""
|
"""
|
||||||
from __future__ import absolute_import
|
|
||||||
|
|
||||||
import copy
|
import copy
|
||||||
from collections import deque
|
from collections import deque
|
||||||
|
|
||||||
from django.apps import apps
|
from django.apps import apps
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.core.exceptions import FieldError
|
from django.core.exceptions import FieldError, FieldDoesNotExist
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
from django.db.models.fields.related import ForeignObjectRel, RelatedField
|
from django.db.models.fields.related import ForeignObjectRel, RelatedField
|
||||||
|
|
@ -144,7 +142,7 @@ def translate_polymorphic_field_path(queryset_model, field_path):
|
||||||
into modela__modelb__modelc__field3.
|
into modela__modelb__modelc__field3.
|
||||||
Returns: translated path (unchanged, if no translation needed)
|
Returns: translated path (unchanged, if no translation needed)
|
||||||
"""
|
"""
|
||||||
if not isinstance(field_path, compat.string_types):
|
if not isinstance(field_path, str):
|
||||||
raise ValueError("Expected field name as string: {0}".format(field_path))
|
raise ValueError("Expected field name as string: {0}".format(field_path))
|
||||||
|
|
||||||
classname, sep, pure_field_path = field_path.partition("___")
|
classname, sep, pure_field_path = field_path.partition("___")
|
||||||
|
|
@ -189,7 +187,7 @@ def translate_polymorphic_field_path(queryset_model, field_path):
|
||||||
# Can also test whether the field exists in the related object to avoid ambiguity between
|
# Can also test whether the field exists in the related object to avoid ambiguity between
|
||||||
# class names and field names, but that never happens when your class names are in CamelCase.
|
# class names and field names, but that never happens when your class names are in CamelCase.
|
||||||
return field_path # No exception raised, field does exist.
|
return field_path # No exception raised, field does exist.
|
||||||
except models.FieldDoesNotExist:
|
except FieldDoesNotExist:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
submodels = _get_all_sub_models(queryset_model)
|
submodels = _get_all_sub_models(queryset_model)
|
||||||
|
|
|
||||||
|
|
@ -3,13 +3,9 @@ import re
|
||||||
|
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
|
||||||
from . import compat
|
|
||||||
from .compat import python_2_unicode_compatible
|
|
||||||
|
|
||||||
RE_DEFERRED = re.compile("_Deferred_.*")
|
RE_DEFERRED = re.compile("_Deferred_.*")
|
||||||
|
|
||||||
|
|
||||||
@python_2_unicode_compatible
|
|
||||||
class ShowFieldBase(object):
|
class ShowFieldBase(object):
|
||||||
""" base class for the ShowField... model mixins, does the work """
|
""" base class for the ShowField... model mixins, does the work """
|
||||||
|
|
||||||
|
|
@ -42,7 +38,7 @@ class ShowFieldBase(object):
|
||||||
out += content.__class__.__name__
|
out += content.__class__.__name__
|
||||||
elif issubclass(field_type, models.ManyToManyField):
|
elif issubclass(field_type, models.ManyToManyField):
|
||||||
out += "%d" % content.count()
|
out += "%d" % content.count()
|
||||||
elif isinstance(content, compat.integer_types):
|
elif isinstance(content, int):
|
||||||
out += str(content)
|
out += str(content)
|
||||||
elif content is None:
|
elif content is None:
|
||||||
out += "None"
|
out += "None"
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,5 @@
|
||||||
from django.template import Library, Node, TemplateSyntaxError
|
from django.template import Library, Node, TemplateSyntaxError
|
||||||
|
|
||||||
from polymorphic import compat
|
|
||||||
|
|
||||||
register = Library()
|
register = Library()
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -31,7 +29,7 @@ class BreadcrumbScope(Node):
|
||||||
# Instead, have an assignment tag that inserts that in the template.
|
# Instead, have an assignment tag that inserts that in the template.
|
||||||
base_opts = self.base_opts.resolve(context)
|
base_opts = self.base_opts.resolve(context)
|
||||||
new_vars = {}
|
new_vars = {}
|
||||||
if base_opts and not isinstance(base_opts, compat.string_types):
|
if base_opts and not isinstance(base_opts, str):
|
||||||
new_vars = {
|
new_vars = {
|
||||||
"app_label": base_opts.app_label, # What this is all about
|
"app_label": base_opts.app_label, # What this is all about
|
||||||
"opts": base_opts,
|
"opts": base_opts,
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ import json
|
||||||
from django.template import Library
|
from django.template import Library
|
||||||
from django.utils.encoding import force_text
|
from django.utils.encoding import force_text
|
||||||
from django.utils.text import capfirst
|
from django.utils.text import capfirst
|
||||||
from django.utils.translation import ugettext
|
from django.utils.translation import gettext
|
||||||
|
|
||||||
from polymorphic.formsets import BasePolymorphicModelFormSet
|
from polymorphic.formsets import BasePolymorphicModelFormSet
|
||||||
|
|
||||||
|
|
@ -44,10 +44,10 @@ def as_script_options(formset):
|
||||||
"prefix": formset.prefix,
|
"prefix": formset.prefix,
|
||||||
"pkFieldName": formset.model._meta.pk.name,
|
"pkFieldName": formset.model._meta.pk.name,
|
||||||
"addText": getattr(formset, "add_text", None)
|
"addText": getattr(formset, "add_text", None)
|
||||||
or ugettext("Add another %(verbose_name)s")
|
or gettext("Add another %(verbose_name)s")
|
||||||
% {"verbose_name": capfirst(verbose_name)},
|
% {"verbose_name": capfirst(verbose_name)},
|
||||||
"showAddButton": getattr(formset, "show_add_button", True),
|
"showAddButton": getattr(formset, "show_add_button", True),
|
||||||
"deleteText": ugettext("Delete"),
|
"deleteText": gettext("Delete"),
|
||||||
}
|
}
|
||||||
|
|
||||||
if isinstance(formset, BasePolymorphicModelFormSet):
|
if isinstance(formset, BasePolymorphicModelFormSet):
|
||||||
|
|
|
||||||
|
|
@ -1450,6 +1450,13 @@ class Migration(migrations.Migration):
|
||||||
to="tests.ParentModelWithManager",
|
to="tests.ParentModelWithManager",
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="childmodelwithmanager",
|
||||||
|
name="field1",
|
||||||
|
field=models.CharField(
|
||||||
|
max_length=10,
|
||||||
|
),
|
||||||
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name="childmodelwithmanager",
|
model_name="childmodelwithmanager",
|
||||||
name="polymorphic_ctype",
|
name="polymorphic_ctype",
|
||||||
|
|
|
||||||
|
|
@ -222,6 +222,7 @@ class ParentModelWithManager(PolymorphicModel):
|
||||||
|
|
||||||
class ChildModelWithManager(PolymorphicModel):
|
class ChildModelWithManager(PolymorphicModel):
|
||||||
# Also test whether foreign keys receive the manager:
|
# Also test whether foreign keys receive the manager:
|
||||||
|
field1 = models.CharField(max_length=10) # needed as MyManager uses it
|
||||||
fk = models.ForeignKey(
|
fk = models.ForeignKey(
|
||||||
ParentModelWithManager, on_delete=models.CASCADE, related_name="childmodel_set"
|
ParentModelWithManager, on_delete=models.CASCADE, related_name="childmodel_set"
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,3 @@
|
||||||
from __future__ import print_function
|
|
||||||
|
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
|
|
@ -20,7 +18,7 @@ from polymorphic.tests.models import (
|
||||||
|
|
||||||
|
|
||||||
class MultipleDatabasesTests(TestCase):
|
class MultipleDatabasesTests(TestCase):
|
||||||
multi_db = True
|
databases = ["default", "secondary"]
|
||||||
|
|
||||||
def test_save_to_non_default_database(self):
|
def test_save_to_non_default_database(self):
|
||||||
Model2A.objects.db_manager("secondary").create(field1="A1")
|
Model2A.objects.db_manager("secondary").create(field1="A1")
|
||||||
|
|
|
||||||
|
|
@ -238,22 +238,12 @@ class PolymorphicTests(TransactionTestCase):
|
||||||
objects_deferred[0].__dict__,
|
objects_deferred[0].__dict__,
|
||||||
"field1 was not deferred (using defer())",
|
"field1 was not deferred (using defer())",
|
||||||
)
|
)
|
||||||
self.assertRegex(
|
|
||||||
repr(objects_deferred[0]),
|
# Check that we have exactly one deferred field ('field1') per resulting object.
|
||||||
r"<Model2A: id \d+, field1 \(CharField\), deferred\[field1\]>",
|
for obj in objects_deferred:
|
||||||
)
|
deferred_fields = obj.get_deferred_fields()
|
||||||
self.assertRegex(
|
self.assertEqual(1, len(deferred_fields))
|
||||||
repr(objects_deferred[1]),
|
self.assertIn("field1", deferred_fields)
|
||||||
r"<Model2B: id \d+, field1 \(CharField\), field2 \(CharField\), deferred\[field1\]>",
|
|
||||||
)
|
|
||||||
self.assertRegex(
|
|
||||||
repr(objects_deferred[2]),
|
|
||||||
r"<Model2C: id \d+, field1 \(CharField\), field2 \(CharField\), field3 \(CharField\), deferred\[field1\]>",
|
|
||||||
)
|
|
||||||
self.assertRegex(
|
|
||||||
repr(objects_deferred[3]),
|
|
||||||
r"<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")
|
||||||
|
|
||||||
|
|
@ -271,48 +261,37 @@ class PolymorphicTests(TransactionTestCase):
|
||||||
self.assertNotIn(
|
self.assertNotIn(
|
||||||
"field4", objects_only[3].__dict__, "field4 was not deferred (using only())"
|
"field4", objects_only[3].__dict__, "field4 was not deferred (using only())"
|
||||||
)
|
)
|
||||||
self.assertRegex(
|
self.assertNotIn("field1", objects_only[0].get_deferred_fields())
|
||||||
repr(objects_only[0]), r"<Model2A: id \d+, field1 \(CharField\)>"
|
|
||||||
)
|
self.assertIn("field2", objects_only[1].get_deferred_fields())
|
||||||
self.assertRegex(
|
|
||||||
repr(objects_only[1]),
|
# objects_only[2] has several deferred fields, ensure they are all set as such.
|
||||||
r"<Model2B: id \d+, field1 \(CharField\), field2 \(CharField\), deferred\[field2\]>",
|
model2c_deferred = objects_only[2].get_deferred_fields()
|
||||||
)
|
self.assertIn("field2", model2c_deferred)
|
||||||
self.assertRegex(
|
self.assertIn("field3", model2c_deferred)
|
||||||
repr(objects_only[2]),
|
self.assertIn("model2a_ptr_id", model2c_deferred)
|
||||||
r"<Model2C: id \d+, field1 \(CharField\), field2 \(CharField\), field3 \(CharField\), "
|
|
||||||
r"deferred\[field2,field3,model2a_ptr_id\]>",
|
# objects_only[3] has a few more fields that should be set as deferred.
|
||||||
)
|
model2d_deferred = objects_only[3].get_deferred_fields()
|
||||||
self.assertRegex(
|
self.assertIn("field2", model2d_deferred)
|
||||||
repr(objects_only[3]),
|
self.assertIn("field3", model2d_deferred)
|
||||||
r"<Model2D: id \d+, field1 \(CharField\), field2 \(CharField\), field3 \(CharField\), field4 \(CharField\), "
|
self.assertIn("field4", model2d_deferred)
|
||||||
r"deferred\[field2,field3,field4,model2a_ptr_id,model2b_ptr_id\]>",
|
self.assertIn("model2a_ptr_id", model2d_deferred)
|
||||||
)
|
self.assertIn("model2b_ptr_id", model2d_deferred)
|
||||||
|
|
||||||
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")
|
||||||
|
|
||||||
|
# If we defer a field on a descendent, the parent's field is not deferred.
|
||||||
objects_deferred = Base.objects.defer("ModelY___field_y")
|
objects_deferred = Base.objects.defer("ModelY___field_y")
|
||||||
self.assertRegex(
|
self.assertNotIn("field_y", objects_deferred[0].get_deferred_fields())
|
||||||
repr(objects_deferred[0]),
|
self.assertIn("field_y", objects_deferred[1].get_deferred_fields())
|
||||||
r"<ModelX: id \d+, field_b \(CharField\), field_x \(CharField\)>",
|
|
||||||
)
|
|
||||||
self.assertRegex(
|
|
||||||
repr(objects_deferred[1]),
|
|
||||||
r"<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.assertRegex(
|
self.assertIn("field_b", objects_only[0].get_deferred_fields())
|
||||||
repr(objects_only[0]),
|
self.assertIn("field_b", objects_only[1].get_deferred_fields())
|
||||||
r"<ModelX: id \d+, field_b \(CharField\), field_x \(CharField\), deferred\[field_b\]>",
|
|
||||||
)
|
|
||||||
self.assertRegex(
|
|
||||||
repr(objects_only[1]),
|
|
||||||
r"<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()
|
||||||
|
|
@ -481,21 +460,22 @@ class PolymorphicTests(TransactionTestCase):
|
||||||
def test_foreignkey_field(self):
|
def test_foreignkey_field(self):
|
||||||
self.create_model2abcd()
|
self.create_model2abcd()
|
||||||
|
|
||||||
object2a = Model2A.base_objects.get(field1="C1")
|
object2a = Model2A.objects.get(field1="C1")
|
||||||
self.assertEqual(object2a.model2b.__class__, Model2B)
|
self.assertEqual(object2a.model2b.__class__, Model2B)
|
||||||
|
|
||||||
object2b = Model2B.base_objects.get(field1="C1")
|
object2b = Model2B.objects.get(field1="C1")
|
||||||
self.assertEqual(object2b.model2c.__class__, Model2C)
|
self.assertEqual(object2b.model2c.__class__, Model2C)
|
||||||
|
|
||||||
def test_onetoone_field(self):
|
def test_onetoone_field(self):
|
||||||
self.create_model2abcd()
|
self.create_model2abcd()
|
||||||
|
|
||||||
|
# FIXME: We should not use base_objects here.
|
||||||
a = Model2A.base_objects.get(field1="C1")
|
a = Model2A.base_objects.get(field1="C1")
|
||||||
b = One2OneRelatingModelDerived.objects.create(
|
b = One2OneRelatingModelDerived.objects.create(
|
||||||
one2one=a, field1="f1", field2="f2"
|
one2one=a, field1="f1", field2="f2"
|
||||||
)
|
)
|
||||||
|
|
||||||
# this result is basically wrong, probably due to Django cacheing (we used base_objects), but should not be a problem
|
# FIXME: this result is basically wrong, probably due to Django cacheing (we used base_objects), but should not be a problem
|
||||||
self.assertEqual(b.one2one.__class__, Model2A)
|
self.assertEqual(b.one2one.__class__, Model2A)
|
||||||
self.assertEqual(b.one2one_id, b.one2one.id)
|
self.assertEqual(b.one2one_id, b.one2one.id)
|
||||||
|
|
||||||
|
|
@ -581,32 +561,18 @@ class PolymorphicTests(TransactionTestCase):
|
||||||
select={"topic": "tests_modelextraexternal.topic"},
|
select={"topic": "tests_modelextraexternal.topic"},
|
||||||
where=["tests_modelextraa.id = tests_modelextraexternal.id"],
|
where=["tests_modelextraa.id = tests_modelextraexternal.id"],
|
||||||
)
|
)
|
||||||
if compat.PY3:
|
self.assertEqual(
|
||||||
self.assertEqual(
|
repr(objects[0]),
|
||||||
repr(objects[0]),
|
'<ModelExtraA: id 1, field1 (CharField) "A1" - Extra: topic (str) "extra1">',
|
||||||
'<ModelExtraA: id 1, field1 (CharField) "A1" - Extra: topic (str) "extra1">',
|
)
|
||||||
)
|
self.assertEqual(
|
||||||
self.assertEqual(
|
repr(objects[1]),
|
||||||
repr(objects[1]),
|
'<ModelExtraB: id 2, field1 (CharField) "B1", field2 (CharField) "B2" - Extra: topic (str) "extra2">',
|
||||||
'<ModelExtraB: id 2, field1 (CharField) "B1", field2 (CharField) "B2" - Extra: topic (str) "extra2">',
|
)
|
||||||
)
|
self.assertEqual(
|
||||||
self.assertEqual(
|
repr(objects[2]),
|
||||||
repr(objects[2]),
|
'<ModelExtraC: id 3, field1 (CharField) "C1", field2 (CharField) "C2", field3 (CharField) "C3" - Extra: topic (str) "extra3">',
|
||||||
'<ModelExtraC: id 3, field1 (CharField) "C1", field2 (CharField) "C2", field3 (CharField) "C3" - Extra: topic (str) "extra3">',
|
)
|
||||||
)
|
|
||||||
else:
|
|
||||||
self.assertEqual(
|
|
||||||
repr(objects[0]),
|
|
||||||
'<ModelExtraA: id 1, field1 (CharField) "A1" - Extra: topic (unicode) "extra1">',
|
|
||||||
)
|
|
||||||
self.assertEqual(
|
|
||||||
repr(objects[1]),
|
|
||||||
'<ModelExtraB: id 2, field1 (CharField) "B1", field2 (CharField) "B2" - Extra: topic (unicode) "extra2">',
|
|
||||||
)
|
|
||||||
self.assertEqual(
|
|
||||||
repr(objects[2]),
|
|
||||||
'<ModelExtraC: id 3, field1 (CharField) "C1", field2 (CharField) "C2", field3 (CharField) "C3" - Extra: topic (unicode) "extra3">',
|
|
||||||
)
|
|
||||||
self.assertEqual(len(objects), 3)
|
self.assertEqual(len(objects), 3)
|
||||||
|
|
||||||
def test_instance_of_filter(self):
|
def test_instance_of_filter(self):
|
||||||
|
|
@ -822,7 +788,6 @@ class PolymorphicTests(TransactionTestCase):
|
||||||
|
|
||||||
self.assertIs(type(ModelWithMyManager.objects), MyManager)
|
self.assertIs(type(ModelWithMyManager.objects), MyManager)
|
||||||
self.assertIs(type(ModelWithMyManager._default_manager), MyManager)
|
self.assertIs(type(ModelWithMyManager._default_manager), MyManager)
|
||||||
self.assertIs(type(ModelWithMyManager.base_objects), models.Manager)
|
|
||||||
|
|
||||||
def test_user_defined_manager_as_secondary(self):
|
def test_user_defined_manager_as_secondary(self):
|
||||||
self.create_model2abcd()
|
self.create_model2abcd()
|
||||||
|
|
@ -845,7 +810,6 @@ class PolymorphicTests(TransactionTestCase):
|
||||||
self.assertIs(
|
self.assertIs(
|
||||||
type(ModelWithMyManagerNoDefault._default_manager), PolymorphicManager
|
type(ModelWithMyManagerNoDefault._default_manager), PolymorphicManager
|
||||||
)
|
)
|
||||||
self.assertIs(type(ModelWithMyManagerNoDefault.base_objects), models.Manager)
|
|
||||||
|
|
||||||
def test_user_objects_manager_as_secondary(self):
|
def test_user_objects_manager_as_secondary(self):
|
||||||
self.create_model2abcd()
|
self.create_model2abcd()
|
||||||
|
|
@ -855,7 +819,6 @@ class PolymorphicTests(TransactionTestCase):
|
||||||
self.assertIs(type(ModelWithMyManagerDefault.my_objects), MyManager)
|
self.assertIs(type(ModelWithMyManagerDefault.my_objects), MyManager)
|
||||||
self.assertIs(type(ModelWithMyManagerDefault.objects), PolymorphicManager)
|
self.assertIs(type(ModelWithMyManagerDefault.objects), PolymorphicManager)
|
||||||
self.assertIs(type(ModelWithMyManagerDefault._default_manager), MyManager)
|
self.assertIs(type(ModelWithMyManagerDefault._default_manager), MyManager)
|
||||||
self.assertIs(type(ModelWithMyManagerDefault.base_objects), models.Manager)
|
|
||||||
|
|
||||||
def test_user_defined_queryset_as_manager(self):
|
def test_user_defined_queryset_as_manager(self):
|
||||||
self.create_model2abcd()
|
self.create_model2abcd()
|
||||||
|
|
@ -878,7 +841,6 @@ class PolymorphicTests(TransactionTestCase):
|
||||||
type(ModelWithMyManager2._default_manager).__name__,
|
type(ModelWithMyManager2._default_manager).__name__,
|
||||||
"PolymorphicManagerFromMyManagerQuerySet",
|
"PolymorphicManagerFromMyManagerQuerySet",
|
||||||
)
|
)
|
||||||
self.assertIs(type(ModelWithMyManager2.base_objects), models.Manager)
|
|
||||||
|
|
||||||
def test_manager_inheritance(self):
|
def test_manager_inheritance(self):
|
||||||
# by choice of MRO, should be MyManager from MROBase1.
|
# by choice of MRO, should be MyManager from MROBase1.
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,3 @@
|
||||||
import sys
|
|
||||||
|
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.db import DEFAULT_DB_ALIAS
|
from django.db import DEFAULT_DB_ALIAS
|
||||||
|
|
||||||
|
|
@ -59,12 +57,9 @@ def sort_by_subclass(*classes):
|
||||||
"""
|
"""
|
||||||
Sort a series of models by their inheritance order.
|
Sort a series of models by their inheritance order.
|
||||||
"""
|
"""
|
||||||
if sys.version_info[0] == 2:
|
from functools import cmp_to_key
|
||||||
return sorted(classes, cmp=_compare_mro)
|
|
||||||
else:
|
|
||||||
from functools import cmp_to_key
|
|
||||||
|
|
||||||
return sorted(classes, key=cmp_to_key(_compare_mro))
|
return sorted(classes, key=cmp_to_key(_compare_mro))
|
||||||
|
|
||||||
|
|
||||||
def get_base_polymorphic_model(ChildModel, allow_abstract=False):
|
def get_base_polymorphic_model(ChildModel, allow_abstract=False):
|
||||||
|
|
|
||||||
|
|
@ -74,6 +74,7 @@ if not settings.configured:
|
||||||
],
|
],
|
||||||
POLYMORPHIC_TEST_SWAPPABLE="polymorphic.swappedmodel",
|
POLYMORPHIC_TEST_SWAPPABLE="polymorphic.swappedmodel",
|
||||||
ROOT_URLCONF=None,
|
ROOT_URLCONF=None,
|
||||||
|
SECRET_KEY="supersecret"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
13
setup.cfg
13
setup.cfg
|
|
@ -1,12 +1,12 @@
|
||||||
[metadata]
|
[metadata]
|
||||||
name = django-polymorphic
|
name = django-polymorphic
|
||||||
version = 2.1.2
|
version = 3.0.0
|
||||||
description = Seamless polymorphic inheritance for Django models
|
description = Seamless polymorphic inheritance for Django models
|
||||||
long_description = file:README.rst
|
long_description = file:README.rst
|
||||||
author = Bert Constantin
|
author = Bert Constantin
|
||||||
author_email = bert.constantin@gmx.de
|
author_email = bert.constantin@gmx.de
|
||||||
maintainer = Christopher Glass
|
maintainer = Christopher Glass
|
||||||
maintainer_email = tribaal@gmail.com
|
maintainer_email = tribaal@ubuntu.com
|
||||||
url = https://github.com/django-polymorphic/django-polymorphic
|
url = https://github.com/django-polymorphic/django-polymorphic
|
||||||
download_url = https://github.com/django-polymorphic/django-polymorphic/tarball/master
|
download_url = https://github.com/django-polymorphic/django-polymorphic/tarball/master
|
||||||
keywords = django, polymorphic
|
keywords = django, polymorphic
|
||||||
|
|
@ -14,25 +14,26 @@ classifiers =
|
||||||
Development Status :: 5 - Production/Stable
|
Development Status :: 5 - Production/Stable
|
||||||
Environment :: Web Environment
|
Environment :: Web Environment
|
||||||
Framework :: Django
|
Framework :: Django
|
||||||
Framework :: Django :: 1.11
|
|
||||||
Framework :: Django :: 2.0
|
|
||||||
Framework :: Django :: 2.1
|
Framework :: Django :: 2.1
|
||||||
Framework :: Django :: 2.2
|
Framework :: Django :: 2.2
|
||||||
|
Framework :: Django :: 3.0
|
||||||
|
Framework :: Django :: 3.1
|
||||||
Intended Audience :: Developers
|
Intended Audience :: Developers
|
||||||
License :: OSI Approved :: BSD License
|
License :: OSI Approved :: BSD License
|
||||||
Operating System :: OS Independent
|
Operating System :: OS Independent
|
||||||
Programming Language :: Python :: 2.7
|
|
||||||
Programming Language :: Python :: 3
|
Programming Language :: Python :: 3
|
||||||
|
Programming Language :: Python :: 3 :: Only
|
||||||
Programming Language :: Python :: 3.5
|
Programming Language :: Python :: 3.5
|
||||||
Programming Language :: Python :: 3.6
|
Programming Language :: Python :: 3.6
|
||||||
Programming Language :: Python :: 3.7
|
Programming Language :: Python :: 3.7
|
||||||
|
Programming Language :: Python :: 3.8
|
||||||
Topic :: Database
|
Topic :: Database
|
||||||
|
|
||||||
[options]
|
[options]
|
||||||
packages = find:
|
packages = find:
|
||||||
include_package_data = True
|
include_package_data = True
|
||||||
install_requires =
|
install_requires =
|
||||||
Django >= 1.11
|
Django >= 2.1
|
||||||
|
|
||||||
[options.packages.find]
|
[options.packages.find]
|
||||||
exclude =
|
exclude =
|
||||||
|
|
|
||||||
13
tox.ini
13
tox.ini
|
|
@ -1,9 +1,9 @@
|
||||||
[tox]
|
[tox]
|
||||||
envlist =
|
envlist =
|
||||||
py27-django{111}
|
py35-django{21,22}
|
||||||
py35-django{111,20}
|
py36-django{21,22,30,31}
|
||||||
py36-django{111,20,21,22,master}
|
py37-django{21,22,30,31}
|
||||||
py37-django{21,22,master}
|
py38-django{21,22,30,31}
|
||||||
docs
|
docs
|
||||||
|
|
||||||
[testenv]
|
[testenv]
|
||||||
|
|
@ -12,12 +12,13 @@ setenv =
|
||||||
postgres: DEFAULT_DATABASE = postgres:///default
|
postgres: DEFAULT_DATABASE = postgres:///default
|
||||||
postgres: SECONDARY_DATABASE = postgres:///secondary
|
postgres: SECONDARY_DATABASE = postgres:///secondary
|
||||||
deps =
|
deps =
|
||||||
|
ipdb
|
||||||
coverage
|
coverage
|
||||||
dj-database-url
|
dj-database-url
|
||||||
django111: Django >= 1.11, < 2.0
|
|
||||||
django20: Django ~= 2.0
|
|
||||||
django21: Django ~= 2.1
|
django21: Django ~= 2.1
|
||||||
django22: Django ~= 2.2
|
django22: Django ~= 2.2
|
||||||
|
django30: Django ~= 3.0
|
||||||
|
django31: Django ~= 3.1
|
||||||
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