Reformat all files with isort and black
parent
d314dce4a2
commit
59c020ee50
|
|
@ -5,11 +5,9 @@
|
||||||
# so the docs root won't be detected by find_packages()
|
# so the docs root won't be detected by find_packages()
|
||||||
|
|
||||||
# Display sane URLs in the docs:
|
# Display sane URLs in the docs:
|
||||||
STATIC_URL = '/static/'
|
STATIC_URL = "/static/"
|
||||||
|
|
||||||
# Avoid error for missing the secret key
|
# Avoid error for missing the secret key
|
||||||
SECRET_KEY = 'docs'
|
SECRET_KEY = "docs"
|
||||||
|
|
||||||
INSTALLED_APPS = [
|
INSTALLED_APPS = ["django.contrib.contenttypes"]
|
||||||
'django.contrib.contenttypes',
|
|
||||||
]
|
|
||||||
|
|
|
||||||
77
docs/conf.py
77
docs/conf.py
|
|
@ -11,17 +11,18 @@
|
||||||
# All configuration values have a default; values that are commented out
|
# All configuration values have a default; values that are commented out
|
||||||
# serve to show the default.
|
# serve to show the default.
|
||||||
|
|
||||||
import sys
|
|
||||||
import os
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
import django
|
import django
|
||||||
import sphinx_rtd_theme
|
import sphinx_rtd_theme
|
||||||
|
|
||||||
# If extensions (or modules to document with autodoc) are in another directory,
|
# If extensions (or modules to document with autodoc) are in another directory,
|
||||||
# add these directories to sys.path here. If the directory is relative to the
|
# add these directories to sys.path here. If the directory is relative to the
|
||||||
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
||||||
sys.path.insert(0, os.path.abspath('_ext'))
|
sys.path.insert(0, os.path.abspath("_ext"))
|
||||||
sys.path.insert(0, os.path.abspath('..'))
|
sys.path.insert(0, os.path.abspath(".."))
|
||||||
os.environ['DJANGO_SETTINGS_MODULE'] = 'djangodummy.settings'
|
os.environ["DJANGO_SETTINGS_MODULE"] = "djangodummy.settings"
|
||||||
|
|
||||||
django.setup()
|
django.setup()
|
||||||
|
|
||||||
|
|
@ -33,36 +34,36 @@ django.setup()
|
||||||
# Add any Sphinx extension module names here, as strings. They can be extensions
|
# Add any Sphinx extension module names here, as strings. They can be extensions
|
||||||
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
|
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
|
||||||
extensions = [
|
extensions = [
|
||||||
'sphinx.ext.autodoc',
|
"sphinx.ext.autodoc",
|
||||||
'sphinx.ext.graphviz',
|
"sphinx.ext.graphviz",
|
||||||
'sphinx.ext.intersphinx',
|
"sphinx.ext.intersphinx",
|
||||||
'sphinxcontrib_django',
|
"sphinxcontrib_django",
|
||||||
]
|
]
|
||||||
|
|
||||||
# Add any paths that contain templates here, relative to this directory.
|
# Add any paths that contain templates here, relative to this directory.
|
||||||
templates_path = ['_templates']
|
templates_path = ["_templates"]
|
||||||
|
|
||||||
# The suffix of source filenames.
|
# The suffix of source filenames.
|
||||||
source_suffix = '.rst'
|
source_suffix = ".rst"
|
||||||
|
|
||||||
# The encoding of source files.
|
# The encoding of source files.
|
||||||
# source_encoding = 'utf-8-sig'
|
# source_encoding = 'utf-8-sig'
|
||||||
|
|
||||||
# The master toctree document.
|
# The master toctree document.
|
||||||
master_doc = 'index'
|
master_doc = "index"
|
||||||
|
|
||||||
# General information about the project.
|
# General information about the project.
|
||||||
project = u'django-polymorphic'
|
project = u"django-polymorphic"
|
||||||
copyright = u'2013, Bert Constantin, Chris Glass, Diederik van der Boor'
|
copyright = u"2013, Bert Constantin, Chris Glass, Diederik van der Boor"
|
||||||
|
|
||||||
# The version info for the project you're documenting, acts as replacement for
|
# The version info for the project you're documenting, acts as replacement for
|
||||||
# |version| and |release|, also used in various other places throughout the
|
# |version| and |release|, also used in various other places throughout the
|
||||||
# built documents.
|
# built documents.
|
||||||
#
|
#
|
||||||
# The short X.Y version.
|
# The short X.Y version.
|
||||||
version = '2.0.3'
|
version = "2.0.3"
|
||||||
# The full version, including alpha/beta/rc tags.
|
# The full version, including alpha/beta/rc tags.
|
||||||
release = '2.0.3'
|
release = "2.0.3"
|
||||||
|
|
||||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||||
# for a list of supported languages.
|
# for a list of supported languages.
|
||||||
|
|
@ -76,7 +77,7 @@ release = '2.0.3'
|
||||||
|
|
||||||
# List of patterns, relative to source directory, that match files and
|
# List of patterns, relative to source directory, that match files and
|
||||||
# directories to ignore when looking for source files.
|
# directories to ignore when looking for source files.
|
||||||
exclude_patterns = ['_build']
|
exclude_patterns = ["_build"]
|
||||||
|
|
||||||
# The reST default role (used for this markup: `text`) to use for all documents.
|
# The reST default role (used for this markup: `text`) to use for all documents.
|
||||||
# default_role = None
|
# default_role = None
|
||||||
|
|
@ -93,7 +94,7 @@ exclude_patterns = ['_build']
|
||||||
# show_authors = False
|
# show_authors = False
|
||||||
|
|
||||||
# The name of the Pygments (syntax highlighting) style to use.
|
# The name of the Pygments (syntax highlighting) style to use.
|
||||||
pygments_style = 'sphinx'
|
pygments_style = "sphinx"
|
||||||
|
|
||||||
# A list of ignored prefixes for module index sorting.
|
# A list of ignored prefixes for module index sorting.
|
||||||
# modindex_common_prefix = []
|
# modindex_common_prefix = []
|
||||||
|
|
@ -103,7 +104,7 @@ pygments_style = 'sphinx'
|
||||||
|
|
||||||
# The theme to use for HTML and HTML Help pages. See the documentation for
|
# The theme to use for HTML and HTML Help pages. See the documentation for
|
||||||
# a list of builtin themes.
|
# a list of builtin themes.
|
||||||
html_theme = 'sphinx_rtd_theme'
|
html_theme = "sphinx_rtd_theme"
|
||||||
html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]
|
html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]
|
||||||
|
|
||||||
# Theme options are theme-specific and customize the look and feel of a theme
|
# Theme options are theme-specific and customize the look and feel of a theme
|
||||||
|
|
@ -133,7 +134,7 @@ html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]
|
||||||
# Add any paths that contain custom static files (such as style sheets) here,
|
# Add any paths that contain custom static files (such as style sheets) here,
|
||||||
# relative to this directory. They are copied after the builtin static files,
|
# relative to this directory. They are copied after the builtin static files,
|
||||||
# so a file named "default.css" will overwrite the builtin "default.css".
|
# so a file named "default.css" will overwrite the builtin "default.css".
|
||||||
html_static_path = ['_static']
|
html_static_path = ["_static"]
|
||||||
|
|
||||||
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
|
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
|
||||||
# using the given strftime format.
|
# using the given strftime format.
|
||||||
|
|
@ -177,7 +178,7 @@ html_static_path = ['_static']
|
||||||
# html_file_suffix = None
|
# html_file_suffix = None
|
||||||
|
|
||||||
# Output file base name for HTML help builder.
|
# Output file base name for HTML help builder.
|
||||||
htmlhelp_basename = 'django-polymorphicdoc'
|
htmlhelp_basename = "django-polymorphicdoc"
|
||||||
|
|
||||||
|
|
||||||
# -- Options for LaTeX output --------------------------------------------------
|
# -- Options for LaTeX output --------------------------------------------------
|
||||||
|
|
@ -185,10 +186,8 @@ htmlhelp_basename = 'django-polymorphicdoc'
|
||||||
latex_elements = {
|
latex_elements = {
|
||||||
# The paper size ('letterpaper' or 'a4paper').
|
# The paper size ('letterpaper' or 'a4paper').
|
||||||
#'papersize': 'letterpaper',
|
#'papersize': 'letterpaper',
|
||||||
|
|
||||||
# The font size ('10pt', '11pt' or '12pt').
|
# The font size ('10pt', '11pt' or '12pt').
|
||||||
#'pointsize': '10pt',
|
#'pointsize': '10pt',
|
||||||
|
|
||||||
# Additional stuff for the LaTeX preamble.
|
# Additional stuff for the LaTeX preamble.
|
||||||
#'preamble': '',
|
#'preamble': '',
|
||||||
}
|
}
|
||||||
|
|
@ -196,8 +195,13 @@ latex_elements = {
|
||||||
# Grouping the document tree into LaTeX files. List of tuples
|
# Grouping the document tree into LaTeX files. List of tuples
|
||||||
# (source start file, target name, title, author, documentclass [howto/manual]).
|
# (source start file, target name, title, author, documentclass [howto/manual]).
|
||||||
latex_documents = [
|
latex_documents = [
|
||||||
('index', 'django-polymorphic.tex', u'django-polymorphic Documentation',
|
(
|
||||||
u'Bert Constantin, Chris Glass, Diederik van der Boor', 'manual'),
|
"index",
|
||||||
|
"django-polymorphic.tex",
|
||||||
|
u"django-polymorphic Documentation",
|
||||||
|
u"Bert Constantin, Chris Glass, Diederik van der Boor",
|
||||||
|
"manual",
|
||||||
|
)
|
||||||
]
|
]
|
||||||
|
|
||||||
# The name of an image file (relative to this directory) to place at the top of
|
# The name of an image file (relative to this directory) to place at the top of
|
||||||
|
|
@ -226,8 +230,13 @@ latex_documents = [
|
||||||
# One entry per manual page. List of tuples
|
# One entry per manual page. List of tuples
|
||||||
# (source start file, name, description, authors, manual section).
|
# (source start file, name, description, authors, manual section).
|
||||||
man_pages = [
|
man_pages = [
|
||||||
('index', 'django-polymorphic', u'django-polymorphic Documentation',
|
(
|
||||||
[u'Bert Constantin, Chris Glass, Diederik van der Boor'], 1)
|
"index",
|
||||||
|
"django-polymorphic",
|
||||||
|
u"django-polymorphic Documentation",
|
||||||
|
[u"Bert Constantin, Chris Glass, Diederik van der Boor"],
|
||||||
|
1,
|
||||||
|
)
|
||||||
]
|
]
|
||||||
|
|
||||||
# If true, show URL addresses after external links.
|
# If true, show URL addresses after external links.
|
||||||
|
|
@ -240,9 +249,15 @@ man_pages = [
|
||||||
# (source start file, target name, title, author,
|
# (source start file, target name, title, author,
|
||||||
# dir menu entry, description, category)
|
# dir menu entry, description, category)
|
||||||
texinfo_documents = [
|
texinfo_documents = [
|
||||||
('index', 'django-polymorphic', u'django-polymorphic Documentation',
|
(
|
||||||
u'Bert Constantin, Chris Glass, Diederik van der Boor', 'django-polymorphic', 'One line description of project.',
|
"index",
|
||||||
'Miscellaneous'),
|
"django-polymorphic",
|
||||||
|
u"django-polymorphic Documentation",
|
||||||
|
u"Bert Constantin, Chris Glass, Diederik van der Boor",
|
||||||
|
"django-polymorphic",
|
||||||
|
"One line description of project.",
|
||||||
|
"Miscellaneous",
|
||||||
|
)
|
||||||
]
|
]
|
||||||
|
|
||||||
# Documents to append as an appendix to all manuals.
|
# Documents to append as an appendix to all manuals.
|
||||||
|
|
@ -258,8 +273,8 @@ texinfo_documents = [
|
||||||
# Example configuration for intersphinx: refer to the Python standard library.
|
# Example configuration for intersphinx: refer to the Python standard library.
|
||||||
intersphinx_mapping = {
|
intersphinx_mapping = {
|
||||||
#'http://docs.python.org/': None,
|
#'http://docs.python.org/': None,
|
||||||
'https://docs.djangoproject.com/en/stable': 'https://docs.djangoproject.com/en/stable/_objects',
|
"https://docs.djangoproject.com/en/stable": "https://docs.djangoproject.com/en/stable/_objects"
|
||||||
}
|
}
|
||||||
|
|
||||||
# autodoc settings
|
# autodoc settings
|
||||||
autodoc_member_order = 'groupwise'
|
autodoc_member_order = "groupwise"
|
||||||
|
|
|
||||||
|
|
@ -10,46 +10,47 @@ MANAGERS = ADMINS
|
||||||
PROJECT_ROOT = os.path.dirname(os.path.dirname(__file__))
|
PROJECT_ROOT = os.path.dirname(os.path.dirname(__file__))
|
||||||
|
|
||||||
DATABASES = {
|
DATABASES = {
|
||||||
'default': {
|
"default": {
|
||||||
'ENGINE': 'django.db.backends.sqlite3',
|
"ENGINE": "django.db.backends.sqlite3",
|
||||||
'NAME': os.path.join(PROJECT_ROOT, 'example.db'),
|
"NAME": os.path.join(PROJECT_ROOT, "example.db"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SITE_ID = 1
|
SITE_ID = 1
|
||||||
|
|
||||||
# Make this unique, and don't share it with anybody.
|
# Make this unique, and don't share it with anybody.
|
||||||
SECRET_KEY = '5$f%)&a4tc*bg(79+ku!7o$kri-duw99@hq_)va^_kaw9*l)!7'
|
SECRET_KEY = "5$f%)&a4tc*bg(79+ku!7o$kri-duw99@hq_)va^_kaw9*l)!7"
|
||||||
|
|
||||||
|
|
||||||
# Language
|
# Language
|
||||||
# TIME_ZONE = 'America/Chicago'
|
# TIME_ZONE = 'America/Chicago'
|
||||||
LANGUAGE_CODE = 'en-us'
|
LANGUAGE_CODE = "en-us"
|
||||||
USE_I18N = True
|
USE_I18N = True
|
||||||
USE_L10N = True
|
USE_L10N = True
|
||||||
USE_TZ = True
|
USE_TZ = True
|
||||||
|
|
||||||
# Paths
|
# Paths
|
||||||
MEDIA_ROOT = ''
|
MEDIA_ROOT = ""
|
||||||
MEDIA_URL = '/media/'
|
MEDIA_URL = "/media/"
|
||||||
STATIC_ROOT = ''
|
STATIC_ROOT = ""
|
||||||
STATIC_URL = '/static/'
|
STATIC_URL = "/static/"
|
||||||
|
|
||||||
# Apps
|
# Apps
|
||||||
STATICFILES_FINDERS = (
|
STATICFILES_FINDERS = (
|
||||||
'django.contrib.staticfiles.finders.FileSystemFinder',
|
"django.contrib.staticfiles.finders.FileSystemFinder",
|
||||||
'django.contrib.staticfiles.finders.AppDirectoriesFinder',
|
"django.contrib.staticfiles.finders.AppDirectoriesFinder",
|
||||||
)
|
)
|
||||||
|
|
||||||
MIDDLEWARE = (
|
MIDDLEWARE = (
|
||||||
'django.middleware.common.CommonMiddleware',
|
"django.middleware.common.CommonMiddleware",
|
||||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
"django.contrib.sessions.middleware.SessionMiddleware",
|
||||||
'django.middleware.csrf.CsrfViewMiddleware',
|
"django.middleware.csrf.CsrfViewMiddleware",
|
||||||
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
"django.contrib.auth.middleware.AuthenticationMiddleware",
|
||||||
'django.contrib.messages.middleware.MessageMiddleware',
|
"django.contrib.messages.middleware.MessageMiddleware",
|
||||||
)
|
)
|
||||||
|
|
||||||
TEMPLATES=[{
|
TEMPLATES = [
|
||||||
|
{
|
||||||
"BACKEND": "django.template.backends.django.DjangoTemplates",
|
"BACKEND": "django.template.backends.django.DjangoTemplates",
|
||||||
"DIRS": (),
|
"DIRS": (),
|
||||||
"OPTIONS": {
|
"OPTIONS": {
|
||||||
|
|
@ -67,48 +68,44 @@ TEMPLATES=[{
|
||||||
"django.contrib.auth.context_processors.auth",
|
"django.contrib.auth.context_processors.auth",
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
}]
|
}
|
||||||
|
]
|
||||||
|
|
||||||
ROOT_URLCONF = 'example.urls'
|
ROOT_URLCONF = "example.urls"
|
||||||
|
|
||||||
WSGI_APPLICATION = 'example.wsgi.application'
|
WSGI_APPLICATION = "example.wsgi.application"
|
||||||
|
|
||||||
INSTALLED_APPS = (
|
INSTALLED_APPS = (
|
||||||
'django.contrib.auth',
|
"django.contrib.auth",
|
||||||
'django.contrib.admin',
|
"django.contrib.admin",
|
||||||
'django.contrib.contenttypes',
|
"django.contrib.contenttypes",
|
||||||
'django.contrib.sessions',
|
"django.contrib.sessions",
|
||||||
'django.contrib.messages',
|
"django.contrib.messages",
|
||||||
'django.contrib.staticfiles',
|
"django.contrib.staticfiles",
|
||||||
|
"polymorphic", # needed if you want to use the polymorphic admin
|
||||||
'polymorphic', # needed if you want to use the polymorphic admin
|
"pexp", # this Django app is for testing and experimentation; not needed otherwise
|
||||||
'pexp', # this Django app is for testing and experimentation; not needed otherwise
|
"orders",
|
||||||
'orders',
|
|
||||||
)
|
)
|
||||||
|
|
||||||
TEST_RUNNER = 'django.test.runner.DiscoverRunner' # silence system checks
|
TEST_RUNNER = "django.test.runner.DiscoverRunner" # silence system checks
|
||||||
|
|
||||||
# Logging configuration
|
# Logging configuration
|
||||||
LOGGING = {
|
LOGGING = {
|
||||||
'version': 1,
|
"version": 1,
|
||||||
'disable_existing_loggers': False,
|
"disable_existing_loggers": False,
|
||||||
'filters': {
|
"filters": {"require_debug_false": {"()": "django.utils.log.RequireDebugFalse"}},
|
||||||
'require_debug_false': {
|
"handlers": {
|
||||||
'()': 'django.utils.log.RequireDebugFalse'
|
"mail_admins": {
|
||||||
|
"level": "ERROR",
|
||||||
|
"filters": ["require_debug_false"],
|
||||||
|
"class": "django.utils.log.AdminEmailHandler",
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
'handlers': {
|
"loggers": {
|
||||||
'mail_admins': {
|
"django.request": {
|
||||||
'level': 'ERROR',
|
"handlers": ["mail_admins"],
|
||||||
'filters': ['require_debug_false'],
|
"level": "ERROR",
|
||||||
'class': 'django.utils.log.AdminEmailHandler'
|
"propagate": True,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
'loggers': {
|
|
||||||
'django.request': {
|
|
||||||
'handlers': ['mail_admins'],
|
|
||||||
'level': 'ERROR',
|
|
||||||
'propagate': True,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,6 @@ from django.views.generic import RedirectView
|
||||||
admin.autodiscover()
|
admin.autodiscover()
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(r'^admin/', admin.site.urls),
|
url(r"^admin/", admin.site.urls),
|
||||||
url(r'^$', RedirectView.as_view(url=reverse_lazy('admin:index'), permanent=False)),
|
url(r"^$", RedirectView.as_view(url=reverse_lazy("admin:index"), permanent=False)),
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -15,12 +15,14 @@ framework.
|
||||||
"""
|
"""
|
||||||
import os
|
import os
|
||||||
|
|
||||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "example.settings")
|
|
||||||
|
|
||||||
# This application object is used by any WSGI server configured to use this
|
# This application object is used by any WSGI server configured to use this
|
||||||
# file. This includes Django's development server, if the WSGI_APPLICATION
|
# file. This includes Django's development server, if the WSGI_APPLICATION
|
||||||
# setting points here.
|
# setting points here.
|
||||||
from django.core.wsgi import get_wsgi_application
|
from django.core.wsgi import get_wsgi_application
|
||||||
|
|
||||||
|
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "example.settings")
|
||||||
|
|
||||||
|
|
||||||
application = get_wsgi_application()
|
application = get_wsgi_application()
|
||||||
|
|
||||||
# Apply WSGI middleware here.
|
# Apply WSGI middleware here.
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,8 @@
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
|
|
||||||
from polymorphic.admin import PolymorphicInlineSupportMixin, StackedPolymorphicInline
|
from polymorphic.admin import PolymorphicInlineSupportMixin, StackedPolymorphicInline
|
||||||
from .models import Order, Payment, CreditCardPayment, BankPayment, SepaPayment
|
|
||||||
|
from .models import BankPayment, CreditCardPayment, Order, Payment, SepaPayment
|
||||||
|
|
||||||
|
|
||||||
class CreditCardPaymentInline(StackedPolymorphicInline.Child):
|
class CreditCardPaymentInline(StackedPolymorphicInline.Child):
|
||||||
|
|
@ -24,11 +25,7 @@ class PaymentInline(StackedPolymorphicInline):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
model = Payment
|
model = Payment
|
||||||
child_inlines = (
|
child_inlines = (CreditCardPaymentInline, BankPaymentInline, SepaPaymentInline)
|
||||||
CreditCardPaymentInline,
|
|
||||||
BankPaymentInline,
|
|
||||||
SepaPaymentInline,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@admin.register(Order)
|
@admin.register(Order)
|
||||||
|
|
@ -39,5 +36,5 @@ class OrderAdmin(PolymorphicInlineSupportMixin, admin.ModelAdmin):
|
||||||
To make sure the inlines are properly handled,
|
To make sure the inlines are properly handled,
|
||||||
the ``PolymorphicInlineSupportMixin`` is needed to
|
the ``PolymorphicInlineSupportMixin`` is needed to
|
||||||
"""
|
"""
|
||||||
inlines = (PaymentInline,)
|
|
||||||
|
|
||||||
|
inlines = (PaymentInline,)
|
||||||
|
|
|
||||||
|
|
@ -6,83 +6,148 @@ from django.db import migrations, models
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [("contenttypes", "0002_remove_content_type_name")]
|
||||||
('contenttypes', '0002_remove_content_type_name'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='Order',
|
name="Order",
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
(
|
||||||
('title', models.CharField(max_length=200, verbose_name='Title')),
|
"id",
|
||||||
|
models.AutoField(
|
||||||
|
verbose_name="ID",
|
||||||
|
serialize=False,
|
||||||
|
auto_created=True,
|
||||||
|
primary_key=True,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("title", models.CharField(max_length=200, verbose_name="Title")),
|
||||||
],
|
],
|
||||||
options={
|
options={
|
||||||
'ordering': ('title',),
|
"ordering": ("title",),
|
||||||
'verbose_name': 'Organisation',
|
"verbose_name": "Organisation",
|
||||||
'verbose_name_plural': 'Organisations',
|
"verbose_name_plural": "Organisations",
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='Payment',
|
name="Payment",
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
(
|
||||||
('currency', models.CharField(default=b'USD', max_length=3)),
|
"id",
|
||||||
('amount', models.DecimalField(max_digits=10, decimal_places=2)),
|
models.AutoField(
|
||||||
|
verbose_name="ID",
|
||||||
|
serialize=False,
|
||||||
|
auto_created=True,
|
||||||
|
primary_key=True,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("currency", models.CharField(default=b"USD", max_length=3)),
|
||||||
|
("amount", models.DecimalField(max_digits=10, decimal_places=2)),
|
||||||
],
|
],
|
||||||
options={
|
options={"verbose_name": "Payment", "verbose_name_plural": "Payments"},
|
||||||
'verbose_name': 'Payment',
|
|
||||||
'verbose_name_plural': 'Payments',
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='BankPayment',
|
name="BankPayment",
|
||||||
fields=[
|
fields=[
|
||||||
('payment_ptr', models.OneToOneField(parent_link=True, auto_created=True, primary_key=True, serialize=False, on_delete=models.CASCADE, to='orders.Payment')),
|
(
|
||||||
('bank_name', models.CharField(max_length=100)),
|
"payment_ptr",
|
||||||
('swift', models.CharField(max_length=20)),
|
models.OneToOneField(
|
||||||
|
parent_link=True,
|
||||||
|
auto_created=True,
|
||||||
|
primary_key=True,
|
||||||
|
serialize=False,
|
||||||
|
on_delete=models.CASCADE,
|
||||||
|
to="orders.Payment",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("bank_name", models.CharField(max_length=100)),
|
||||||
|
("swift", models.CharField(max_length=20)),
|
||||||
],
|
],
|
||||||
options={
|
options={
|
||||||
'verbose_name': 'Bank Payment',
|
"verbose_name": "Bank Payment",
|
||||||
'verbose_name_plural': 'Bank Payments',
|
"verbose_name_plural": "Bank Payments",
|
||||||
},
|
},
|
||||||
bases=('orders.payment',),
|
bases=("orders.payment",),
|
||||||
),
|
),
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='CreditCardPayment',
|
name="CreditCardPayment",
|
||||||
fields=[
|
fields=[
|
||||||
('payment_ptr', models.OneToOneField(parent_link=True, auto_created=True, primary_key=True, serialize=False, on_delete=models.CASCADE, to='orders.Payment')),
|
(
|
||||||
('card_type', models.CharField(max_length=10)),
|
"payment_ptr",
|
||||||
('expiry_month', models.PositiveSmallIntegerField(choices=[(1, 'jan'), (2, 'feb'), (3, 'mar'), (4, 'apr'), (5, 'may'), (6, 'jun'), (7, 'jul'), (8, 'aug'), (9, 'sep'), (10, 'oct'), (11, 'nov'), (12, 'dec')])),
|
models.OneToOneField(
|
||||||
('expiry_year', models.PositiveIntegerField()),
|
parent_link=True,
|
||||||
|
auto_created=True,
|
||||||
|
primary_key=True,
|
||||||
|
serialize=False,
|
||||||
|
on_delete=models.CASCADE,
|
||||||
|
to="orders.Payment",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("card_type", models.CharField(max_length=10)),
|
||||||
|
(
|
||||||
|
"expiry_month",
|
||||||
|
models.PositiveSmallIntegerField(
|
||||||
|
choices=[
|
||||||
|
(1, "jan"),
|
||||||
|
(2, "feb"),
|
||||||
|
(3, "mar"),
|
||||||
|
(4, "apr"),
|
||||||
|
(5, "may"),
|
||||||
|
(6, "jun"),
|
||||||
|
(7, "jul"),
|
||||||
|
(8, "aug"),
|
||||||
|
(9, "sep"),
|
||||||
|
(10, "oct"),
|
||||||
|
(11, "nov"),
|
||||||
|
(12, "dec"),
|
||||||
|
]
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("expiry_year", models.PositiveIntegerField()),
|
||||||
],
|
],
|
||||||
options={
|
options={
|
||||||
'verbose_name': 'Credit Card Payment',
|
"verbose_name": "Credit Card Payment",
|
||||||
'verbose_name_plural': 'Credit Card Payments',
|
"verbose_name_plural": "Credit Card Payments",
|
||||||
},
|
},
|
||||||
bases=('orders.payment',),
|
bases=("orders.payment",),
|
||||||
),
|
),
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='SepaPayment',
|
name="SepaPayment",
|
||||||
fields=[
|
fields=[
|
||||||
('payment_ptr', models.OneToOneField(parent_link=True, auto_created=True, primary_key=True, serialize=False, on_delete=models.CASCADE, to='orders.Payment')),
|
(
|
||||||
('iban', models.CharField(max_length=34)),
|
"payment_ptr",
|
||||||
('bic', models.CharField(max_length=11)),
|
models.OneToOneField(
|
||||||
|
parent_link=True,
|
||||||
|
auto_created=True,
|
||||||
|
primary_key=True,
|
||||||
|
serialize=False,
|
||||||
|
on_delete=models.CASCADE,
|
||||||
|
to="orders.Payment",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("iban", models.CharField(max_length=34)),
|
||||||
|
("bic", models.CharField(max_length=11)),
|
||||||
],
|
],
|
||||||
options={
|
options={
|
||||||
'verbose_name': 'Bank Payment',
|
"verbose_name": "Bank Payment",
|
||||||
'verbose_name_plural': 'Bank Payments',
|
"verbose_name_plural": "Bank Payments",
|
||||||
},
|
},
|
||||||
bases=('orders.payment',),
|
bases=("orders.payment",),
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='payment',
|
model_name="payment",
|
||||||
name='order',
|
name="order",
|
||||||
field=models.ForeignKey(to='orders.Order', on_delete=models.CASCADE),
|
field=models.ForeignKey(to="orders.Order", on_delete=models.CASCADE),
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='payment',
|
model_name="payment",
|
||||||
name='polymorphic_ctype',
|
name="polymorphic_ctype",
|
||||||
field=models.ForeignKey(related_name='polymorphic_orders.payment_set+', editable=False, on_delete=models.CASCADE, to='contenttypes.ContentType', null=True),
|
field=models.ForeignKey(
|
||||||
|
related_name="polymorphic_orders.payment_set+",
|
||||||
|
editable=False,
|
||||||
|
on_delete=models.CASCADE,
|
||||||
|
to="contenttypes.ContentType",
|
||||||
|
null=True,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -11,12 +11,13 @@ class Order(models.Model):
|
||||||
"""
|
"""
|
||||||
An example order that has polymorphic relations
|
An example order that has polymorphic relations
|
||||||
"""
|
"""
|
||||||
|
|
||||||
title = models.CharField(_("Title"), max_length=200)
|
title = models.CharField(_("Title"), max_length=200)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = _("Organisation")
|
verbose_name = _("Organisation")
|
||||||
verbose_name_plural = _("Organisations")
|
verbose_name_plural = _("Organisations")
|
||||||
ordering = ('title',)
|
ordering = ("title",)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.title
|
return self.title
|
||||||
|
|
@ -27,8 +28,9 @@ class Payment(PolymorphicModel):
|
||||||
"""
|
"""
|
||||||
A generic payment model.
|
A generic payment model.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
order = models.ForeignKey(Order, on_delete=models.CASCADE)
|
order = models.ForeignKey(Order, on_delete=models.CASCADE)
|
||||||
currency = models.CharField(default='USD', max_length=3)
|
currency = models.CharField(default="USD", max_length=3)
|
||||||
amount = models.DecimalField(max_digits=10, decimal_places=2)
|
amount = models.DecimalField(max_digits=10, decimal_places=2)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
|
@ -43,6 +45,7 @@ class CreditCardPayment(Payment):
|
||||||
"""
|
"""
|
||||||
Credit card
|
Credit card
|
||||||
"""
|
"""
|
||||||
|
|
||||||
MONTH_CHOICES = [(i, n) for i, n in sorted(MONTHS_3.items())]
|
MONTH_CHOICES = [(i, n) for i, n in sorted(MONTHS_3.items())]
|
||||||
|
|
||||||
card_type = models.CharField(max_length=10)
|
card_type = models.CharField(max_length=10)
|
||||||
|
|
@ -58,6 +61,7 @@ class BankPayment(Payment):
|
||||||
"""
|
"""
|
||||||
Payment by bank
|
Payment by bank
|
||||||
"""
|
"""
|
||||||
|
|
||||||
bank_name = models.CharField(max_length=100)
|
bank_name = models.CharField(max_length=100)
|
||||||
swift = models.CharField(max_length=20)
|
swift = models.CharField(max_length=20)
|
||||||
|
|
||||||
|
|
@ -70,6 +74,7 @@ class SepaPayment(Payment):
|
||||||
"""
|
"""
|
||||||
Payment by SEPA (EU)
|
Payment by SEPA (EU)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
iban = models.CharField(max_length=34)
|
iban = models.CharField(max_length=34)
|
||||||
bic = models.CharField(max_length=11)
|
bic = models.CharField(max_length=11)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,11 @@
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from polymorphic.admin import PolymorphicParentModelAdmin, PolymorphicChildModelAdmin, PolymorphicChildModelFilter
|
|
||||||
from pexp.models import *
|
from pexp.models import *
|
||||||
|
from polymorphic.admin import (
|
||||||
|
PolymorphicChildModelAdmin,
|
||||||
|
PolymorphicChildModelFilter,
|
||||||
|
PolymorphicParentModelAdmin,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class ProjectAdmin(PolymorphicParentModelAdmin):
|
class ProjectAdmin(PolymorphicParentModelAdmin):
|
||||||
|
|
@ -14,11 +19,7 @@ class ProjectChildAdmin(PolymorphicChildModelAdmin):
|
||||||
|
|
||||||
# On purpose, only have the shared fields here.
|
# On purpose, only have the shared fields here.
|
||||||
# The fields of the derived model should still be displayed.
|
# The fields of the derived model should still be displayed.
|
||||||
base_fieldsets = (
|
base_fieldsets = (("Base fields", {"fields": ("topic",)}),)
|
||||||
("Base fields", {
|
|
||||||
'fields': ('topic',)
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
admin.site.register(Project, ProjectAdmin)
|
admin.site.register(Project, ProjectAdmin)
|
||||||
|
|
|
||||||
|
|
@ -7,12 +7,12 @@ import sys
|
||||||
import time
|
import time
|
||||||
from pprint import pprint
|
from pprint import pprint
|
||||||
from random import Random
|
from random import Random
|
||||||
|
|
||||||
from django.core.management import BaseCommand
|
from django.core.management import BaseCommand
|
||||||
from django.db import connection
|
from django.db import connection
|
||||||
|
|
||||||
from pexp.models import *
|
from pexp.models import *
|
||||||
|
|
||||||
|
|
||||||
rnd = Random()
|
rnd = Random()
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -24,7 +24,7 @@ def show_queries():
|
||||||
connection.queries = []
|
connection.queries = []
|
||||||
|
|
||||||
|
|
||||||
def print_timing(func, message='', iterations=1):
|
def print_timing(func, message="", iterations=1):
|
||||||
def wrapper(*arg):
|
def wrapper(*arg):
|
||||||
results = []
|
results = []
|
||||||
connection.queries_log.clear()
|
connection.queries_log.clear()
|
||||||
|
|
@ -36,13 +36,12 @@ def print_timing(func, message='', iterations=1):
|
||||||
res_sum = 0
|
res_sum = 0
|
||||||
for r in results:
|
for r in results:
|
||||||
res_sum += r
|
res_sum += r
|
||||||
print("%s%-19s: %.4f ms, %i queries (%i times)" % (
|
print(
|
||||||
message, func.func_name,
|
"%s%-19s: %.4f ms, %i queries (%i times)"
|
||||||
res_sum,
|
% (message, func.func_name, res_sum, len(connection.queries), iterations)
|
||||||
len(connection.queries),
|
)
|
||||||
iterations
|
|
||||||
))
|
|
||||||
sys.stdout.flush()
|
sys.stdout.flush()
|
||||||
|
|
||||||
return wrapper
|
return wrapper
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -52,9 +51,9 @@ class Command(BaseCommand):
|
||||||
def handle_noargs(self, **options):
|
def handle_noargs(self, **options):
|
||||||
if False:
|
if False:
|
||||||
TestModelA.objects.all().delete()
|
TestModelA.objects.all().delete()
|
||||||
a = TestModelA.objects.create(field1='A1')
|
a = TestModelA.objects.create(field1="A1")
|
||||||
b = TestModelB.objects.create(field1='B1', field2='B2')
|
b = TestModelB.objects.create(field1="B1", field2="B2")
|
||||||
c = TestModelC.objects.create(field1='C1', field2='C2', field3='C3')
|
c = TestModelC.objects.create(field1="C1", field2="C2", field3="C3")
|
||||||
connection.queries_log.clear()
|
connection.queries_log.clear()
|
||||||
print(TestModelC.base_objects.all())
|
print(TestModelC.base_objects.all())
|
||||||
show_queries()
|
show_queries()
|
||||||
|
|
@ -64,7 +63,9 @@ class Command(BaseCommand):
|
||||||
for i in range(1000):
|
for i in range(1000):
|
||||||
a = TestModelA.objects.create(field1=str(i % 100))
|
a = TestModelA.objects.create(field1=str(i % 100))
|
||||||
b = TestModelB.objects.create(field1=str(i % 100), field2=str(i % 200))
|
b = TestModelB.objects.create(field1=str(i % 100), field2=str(i % 200))
|
||||||
c = TestModelC.objects.create(field1=str(i % 100), field2=str(i % 200), field3=str(i % 300))
|
c = TestModelC.objects.create(
|
||||||
|
field1=str(i % 100), field2=str(i % 200), field3=str(i % 300)
|
||||||
|
)
|
||||||
if i % 100 == 0:
|
if i % 100 == 0:
|
||||||
print(i)
|
print(i)
|
||||||
|
|
||||||
|
|
@ -77,9 +78,9 @@ class Command(BaseCommand):
|
||||||
return
|
return
|
||||||
|
|
||||||
NormalModelA.objects.all().delete()
|
NormalModelA.objects.all().delete()
|
||||||
a = NormalModelA.objects.create(field1='A1')
|
a = NormalModelA.objects.create(field1="A1")
|
||||||
b = NormalModelB.objects.create(field1='B1', field2='B2')
|
b = NormalModelB.objects.create(field1="B1", field2="B2")
|
||||||
c = NormalModelC.objects.create(field1='C1', field2='C2', field3='C3')
|
c = NormalModelC.objects.create(field1="C1", field2="C2", field3="C3")
|
||||||
qs = TestModelA.objects.raw("SELECT * from pexp_testmodela")
|
qs = TestModelA.objects.raw("SELECT * from pexp_testmodela")
|
||||||
for o in list(qs):
|
for o in list(qs):
|
||||||
print(o)
|
print(o)
|
||||||
|
|
@ -87,7 +88,8 @@ class Command(BaseCommand):
|
||||||
|
|
||||||
def poly_sql_query():
|
def poly_sql_query():
|
||||||
cursor = connection.cursor()
|
cursor = connection.cursor()
|
||||||
cursor.execute("""
|
cursor.execute(
|
||||||
|
"""
|
||||||
SELECT id, pexp_testmodela.field1, pexp_testmodelb.field2, pexp_testmodelc.field3
|
SELECT id, pexp_testmodela.field1, pexp_testmodelb.field2, pexp_testmodelc.field3
|
||||||
FROM pexp_testmodela
|
FROM pexp_testmodela
|
||||||
LEFT OUTER JOIN pexp_testmodelb
|
LEFT OUTER JOIN pexp_testmodelb
|
||||||
|
|
@ -96,18 +98,23 @@ def poly_sql_query():
|
||||||
ON pexp_testmodelb.testmodela_ptr_id = pexp_testmodelc.testmodelb_ptr_id
|
ON pexp_testmodelb.testmodela_ptr_id = pexp_testmodelc.testmodelb_ptr_id
|
||||||
WHERE pexp_testmodela.field1=%i
|
WHERE pexp_testmodela.field1=%i
|
||||||
ORDER BY pexp_testmodela.id
|
ORDER BY pexp_testmodela.id
|
||||||
""" % rnd.randint(0, 100))
|
"""
|
||||||
|
% rnd.randint(0, 100)
|
||||||
|
)
|
||||||
# row=cursor.fetchone()
|
# row=cursor.fetchone()
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
||||||
def poly_sql_query2():
|
def poly_sql_query2():
|
||||||
cursor = connection.cursor()
|
cursor = connection.cursor()
|
||||||
cursor.execute("""
|
cursor.execute(
|
||||||
|
"""
|
||||||
SELECT id, pexp_testmodela.field1
|
SELECT id, pexp_testmodela.field1
|
||||||
FROM pexp_testmodela
|
FROM pexp_testmodela
|
||||||
WHERE pexp_testmodela.field1=%i
|
WHERE pexp_testmodela.field1=%i
|
||||||
ORDER BY pexp_testmodela.id
|
ORDER BY pexp_testmodela.id
|
||||||
""" % rnd.randint(0, 100))
|
"""
|
||||||
|
% rnd.randint(0, 100)
|
||||||
|
)
|
||||||
# row=cursor.fetchone()
|
# row=cursor.fetchone()
|
||||||
return
|
return
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
"""
|
||||||
This module is a scratchpad for general development, testing & debugging.
|
This module is a scratchpad for general development, testing & debugging.
|
||||||
"""
|
"""
|
||||||
|
|
@ -20,13 +19,15 @@ class Command(NoArgsCommand):
|
||||||
Project.objects.all().delete()
|
Project.objects.all().delete()
|
||||||
a = Project.objects.create(topic="John's gathering")
|
a = Project.objects.create(topic="John's gathering")
|
||||||
b = ArtProject.objects.create(topic="Sculpting with Tim", artist="T. Turner")
|
b = ArtProject.objects.create(topic="Sculpting with Tim", artist="T. Turner")
|
||||||
c = ResearchProject.objects.create(topic="Swallow Aerodynamics", supervisor="Dr. Winter")
|
c = ResearchProject.objects.create(
|
||||||
print Project.objects.all()
|
topic="Swallow Aerodynamics", supervisor="Dr. Winter"
|
||||||
print
|
)
|
||||||
|
print(Project.objects.all())
|
||||||
|
print("")
|
||||||
|
|
||||||
TestModelA.objects.all().delete()
|
TestModelA.objects.all().delete()
|
||||||
a = TestModelA.objects.create(field1='A1')
|
a = TestModelA.objects.create(field1="A1")
|
||||||
b = TestModelB.objects.create(field1='B1', field2='B2')
|
b = TestModelB.objects.create(field1="B1", field2="B2")
|
||||||
c = TestModelC.objects.create(field1='C1', field2='C2', field3='C3')
|
c = TestModelC.objects.create(field1="C1", field2="C2", field3="C3")
|
||||||
print TestModelA.objects.all()
|
print(TestModelA.objects.all())
|
||||||
print
|
print("")
|
||||||
|
|
|
||||||
|
|
@ -3,14 +3,14 @@
|
||||||
This module is a scratchpad for general development, testing & debugging
|
This module is a scratchpad for general development, testing & debugging
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import time
|
|
||||||
import sys
|
import sys
|
||||||
|
import time
|
||||||
|
from pprint import pprint
|
||||||
|
|
||||||
from django.core.management import BaseCommand
|
from django.core.management import BaseCommand
|
||||||
from django.db import connection
|
from django.db import connection
|
||||||
from pprint import pprint
|
|
||||||
from pexp.models import *
|
|
||||||
|
|
||||||
|
from pexp.models import *
|
||||||
|
|
||||||
num_objects = 1000
|
num_objects = 1000
|
||||||
|
|
||||||
|
|
@ -27,7 +27,7 @@ def show_queries():
|
||||||
# benchmark wrappers
|
# benchmark wrappers
|
||||||
|
|
||||||
|
|
||||||
def print_timing(func, message='', iterations=1):
|
def print_timing(func, message="", iterations=1):
|
||||||
def wrapper(*arg):
|
def wrapper(*arg):
|
||||||
results = []
|
results = []
|
||||||
connection.queries_log.clear()
|
connection.queries_log.clear()
|
||||||
|
|
@ -40,28 +40,31 @@ def print_timing(func, message='', iterations=1):
|
||||||
for r in results:
|
for r in results:
|
||||||
res_sum += r
|
res_sum += r
|
||||||
median = res_sum / len(results)
|
median = res_sum / len(results)
|
||||||
print("%s%-19s: %.0f ms, %i queries" % (
|
print(
|
||||||
message, func.func_name,
|
"%s%-19s: %.0f ms, %i queries"
|
||||||
median,
|
% (message, func.func_name, median, len(connection.queries) / len(results))
|
||||||
len(connection.queries) / len(results)
|
)
|
||||||
))
|
|
||||||
sys.stdout.flush()
|
sys.stdout.flush()
|
||||||
|
|
||||||
return wrapper
|
return wrapper
|
||||||
|
|
||||||
|
|
||||||
def run_vanilla_any_poly(func, iterations=1):
|
def run_vanilla_any_poly(func, iterations=1):
|
||||||
f = print_timing(func, ' ', iterations)
|
f = print_timing(func, " ", iterations)
|
||||||
f(NormalModelC)
|
f(NormalModelC)
|
||||||
f = print_timing(func, 'poly ', iterations)
|
f = print_timing(func, "poly ", iterations)
|
||||||
f(TestModelC)
|
f(TestModelC)
|
||||||
|
|
||||||
|
|
||||||
###################################################################################
|
###################################################################################
|
||||||
# benchmarks
|
# benchmarks
|
||||||
|
|
||||||
|
|
||||||
def bench_create(model):
|
def bench_create(model):
|
||||||
for i in range(num_objects):
|
for i in range(num_objects):
|
||||||
model.objects.create(field1='abc' + str(i), field2='abcd' + str(i), field3='abcde' + str(i))
|
model.objects.create(
|
||||||
|
field1="abc" + str(i), field2="abcd" + str(i), field3="abcde" + str(i)
|
||||||
|
)
|
||||||
# print 'count:',model.objects.count()
|
# print 'count:',model.objects.count()
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -94,6 +97,7 @@ def bench_load2_short(model):
|
||||||
def bench_delete(model):
|
def bench_delete(model):
|
||||||
model.objects.all().delete()
|
model.objects.all().delete()
|
||||||
|
|
||||||
|
|
||||||
###################################################################################
|
###################################################################################
|
||||||
# Command
|
# Command
|
||||||
|
|
||||||
|
|
@ -108,7 +112,7 @@ class Command(BaseCommand):
|
||||||
(bench_load1, 5),
|
(bench_load1, 5),
|
||||||
(bench_load1_short, 5),
|
(bench_load1_short, 5),
|
||||||
(bench_load2, 5),
|
(bench_load2, 5),
|
||||||
(bench_load2_short, 5)
|
(bench_load2_short, 5),
|
||||||
]
|
]
|
||||||
for f, iterations in func_list:
|
for f, iterations in func_list:
|
||||||
run_vanilla_any_poly(f, iterations=iterations)
|
run_vanilla_any_poly(f, iterations=iterations)
|
||||||
|
|
|
||||||
|
|
@ -15,5 +15,7 @@ class Command(BaseCommand):
|
||||||
Project.objects.all().delete()
|
Project.objects.all().delete()
|
||||||
o = Project.objects.create(topic="John's gathering")
|
o = Project.objects.create(topic="John's gathering")
|
||||||
o = ArtProject.objects.create(topic="Sculpting with Tim", artist="T. Turner")
|
o = ArtProject.objects.create(topic="Sculpting with Tim", artist="T. Turner")
|
||||||
o = ResearchProject.objects.create(topic="Swallow Aerodynamics", supervisor="Dr. Winter")
|
o = ResearchProject.objects.create(
|
||||||
|
topic="Swallow Aerodynamics", supervisor="Dr. Winter"
|
||||||
|
)
|
||||||
print(Project.objects.all())
|
print(Project.objects.all())
|
||||||
|
|
|
||||||
|
|
@ -2,181 +2,295 @@
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
|
|
||||||
import polymorphic.showfields
|
import polymorphic.showfields
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [("contenttypes", "0002_remove_content_type_name")]
|
||||||
('contenttypes', '0002_remove_content_type_name'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='NormalModelA',
|
name="NormalModelA",
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
(
|
||||||
('field1', models.CharField(max_length=10)),
|
"id",
|
||||||
|
models.AutoField(
|
||||||
|
verbose_name="ID",
|
||||||
|
serialize=False,
|
||||||
|
auto_created=True,
|
||||||
|
primary_key=True,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("field1", models.CharField(max_length=10)),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='Project',
|
name="Project",
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
(
|
||||||
('topic', models.CharField(max_length=30)),
|
"id",
|
||||||
|
models.AutoField(
|
||||||
|
verbose_name="ID",
|
||||||
|
serialize=False,
|
||||||
|
auto_created=True,
|
||||||
|
primary_key=True,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("topic", models.CharField(max_length=30)),
|
||||||
],
|
],
|
||||||
options={
|
options={"abstract": False},
|
||||||
'abstract': False,
|
|
||||||
},
|
|
||||||
bases=(polymorphic.showfields.ShowFieldContent, models.Model),
|
bases=(polymorphic.showfields.ShowFieldContent, models.Model),
|
||||||
),
|
),
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='ProxyBase',
|
name="ProxyBase",
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
(
|
||||||
('title', models.CharField(max_length=200)),
|
"id",
|
||||||
('polymorphic_ctype', models.ForeignKey(related_name='polymorphic_pexp.proxybase_set+', editable=False, on_delete=models.CASCADE, to='contenttypes.ContentType', null=True)),
|
models.AutoField(
|
||||||
|
verbose_name="ID",
|
||||||
|
serialize=False,
|
||||||
|
auto_created=True,
|
||||||
|
primary_key=True,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("title", models.CharField(max_length=200)),
|
||||||
|
(
|
||||||
|
"polymorphic_ctype",
|
||||||
|
models.ForeignKey(
|
||||||
|
related_name="polymorphic_pexp.proxybase_set+",
|
||||||
|
editable=False,
|
||||||
|
on_delete=models.CASCADE,
|
||||||
|
to="contenttypes.ContentType",
|
||||||
|
null=True,
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
options={
|
options={"ordering": ("title",)},
|
||||||
'ordering': ('title',),
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='TestModelA',
|
name="TestModelA",
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
(
|
||||||
('field1', models.CharField(max_length=10)),
|
"id",
|
||||||
|
models.AutoField(
|
||||||
|
verbose_name="ID",
|
||||||
|
serialize=False,
|
||||||
|
auto_created=True,
|
||||||
|
primary_key=True,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("field1", models.CharField(max_length=10)),
|
||||||
],
|
],
|
||||||
options={
|
options={"abstract": False},
|
||||||
'abstract': False,
|
|
||||||
},
|
|
||||||
bases=(polymorphic.showfields.ShowFieldTypeAndContent, models.Model),
|
bases=(polymorphic.showfields.ShowFieldTypeAndContent, models.Model),
|
||||||
),
|
),
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='UUIDModelA',
|
name="UUIDModelA",
|
||||||
fields=[
|
fields=[
|
||||||
('uuid_primary_key', models.UUIDField(serialize=False, primary_key=True)),
|
(
|
||||||
('field1', models.CharField(max_length=10)),
|
"uuid_primary_key",
|
||||||
|
models.UUIDField(serialize=False, primary_key=True),
|
||||||
|
),
|
||||||
|
("field1", models.CharField(max_length=10)),
|
||||||
],
|
],
|
||||||
options={
|
options={"abstract": False},
|
||||||
'abstract': False,
|
|
||||||
},
|
|
||||||
bases=(polymorphic.showfields.ShowFieldTypeAndContent, models.Model),
|
bases=(polymorphic.showfields.ShowFieldTypeAndContent, models.Model),
|
||||||
),
|
),
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='ArtProject',
|
name="ArtProject",
|
||||||
fields=[
|
fields=[
|
||||||
('project_ptr', models.OneToOneField(parent_link=True, auto_created=True, primary_key=True, serialize=False, on_delete=models.CASCADE, to='pexp.Project')),
|
(
|
||||||
('artist', models.CharField(max_length=30)),
|
"project_ptr",
|
||||||
|
models.OneToOneField(
|
||||||
|
parent_link=True,
|
||||||
|
auto_created=True,
|
||||||
|
primary_key=True,
|
||||||
|
serialize=False,
|
||||||
|
on_delete=models.CASCADE,
|
||||||
|
to="pexp.Project",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("artist", models.CharField(max_length=30)),
|
||||||
],
|
],
|
||||||
options={
|
options={"abstract": False},
|
||||||
'abstract': False,
|
bases=("pexp.project",),
|
||||||
},
|
|
||||||
bases=('pexp.project',),
|
|
||||||
),
|
),
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='NormalModelB',
|
name="NormalModelB",
|
||||||
fields=[
|
fields=[
|
||||||
('normalmodela_ptr', models.OneToOneField(parent_link=True, auto_created=True, primary_key=True, serialize=False, on_delete=models.CASCADE, to='pexp.NormalModelA')),
|
(
|
||||||
('field2', models.CharField(max_length=10)),
|
"normalmodela_ptr",
|
||||||
|
models.OneToOneField(
|
||||||
|
parent_link=True,
|
||||||
|
auto_created=True,
|
||||||
|
primary_key=True,
|
||||||
|
serialize=False,
|
||||||
|
on_delete=models.CASCADE,
|
||||||
|
to="pexp.NormalModelA",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("field2", models.CharField(max_length=10)),
|
||||||
],
|
],
|
||||||
bases=('pexp.normalmodela',),
|
bases=("pexp.normalmodela",),
|
||||||
),
|
),
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='ResearchProject',
|
name="ResearchProject",
|
||||||
fields=[
|
fields=[
|
||||||
('project_ptr', models.OneToOneField(parent_link=True, auto_created=True, primary_key=True, serialize=False, on_delete=models.CASCADE, to='pexp.Project')),
|
(
|
||||||
('supervisor', models.CharField(max_length=30)),
|
"project_ptr",
|
||||||
|
models.OneToOneField(
|
||||||
|
parent_link=True,
|
||||||
|
auto_created=True,
|
||||||
|
primary_key=True,
|
||||||
|
serialize=False,
|
||||||
|
on_delete=models.CASCADE,
|
||||||
|
to="pexp.Project",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("supervisor", models.CharField(max_length=30)),
|
||||||
],
|
],
|
||||||
options={
|
options={"abstract": False},
|
||||||
'abstract': False,
|
bases=("pexp.project",),
|
||||||
},
|
|
||||||
bases=('pexp.project',),
|
|
||||||
),
|
),
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='TestModelB',
|
name="TestModelB",
|
||||||
fields=[
|
fields=[
|
||||||
('testmodela_ptr', models.OneToOneField(parent_link=True, auto_created=True, primary_key=True, serialize=False, on_delete=models.CASCADE, to='pexp.TestModelA')),
|
(
|
||||||
('field2', models.CharField(max_length=10)),
|
"testmodela_ptr",
|
||||||
|
models.OneToOneField(
|
||||||
|
parent_link=True,
|
||||||
|
auto_created=True,
|
||||||
|
primary_key=True,
|
||||||
|
serialize=False,
|
||||||
|
on_delete=models.CASCADE,
|
||||||
|
to="pexp.TestModelA",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("field2", models.CharField(max_length=10)),
|
||||||
],
|
],
|
||||||
options={
|
options={"abstract": False},
|
||||||
'abstract': False,
|
bases=("pexp.testmodela",),
|
||||||
},
|
|
||||||
bases=('pexp.testmodela',),
|
|
||||||
),
|
),
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='UUIDModelB',
|
name="UUIDModelB",
|
||||||
fields=[
|
fields=[
|
||||||
('uuidmodela_ptr', models.OneToOneField(parent_link=True, auto_created=True, primary_key=True, serialize=False, on_delete=models.CASCADE, to='pexp.UUIDModelA')),
|
(
|
||||||
('field2', models.CharField(max_length=10)),
|
"uuidmodela_ptr",
|
||||||
|
models.OneToOneField(
|
||||||
|
parent_link=True,
|
||||||
|
auto_created=True,
|
||||||
|
primary_key=True,
|
||||||
|
serialize=False,
|
||||||
|
on_delete=models.CASCADE,
|
||||||
|
to="pexp.UUIDModelA",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("field2", models.CharField(max_length=10)),
|
||||||
],
|
],
|
||||||
options={
|
options={"abstract": False},
|
||||||
'abstract': False,
|
bases=("pexp.uuidmodela",),
|
||||||
},
|
|
||||||
bases=('pexp.uuidmodela',),
|
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='uuidmodela',
|
model_name="uuidmodela",
|
||||||
name='polymorphic_ctype',
|
name="polymorphic_ctype",
|
||||||
field=models.ForeignKey(related_name='polymorphic_pexp.uuidmodela_set+', editable=False, on_delete=models.CASCADE, to='contenttypes.ContentType', null=True),
|
field=models.ForeignKey(
|
||||||
|
related_name="polymorphic_pexp.uuidmodela_set+",
|
||||||
|
editable=False,
|
||||||
|
on_delete=models.CASCADE,
|
||||||
|
to="contenttypes.ContentType",
|
||||||
|
null=True,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='testmodela',
|
model_name="testmodela",
|
||||||
name='polymorphic_ctype',
|
name="polymorphic_ctype",
|
||||||
field=models.ForeignKey(related_name='polymorphic_pexp.testmodela_set+', editable=False, on_delete=models.CASCADE, to='contenttypes.ContentType', null=True),
|
field=models.ForeignKey(
|
||||||
|
related_name="polymorphic_pexp.testmodela_set+",
|
||||||
|
editable=False,
|
||||||
|
on_delete=models.CASCADE,
|
||||||
|
to="contenttypes.ContentType",
|
||||||
|
null=True,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='project',
|
model_name="project",
|
||||||
name='polymorphic_ctype',
|
name="polymorphic_ctype",
|
||||||
field=models.ForeignKey(related_name='polymorphic_pexp.project_set+', editable=False, on_delete=models.CASCADE, to='contenttypes.ContentType', null=True),
|
field=models.ForeignKey(
|
||||||
|
related_name="polymorphic_pexp.project_set+",
|
||||||
|
editable=False,
|
||||||
|
on_delete=models.CASCADE,
|
||||||
|
to="contenttypes.ContentType",
|
||||||
|
null=True,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='ProxyA',
|
name="ProxyA", fields=[], options={"proxy": True}, bases=("pexp.proxybase",)
|
||||||
fields=[
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'proxy': True,
|
|
||||||
},
|
|
||||||
bases=('pexp.proxybase',),
|
|
||||||
),
|
),
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='ProxyB',
|
name="ProxyB", fields=[], options={"proxy": True}, bases=("pexp.proxybase",)
|
||||||
fields=[
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'proxy': True,
|
|
||||||
},
|
|
||||||
bases=('pexp.proxybase',),
|
|
||||||
),
|
),
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='NormalModelC',
|
name="NormalModelC",
|
||||||
fields=[
|
fields=[
|
||||||
('normalmodelb_ptr', models.OneToOneField(parent_link=True, auto_created=True, primary_key=True, serialize=False, on_delete=models.CASCADE, to='pexp.NormalModelB')),
|
(
|
||||||
('field3', models.CharField(max_length=10)),
|
"normalmodelb_ptr",
|
||||||
|
models.OneToOneField(
|
||||||
|
parent_link=True,
|
||||||
|
auto_created=True,
|
||||||
|
primary_key=True,
|
||||||
|
serialize=False,
|
||||||
|
on_delete=models.CASCADE,
|
||||||
|
to="pexp.NormalModelB",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("field3", models.CharField(max_length=10)),
|
||||||
],
|
],
|
||||||
bases=('pexp.normalmodelb',),
|
bases=("pexp.normalmodelb",),
|
||||||
),
|
),
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='TestModelC',
|
name="TestModelC",
|
||||||
fields=[
|
fields=[
|
||||||
('testmodelb_ptr', models.OneToOneField(parent_link=True, auto_created=True, primary_key=True, serialize=False, on_delete=models.CASCADE, to='pexp.TestModelB')),
|
(
|
||||||
('field3', models.CharField(max_length=10)),
|
"testmodelb_ptr",
|
||||||
('field4', models.ManyToManyField(related_name='related_c', to='pexp.TestModelB')),
|
models.OneToOneField(
|
||||||
|
parent_link=True,
|
||||||
|
auto_created=True,
|
||||||
|
primary_key=True,
|
||||||
|
serialize=False,
|
||||||
|
on_delete=models.CASCADE,
|
||||||
|
to="pexp.TestModelB",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("field3", models.CharField(max_length=10)),
|
||||||
|
(
|
||||||
|
"field4",
|
||||||
|
models.ManyToManyField(
|
||||||
|
related_name="related_c", to="pexp.TestModelB"
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
options={
|
options={"abstract": False},
|
||||||
'abstract': False,
|
bases=("pexp.testmodelb",),
|
||||||
},
|
|
||||||
bases=('pexp.testmodelb',),
|
|
||||||
),
|
),
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='UUIDModelC',
|
name="UUIDModelC",
|
||||||
fields=[
|
fields=[
|
||||||
('uuidmodelb_ptr', models.OneToOneField(parent_link=True, auto_created=True, primary_key=True, serialize=False, on_delete=models.CASCADE, to='pexp.UUIDModelB')),
|
(
|
||||||
('field3', models.CharField(max_length=10)),
|
"uuidmodelb_ptr",
|
||||||
|
models.OneToOneField(
|
||||||
|
parent_link=True,
|
||||||
|
auto_created=True,
|
||||||
|
primary_key=True,
|
||||||
|
serialize=False,
|
||||||
|
on_delete=models.CASCADE,
|
||||||
|
to="pexp.UUIDModelB",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("field3", models.CharField(max_length=10)),
|
||||||
],
|
],
|
||||||
options={
|
options={"abstract": False},
|
||||||
'abstract': False,
|
bases=("pexp.uuidmodelb",),
|
||||||
},
|
|
||||||
bases=('pexp.uuidmodelb',),
|
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ from polymorphic.showfields import ShowFieldContent, ShowFieldTypeAndContent
|
||||||
|
|
||||||
class Project(ShowFieldContent, PolymorphicModel):
|
class Project(ShowFieldContent, PolymorphicModel):
|
||||||
"""Polymorphic model"""
|
"""Polymorphic model"""
|
||||||
|
|
||||||
topic = models.CharField(max_length=30)
|
topic = models.CharField(max_length=30)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -21,6 +22,7 @@ class ResearchProject(Project):
|
||||||
|
|
||||||
class UUIDModelA(ShowFieldTypeAndContent, PolymorphicModel):
|
class UUIDModelA(ShowFieldTypeAndContent, PolymorphicModel):
|
||||||
"""UUID as primary key example"""
|
"""UUID as primary key example"""
|
||||||
|
|
||||||
uuid_primary_key = models.UUIDField(primary_key=True)
|
uuid_primary_key = models.UUIDField(primary_key=True)
|
||||||
field1 = models.CharField(max_length=10)
|
field1 = models.CharField(max_length=10)
|
||||||
|
|
||||||
|
|
@ -35,17 +37,17 @@ class UUIDModelC(UUIDModelB):
|
||||||
|
|
||||||
class ProxyBase(PolymorphicModel):
|
class ProxyBase(PolymorphicModel):
|
||||||
"""Proxy model example - a single table with multiple types."""
|
"""Proxy model example - a single table with multiple types."""
|
||||||
|
|
||||||
title = models.CharField(max_length=200)
|
title = models.CharField(max_length=200)
|
||||||
|
|
||||||
def __unicode__(self):
|
def __unicode__(self):
|
||||||
return u"<ProxyBase[type={0}]: {1}>".format(self.polymorphic_ctype, self.title)
|
return u"<ProxyBase[type={0}]: {1}>".format(self.polymorphic_ctype, self.title)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ('title',)
|
ordering = ("title",)
|
||||||
|
|
||||||
|
|
||||||
class ProxyA(ProxyBase):
|
class ProxyA(ProxyBase):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
proxy = True
|
proxy = True
|
||||||
|
|
||||||
|
|
@ -54,7 +56,6 @@ class ProxyA(ProxyBase):
|
||||||
|
|
||||||
|
|
||||||
class ProxyB(ProxyBase):
|
class ProxyB(ProxyBase):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
proxy = True
|
proxy = True
|
||||||
|
|
||||||
|
|
@ -64,6 +65,7 @@ class ProxyB(ProxyBase):
|
||||||
|
|
||||||
# Internals for management command tests
|
# Internals for management command tests
|
||||||
|
|
||||||
|
|
||||||
class TestModelA(ShowFieldTypeAndContent, PolymorphicModel):
|
class TestModelA(ShowFieldTypeAndContent, PolymorphicModel):
|
||||||
field1 = models.CharField(max_length=10)
|
field1 = models.CharField(max_length=10)
|
||||||
|
|
||||||
|
|
@ -74,11 +76,12 @@ class TestModelB(TestModelA):
|
||||||
|
|
||||||
class TestModelC(TestModelB):
|
class TestModelC(TestModelB):
|
||||||
field3 = models.CharField(max_length=10)
|
field3 = models.CharField(max_length=10)
|
||||||
field4 = models.ManyToManyField(TestModelB, related_name='related_c')
|
field4 = models.ManyToManyField(TestModelB, related_name="related_c")
|
||||||
|
|
||||||
|
|
||||||
class NormalModelA(models.Model):
|
class NormalModelA(models.Model):
|
||||||
"""Normal Django inheritance, no polymorphic behavior"""
|
"""Normal Django inheritance, no polymorphic behavior"""
|
||||||
|
|
||||||
field1 = models.CharField(max_length=10)
|
field1 = models.CharField(max_length=10)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -13,4 +13,3 @@ try:
|
||||||
__version__ = pkg_resources.require("django-polymorphic")[0].version
|
__version__ = pkg_resources.require("django-polymorphic")[0].version
|
||||||
except pkg_resources.DistributionNotFound:
|
except pkg_resources.DistributionNotFound:
|
||||||
__version__ = None # for RTD among others
|
__version__ = None # for RTD among others
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,44 +4,38 @@ ModelAdmin code to display polymorphic models.
|
||||||
The admin consists of a parent admin (which shows in the admin with a list),
|
The admin consists of a parent admin (which shows in the admin with a list),
|
||||||
and a child admin (which is used internally to show the edit/delete dialog).
|
and a child admin (which is used internally to show the edit/delete dialog).
|
||||||
"""
|
"""
|
||||||
# Admins for the regular models
|
|
||||||
from .parentadmin import PolymorphicParentModelAdmin
|
|
||||||
from .childadmin import PolymorphicChildModelAdmin
|
from .childadmin import PolymorphicChildModelAdmin
|
||||||
|
from .filters import PolymorphicChildModelFilter
|
||||||
|
|
||||||
# Utils
|
# Utils
|
||||||
from .forms import PolymorphicModelChoiceForm
|
from .forms import PolymorphicModelChoiceForm
|
||||||
from .filters import PolymorphicChildModelFilter
|
|
||||||
|
|
||||||
# Inlines
|
|
||||||
from .inlines import (
|
|
||||||
PolymorphicInlineModelAdmin, # base class
|
|
||||||
StackedPolymorphicInline, # stacked inline
|
|
||||||
)
|
|
||||||
|
|
||||||
# Helpers for the inlines
|
|
||||||
from .helpers import (
|
|
||||||
PolymorphicInlineAdminForm,
|
|
||||||
PolymorphicInlineAdminFormSet,
|
|
||||||
PolymorphicInlineSupportMixin, # mixin for the regular model admin!
|
|
||||||
)
|
|
||||||
|
|
||||||
# Expose generic admin features too. There is no need to split those
|
# Expose generic admin features too. There is no need to split those
|
||||||
# as the admin already relies on contenttypes.
|
# as the admin already relies on contenttypes.
|
||||||
from .generic import (
|
from .generic import GenericPolymorphicInlineModelAdmin # base class
|
||||||
GenericPolymorphicInlineModelAdmin, # base class
|
from .generic import GenericStackedPolymorphicInline # stacked inline
|
||||||
GenericStackedPolymorphicInline, # stacked inline
|
|
||||||
)
|
# Helpers for the inlines
|
||||||
|
from .helpers import PolymorphicInlineSupportMixin # mixin for the regular model admin!
|
||||||
|
from .helpers import PolymorphicInlineAdminForm, PolymorphicInlineAdminFormSet
|
||||||
|
|
||||||
|
# Inlines
|
||||||
|
from .inlines import PolymorphicInlineModelAdmin # base class
|
||||||
|
from .inlines import StackedPolymorphicInline # stacked inline
|
||||||
|
|
||||||
|
# Admins for the regular models
|
||||||
|
from .parentadmin import PolymorphicParentModelAdmin
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
'PolymorphicParentModelAdmin',
|
"PolymorphicParentModelAdmin",
|
||||||
'PolymorphicChildModelAdmin',
|
"PolymorphicChildModelAdmin",
|
||||||
'PolymorphicModelChoiceForm',
|
"PolymorphicModelChoiceForm",
|
||||||
'PolymorphicChildModelFilter',
|
"PolymorphicChildModelFilter",
|
||||||
'PolymorphicInlineAdminForm',
|
"PolymorphicInlineAdminForm",
|
||||||
'PolymorphicInlineAdminFormSet',
|
"PolymorphicInlineAdminFormSet",
|
||||||
'PolymorphicInlineSupportMixin',
|
"PolymorphicInlineSupportMixin",
|
||||||
'PolymorphicInlineModelAdmin',
|
"PolymorphicInlineModelAdmin",
|
||||||
'StackedPolymorphicInline',
|
"StackedPolymorphicInline",
|
||||||
'GenericPolymorphicInlineModelAdmin',
|
"GenericPolymorphicInlineModelAdmin",
|
||||||
'GenericStackedPolymorphicInline',
|
"GenericStackedPolymorphicInline",
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ from django.urls import resolve
|
||||||
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 ..admin import PolymorphicParentModelAdmin
|
from ..admin import PolymorphicParentModelAdmin
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -46,7 +47,9 @@ class PolymorphicChildModelAdmin(admin.ModelAdmin):
|
||||||
show_in_index = False
|
show_in_index = False
|
||||||
|
|
||||||
def __init__(self, model, admin_site, *args, **kwargs):
|
def __init__(self, model, admin_site, *args, **kwargs):
|
||||||
super(PolymorphicChildModelAdmin, self).__init__(model, admin_site, *args, **kwargs)
|
super(PolymorphicChildModelAdmin, self).__init__(
|
||||||
|
model, admin_site, *args, **kwargs
|
||||||
|
)
|
||||||
|
|
||||||
if self.base_model is None:
|
if self.base_model is None:
|
||||||
self.base_model = get_base_polymorphic_model(model)
|
self.base_model = get_base_polymorphic_model(model)
|
||||||
|
|
@ -59,19 +62,23 @@ class PolymorphicChildModelAdmin(admin.ModelAdmin):
|
||||||
#
|
#
|
||||||
# Instead, pass the form unchecked here, because the standard ModelForm will just work.
|
# Instead, pass the form unchecked here, because the standard ModelForm will just work.
|
||||||
# If the derived class sets the model explicitly, respect that setting.
|
# If the derived class sets the model explicitly, respect that setting.
|
||||||
kwargs.setdefault('form', self.base_form or self.form)
|
kwargs.setdefault("form", self.base_form or self.form)
|
||||||
|
|
||||||
# prevent infinite recursion when this is called from get_subclass_fields
|
# prevent infinite recursion when this is called from get_subclass_fields
|
||||||
if not self.fieldsets and not self.fields:
|
if not self.fieldsets and not self.fields:
|
||||||
kwargs.setdefault('fields', '__all__')
|
kwargs.setdefault("fields", "__all__")
|
||||||
|
|
||||||
return super(PolymorphicChildModelAdmin, self).get_form(request, obj, **kwargs)
|
return super(PolymorphicChildModelAdmin, self).get_form(request, obj, **kwargs)
|
||||||
|
|
||||||
def get_model_perms(self, request):
|
def get_model_perms(self, request):
|
||||||
match = resolve(request.path)
|
match = resolve(request.path)
|
||||||
|
|
||||||
if not self.show_in_index and match.app_name == 'admin' and match.url_name in ('index', 'app_list'):
|
if (
|
||||||
return {'add': False, 'change': False, 'delete': False}
|
not self.show_in_index
|
||||||
|
and match.app_name == "admin"
|
||||||
|
and match.url_name in ("index", "app_list")
|
||||||
|
):
|
||||||
|
return {"add": False, "change": False, "delete": False}
|
||||||
return super(PolymorphicChildModelAdmin, self).get_model_perms(request)
|
return super(PolymorphicChildModelAdmin, self).get_model_perms(request)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
|
@ -87,10 +94,11 @@ class PolymorphicChildModelAdmin(admin.ModelAdmin):
|
||||||
"admin/%s/%s/change_form.html" % (app_label, opts.object_name.lower()),
|
"admin/%s/%s/change_form.html" % (app_label, opts.object_name.lower()),
|
||||||
"admin/%s/change_form.html" % app_label,
|
"admin/%s/change_form.html" % app_label,
|
||||||
# Added:
|
# Added:
|
||||||
"admin/%s/%s/change_form.html" % (base_app_label, base_opts.object_name.lower()),
|
"admin/%s/%s/change_form.html"
|
||||||
|
% (base_app_label, base_opts.object_name.lower()),
|
||||||
"admin/%s/change_form.html" % base_app_label,
|
"admin/%s/change_form.html" % base_app_label,
|
||||||
"admin/polymorphic/change_form.html",
|
"admin/polymorphic/change_form.html",
|
||||||
"admin/change_form.html"
|
"admin/change_form.html",
|
||||||
]
|
]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
|
@ -103,13 +111,15 @@ class PolymorphicChildModelAdmin(admin.ModelAdmin):
|
||||||
base_app_label = base_opts.app_label
|
base_app_label = base_opts.app_label
|
||||||
|
|
||||||
return [
|
return [
|
||||||
"admin/%s/%s/delete_confirmation.html" % (app_label, opts.object_name.lower()),
|
"admin/%s/%s/delete_confirmation.html"
|
||||||
|
% (app_label, opts.object_name.lower()),
|
||||||
"admin/%s/delete_confirmation.html" % app_label,
|
"admin/%s/delete_confirmation.html" % app_label,
|
||||||
# Added:
|
# Added:
|
||||||
"admin/%s/%s/delete_confirmation.html" % (base_app_label, base_opts.object_name.lower()),
|
"admin/%s/%s/delete_confirmation.html"
|
||||||
|
% (base_app_label, base_opts.object_name.lower()),
|
||||||
"admin/%s/delete_confirmation.html" % base_app_label,
|
"admin/%s/delete_confirmation.html" % base_app_label,
|
||||||
"admin/polymorphic/delete_confirmation.html",
|
"admin/polymorphic/delete_confirmation.html",
|
||||||
"admin/delete_confirmation.html"
|
"admin/delete_confirmation.html",
|
||||||
]
|
]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
|
@ -125,15 +135,16 @@ class PolymorphicChildModelAdmin(admin.ModelAdmin):
|
||||||
"admin/%s/%s/object_history.html" % (app_label, opts.object_name.lower()),
|
"admin/%s/%s/object_history.html" % (app_label, opts.object_name.lower()),
|
||||||
"admin/%s/object_history.html" % app_label,
|
"admin/%s/object_history.html" % app_label,
|
||||||
# Added:
|
# Added:
|
||||||
"admin/%s/%s/object_history.html" % (base_app_label, base_opts.object_name.lower()),
|
"admin/%s/%s/object_history.html"
|
||||||
|
% (base_app_label, base_opts.object_name.lower()),
|
||||||
"admin/%s/object_history.html" % base_app_label,
|
"admin/%s/object_history.html" % base_app_label,
|
||||||
"admin/polymorphic/object_history.html",
|
"admin/polymorphic/object_history.html",
|
||||||
"admin/object_history.html"
|
"admin/object_history.html",
|
||||||
]
|
]
|
||||||
|
|
||||||
def _get_parent_admin(self):
|
def _get_parent_admin(self):
|
||||||
# this returns parent admin instance on which to call response_post_save methods
|
# this returns parent admin instance on which to call response_post_save methods
|
||||||
parent_model = self.model._meta.get_field('polymorphic_ctype').model
|
parent_model = self.model._meta.get_field("polymorphic_ctype").model
|
||||||
if parent_model == self.model:
|
if parent_model == self.model:
|
||||||
# when parent_model is in among child_models, just return super instance
|
# when parent_model is in among child_models, just return super instance
|
||||||
return super(PolymorphicChildModelAdmin, self)
|
return super(PolymorphicChildModelAdmin, self)
|
||||||
|
|
@ -149,11 +160,15 @@ class PolymorphicChildModelAdmin(admin.ModelAdmin):
|
||||||
|
|
||||||
# Fetch admin instance for model class, see if it's a possible candidate.
|
# Fetch admin instance for model class, see if it's a possible candidate.
|
||||||
model_admin = self.admin_site._registry.get(klass)
|
model_admin = self.admin_site._registry.get(klass)
|
||||||
if model_admin is not None and isinstance(model_admin, PolymorphicParentModelAdmin):
|
if model_admin is not None and isinstance(
|
||||||
|
model_admin, PolymorphicParentModelAdmin
|
||||||
|
):
|
||||||
return model_admin # Success!
|
return model_admin # Success!
|
||||||
|
|
||||||
# If we get this far without returning there is no admin available
|
# If we get this far without returning there is no admin available
|
||||||
raise ParentAdminNotRegistered("No parent admin was registered for a '{0}' model.".format(parent_model))
|
raise ParentAdminNotRegistered(
|
||||||
|
"No parent admin was registered for a '{0}' model.".format(parent_model)
|
||||||
|
)
|
||||||
|
|
||||||
def response_post_save_add(self, request, obj):
|
def response_post_save_add(self, request, obj):
|
||||||
return self._get_parent_admin().response_post_save_add(request, obj)
|
return self._get_parent_admin().response_post_save_add(request, obj)
|
||||||
|
|
@ -161,26 +176,28 @@ class PolymorphicChildModelAdmin(admin.ModelAdmin):
|
||||||
def response_post_save_change(self, request, obj):
|
def response_post_save_change(self, request, obj):
|
||||||
return self._get_parent_admin().response_post_save_change(request, obj)
|
return self._get_parent_admin().response_post_save_change(request, obj)
|
||||||
|
|
||||||
def render_change_form(self, request, context, add=False, change=False, form_url='', obj=None):
|
def render_change_form(
|
||||||
context.update({
|
self, request, context, add=False, change=False, form_url="", obj=None
|
||||||
'base_opts': self.base_model._meta,
|
):
|
||||||
})
|
context.update({"base_opts": self.base_model._meta})
|
||||||
return super(PolymorphicChildModelAdmin, self).render_change_form(request, context, add=add, change=change, form_url=form_url, obj=obj)
|
return super(PolymorphicChildModelAdmin, self).render_change_form(
|
||||||
|
request, context, add=add, change=change, form_url=form_url, obj=obj
|
||||||
|
)
|
||||||
|
|
||||||
def delete_view(self, request, object_id, context=None):
|
def delete_view(self, request, object_id, context=None):
|
||||||
extra_context = {
|
extra_context = {"base_opts": self.base_model._meta}
|
||||||
'base_opts': self.base_model._meta,
|
return super(PolymorphicChildModelAdmin, self).delete_view(
|
||||||
}
|
request, object_id, extra_context
|
||||||
return super(PolymorphicChildModelAdmin, self).delete_view(request, object_id, extra_context)
|
)
|
||||||
|
|
||||||
def history_view(self, request, object_id, extra_context=None):
|
def history_view(self, request, object_id, extra_context=None):
|
||||||
# Make sure the history view can also display polymorphic breadcrumbs
|
# Make sure the history view can also display polymorphic breadcrumbs
|
||||||
context = {
|
context = {"base_opts": self.base_model._meta}
|
||||||
'base_opts': self.base_model._meta,
|
|
||||||
}
|
|
||||||
if extra_context:
|
if extra_context:
|
||||||
context.update(extra_context)
|
context.update(extra_context)
|
||||||
return super(PolymorphicChildModelAdmin, self).history_view(request, object_id, extra_context=context)
|
return super(PolymorphicChildModelAdmin, self).history_view(
|
||||||
|
request, object_id, extra_context=context
|
||||||
|
)
|
||||||
|
|
||||||
# ---- Extra: improving the form/fieldset default display ----
|
# ---- Extra: improving the form/fieldset default display ----
|
||||||
|
|
||||||
|
|
@ -201,7 +218,7 @@ class PolymorphicChildModelAdmin(admin.ModelAdmin):
|
||||||
if other_fields:
|
if other_fields:
|
||||||
return (
|
return (
|
||||||
base_fieldsets[0],
|
base_fieldsets[0],
|
||||||
(self.extra_fieldset_title, {'fields': other_fields}),
|
(self.extra_fieldset_title, {"fields": other_fields}),
|
||||||
) + base_fieldsets[1:]
|
) + base_fieldsets[1:]
|
||||||
else:
|
else:
|
||||||
return base_fieldsets
|
return base_fieldsets
|
||||||
|
|
@ -215,11 +232,13 @@ class PolymorphicChildModelAdmin(admin.ModelAdmin):
|
||||||
# By not declaring the fields/form in the base class,
|
# By not declaring the fields/form in the base class,
|
||||||
# get_form() will populate the form with all available fields.
|
# get_form() will populate the form with all available fields.
|
||||||
form = self.get_form(request, obj, exclude=exclude)
|
form = self.get_form(request, obj, exclude=exclude)
|
||||||
subclass_fields = list(form.base_fields.keys()) + list(self.get_readonly_fields(request, obj))
|
subclass_fields = list(form.base_fields.keys()) + list(
|
||||||
|
self.get_readonly_fields(request, obj)
|
||||||
|
)
|
||||||
|
|
||||||
# Find which fields are not part of the common fields.
|
# Find which fields are not part of the common fields.
|
||||||
for fieldset in self.get_base_fieldsets(request, obj):
|
for fieldset in self.get_base_fieldsets(request, obj):
|
||||||
for field in fieldset[1]['fields']:
|
for field in fieldset[1]["fields"]:
|
||||||
try:
|
try:
|
||||||
subclass_fields.remove(field)
|
subclass_fields.remove(field)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
|
|
|
||||||
|
|
@ -14,11 +14,12 @@ class PolymorphicChildModelFilter(admin.SimpleListFilter):
|
||||||
|
|
||||||
list_filter = (PolymorphicChildModelFilter,)
|
list_filter = (PolymorphicChildModelFilter,)
|
||||||
"""
|
"""
|
||||||
title = _('Type')
|
|
||||||
parameter_name = 'polymorphic_ctype'
|
title = _("Type")
|
||||||
|
parameter_name = "polymorphic_ctype"
|
||||||
|
|
||||||
def lookups(self, request, model_admin):
|
def lookups(self, request, model_admin):
|
||||||
return model_admin.get_child_type_choices(request, 'change')
|
return model_admin.get_child_type_choices(request, "change")
|
||||||
|
|
||||||
def queryset(self, request, queryset):
|
def queryset(self, request, queryset):
|
||||||
try:
|
try:
|
||||||
|
|
@ -31,5 +32,8 @@ class PolymorphicChildModelFilter(admin.SimpleListFilter):
|
||||||
if choice_value == value:
|
if choice_value == value:
|
||||||
return queryset.filter(polymorphic_ctype_id=choice_value)
|
return queryset.filter(polymorphic_ctype_id=choice_value)
|
||||||
raise PermissionDenied(
|
raise PermissionDenied(
|
||||||
'Invalid ContentType "{0}". It must be registered as child model.'.format(value))
|
'Invalid ContentType "{0}". It must be registered as child model.'.format(
|
||||||
|
value
|
||||||
|
)
|
||||||
|
)
|
||||||
return queryset
|
return queryset
|
||||||
|
|
|
||||||
|
|
@ -7,12 +7,15 @@ class PolymorphicModelChoiceForm(forms.Form):
|
||||||
"""
|
"""
|
||||||
The default form for the ``add_type_form``. Can be overwritten and replaced.
|
The default form for the ``add_type_form``. Can be overwritten and replaced.
|
||||||
"""
|
"""
|
||||||
#: Define the label for the radiofield
|
|
||||||
type_label = _('Type')
|
|
||||||
|
|
||||||
ct_id = forms.ChoiceField(label=type_label, widget=AdminRadioSelect(attrs={'class': 'radiolist'}))
|
#: Define the label for the radiofield
|
||||||
|
type_label = _("Type")
|
||||||
|
|
||||||
|
ct_id = forms.ChoiceField(
|
||||||
|
label=type_label, widget=AdminRadioSelect(attrs={"class": "radiolist"})
|
||||||
|
)
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
# Allow to easily redefine the label (a commonly expected usecase)
|
# Allow to easily redefine the label (a commonly expected usecase)
|
||||||
super(PolymorphicModelChoiceForm, self).__init__(*args, **kwargs)
|
super(PolymorphicModelChoiceForm, self).__init__(*args, **kwargs)
|
||||||
self.fields['ct_id'].label = self.type_label
|
self.fields["ct_id"].label = self.type_label
|
||||||
|
|
|
||||||
|
|
@ -2,14 +2,22 @@ from django.contrib.contenttypes.admin import GenericInlineModelAdmin
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.utils.functional import cached_property
|
from django.utils.functional import cached_property
|
||||||
|
|
||||||
from polymorphic.formsets import polymorphic_child_forms_factory, BaseGenericPolymorphicInlineFormSet, GenericPolymorphicFormSetChild
|
from polymorphic.formsets import (
|
||||||
|
BaseGenericPolymorphicInlineFormSet,
|
||||||
|
GenericPolymorphicFormSetChild,
|
||||||
|
polymorphic_child_forms_factory,
|
||||||
|
)
|
||||||
|
|
||||||
from .inlines import PolymorphicInlineModelAdmin
|
from .inlines import PolymorphicInlineModelAdmin
|
||||||
|
|
||||||
|
|
||||||
class GenericPolymorphicInlineModelAdmin(PolymorphicInlineModelAdmin, GenericInlineModelAdmin):
|
class GenericPolymorphicInlineModelAdmin(
|
||||||
|
PolymorphicInlineModelAdmin, GenericInlineModelAdmin
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
Base class for variation of inlines based on generic foreign keys.
|
Base class for variation of inlines based on generic foreign keys.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
#: The formset class
|
#: The formset class
|
||||||
formset = BaseGenericPolymorphicInlineFormSet
|
formset = BaseGenericPolymorphicInlineFormSet
|
||||||
|
|
||||||
|
|
@ -31,6 +39,7 @@ class GenericPolymorphicInlineModelAdmin(PolymorphicInlineModelAdmin, GenericInl
|
||||||
"""
|
"""
|
||||||
Variation for generic inlines.
|
Variation for generic inlines.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Make sure that the GFK fields are excluded from the child forms
|
# Make sure that the GFK fields are excluded from the child forms
|
||||||
formset_child = GenericPolymorphicFormSetChild
|
formset_child = GenericPolymorphicFormSetChild
|
||||||
ct_field = "content_type"
|
ct_field = "content_type"
|
||||||
|
|
@ -42,22 +51,24 @@ class GenericPolymorphicInlineModelAdmin(PolymorphicInlineModelAdmin, GenericInl
|
||||||
Expose the ContentType that the child relates to.
|
Expose the ContentType that the child relates to.
|
||||||
This can be used for the ``polymorphic_ctype`` field.
|
This can be used for the ``polymorphic_ctype`` field.
|
||||||
"""
|
"""
|
||||||
return ContentType.objects.get_for_model(self.model, for_concrete_model=False)
|
return ContentType.objects.get_for_model(
|
||||||
|
self.model, for_concrete_model=False
|
||||||
|
)
|
||||||
|
|
||||||
def get_formset_child(self, request, obj=None, **kwargs):
|
def get_formset_child(self, request, obj=None, **kwargs):
|
||||||
# Similar to GenericInlineModelAdmin.get_formset(),
|
# Similar to GenericInlineModelAdmin.get_formset(),
|
||||||
# make sure the GFK is automatically excluded from the form
|
# make sure the GFK is automatically excluded from the form
|
||||||
defaults = {
|
defaults = {"ct_field": self.ct_field, "fk_field": self.ct_fk_field}
|
||||||
"ct_field": self.ct_field,
|
|
||||||
"fk_field": self.ct_fk_field,
|
|
||||||
}
|
|
||||||
defaults.update(kwargs)
|
defaults.update(kwargs)
|
||||||
return super(GenericPolymorphicInlineModelAdmin.Child, self).get_formset_child(request, obj=obj, **defaults)
|
return super(
|
||||||
|
GenericPolymorphicInlineModelAdmin.Child, self
|
||||||
|
).get_formset_child(request, obj=obj, **defaults)
|
||||||
|
|
||||||
|
|
||||||
class GenericStackedPolymorphicInline(GenericPolymorphicInlineModelAdmin):
|
class GenericStackedPolymorphicInline(GenericPolymorphicInlineModelAdmin):
|
||||||
"""
|
"""
|
||||||
The stacked layout for generic inlines.
|
The stacked layout for generic inlines.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
#: The default template to use.
|
#: The default template to use.
|
||||||
template = 'admin/polymorphic/edit_inline/stacked.html'
|
template = "admin/polymorphic/edit_inline/stacked.html"
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ This makes sure that admin fieldsets/layout settings are exported to the templat
|
||||||
"""
|
"""
|
||||||
import json
|
import json
|
||||||
|
|
||||||
from django.contrib.admin.helpers import InlineAdminFormSet, InlineAdminForm, AdminField
|
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 ugettext
|
||||||
|
|
@ -19,11 +19,11 @@ class PolymorphicInlineAdminForm(InlineAdminForm):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def polymorphic_ctype_field(self):
|
def polymorphic_ctype_field(self):
|
||||||
return AdminField(self.form, 'polymorphic_ctype', False)
|
return AdminField(self.form, "polymorphic_ctype", False)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_empty(self):
|
def is_empty(self):
|
||||||
return '__prefix__' in self.form.prefix
|
return "__prefix__" in self.form.prefix
|
||||||
|
|
||||||
|
|
||||||
class PolymorphicInlineAdminFormSet(InlineAdminFormSet):
|
class PolymorphicInlineAdminFormSet(InlineAdminFormSet):
|
||||||
|
|
@ -32,15 +32,19 @@ class PolymorphicInlineAdminFormSet(InlineAdminFormSet):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
self.request = kwargs.pop('request', None) # Assigned later via PolymorphicInlineSupportMixin later.
|
self.request = kwargs.pop(
|
||||||
self.obj = kwargs.pop('obj', None)
|
"request", None
|
||||||
|
) # Assigned later via PolymorphicInlineSupportMixin later.
|
||||||
|
self.obj = kwargs.pop("obj", None)
|
||||||
super(PolymorphicInlineAdminFormSet, self).__init__(*args, **kwargs)
|
super(PolymorphicInlineAdminFormSet, self).__init__(*args, **kwargs)
|
||||||
|
|
||||||
def __iter__(self):
|
def __iter__(self):
|
||||||
"""
|
"""
|
||||||
Output all forms using the proper subtype settings.
|
Output all forms using the proper subtype settings.
|
||||||
"""
|
"""
|
||||||
for form, original in zip(self.formset.initial_forms, self.formset.get_queryset()):
|
for form, original in zip(
|
||||||
|
self.formset.initial_forms, self.formset.get_queryset()
|
||||||
|
):
|
||||||
# Output the form
|
# Output the form
|
||||||
model = original.get_real_instance_class()
|
model = original.get_real_instance_class()
|
||||||
child_inline = self.opts.get_child_inline_instance(model)
|
child_inline = self.opts.get_child_inline_instance(model)
|
||||||
|
|
@ -54,7 +58,7 @@ class PolymorphicInlineAdminFormSet(InlineAdminFormSet):
|
||||||
original=original,
|
original=original,
|
||||||
readonly_fields=self.get_child_readonly_fields(child_inline),
|
readonly_fields=self.get_child_readonly_fields(child_inline),
|
||||||
model_admin=child_inline,
|
model_admin=child_inline,
|
||||||
view_on_site_url=view_on_site_url
|
view_on_site_url=view_on_site_url,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Extra rows, and empty prefixed forms.
|
# Extra rows, and empty prefixed forms.
|
||||||
|
|
@ -88,22 +92,24 @@ class PolymorphicInlineAdminFormSet(InlineAdminFormSet):
|
||||||
This overrides the default Django version to add the ``childTypes`` data.
|
This overrides the default Django version to add the ``childTypes`` data.
|
||||||
"""
|
"""
|
||||||
verbose_name = self.opts.verbose_name
|
verbose_name = self.opts.verbose_name
|
||||||
return json.dumps({
|
return json.dumps(
|
||||||
'name': '#%s' % self.formset.prefix,
|
|
||||||
'options': {
|
|
||||||
'prefix': self.formset.prefix,
|
|
||||||
'addText': ugettext('Add another %(verbose_name)s') % {
|
|
||||||
'verbose_name': capfirst(verbose_name),
|
|
||||||
},
|
|
||||||
'childTypes': [
|
|
||||||
{
|
{
|
||||||
'type': model._meta.model_name,
|
"name": "#%s" % self.formset.prefix,
|
||||||
'name': force_text(model._meta.verbose_name)
|
"options": {
|
||||||
} for model in self.formset.child_forms.keys()
|
"prefix": self.formset.prefix,
|
||||||
],
|
"addText": ugettext("Add another %(verbose_name)s")
|
||||||
'deleteText': ugettext('Remove'),
|
% {"verbose_name": capfirst(verbose_name)},
|
||||||
|
"childTypes": [
|
||||||
|
{
|
||||||
|
"type": model._meta.model_name,
|
||||||
|
"name": force_text(model._meta.verbose_name),
|
||||||
}
|
}
|
||||||
})
|
for model in self.formset.child_forms.keys()
|
||||||
|
],
|
||||||
|
"deleteText": ugettext("Remove"),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class PolymorphicInlineSupportMixin(object):
|
class PolymorphicInlineSupportMixin(object):
|
||||||
|
|
@ -119,14 +125,17 @@ class PolymorphicInlineSupportMixin(object):
|
||||||
:class:`~django.contrib.admin.helpers.InlineAdminFormSet` for the polymorphic formsets.
|
:class:`~django.contrib.admin.helpers.InlineAdminFormSet` for the polymorphic formsets.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def get_inline_formsets(self, request, formsets, inline_instances, obj=None, *args, **kwargs):
|
def get_inline_formsets(
|
||||||
|
self, request, formsets, inline_instances, obj=None, *args, **kwargs
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
Overwritten version to produce the proper admin wrapping for the
|
Overwritten version to produce the proper admin wrapping for the
|
||||||
polymorphic inline formset. This fixes the media and form appearance
|
polymorphic inline formset. This fixes the media and form appearance
|
||||||
of the inline polymorphic models.
|
of the inline polymorphic models.
|
||||||
"""
|
"""
|
||||||
inline_admin_formsets = super(PolymorphicInlineSupportMixin, self).get_inline_formsets(
|
inline_admin_formsets = super(
|
||||||
request, formsets, inline_instances, obj=obj)
|
PolymorphicInlineSupportMixin, self
|
||||||
|
).get_inline_formsets(request, formsets, inline_instances, obj=obj)
|
||||||
|
|
||||||
for admin_formset in inline_admin_formsets:
|
for admin_formset in inline_admin_formsets:
|
||||||
if isinstance(admin_formset.formset, BasePolymorphicModelFormSet):
|
if isinstance(admin_formset.formset, BasePolymorphicModelFormSet):
|
||||||
|
|
|
||||||
|
|
@ -10,9 +10,14 @@ from django.contrib.admin.utils import flatten_fieldsets
|
||||||
from django.core.exceptions import ImproperlyConfigured
|
from django.core.exceptions import ImproperlyConfigured
|
||||||
from django.forms import Media
|
from django.forms import Media
|
||||||
|
|
||||||
from polymorphic.formsets import polymorphic_child_forms_factory, BasePolymorphicInlineFormSet, \
|
from polymorphic.formsets import (
|
||||||
PolymorphicFormSetChild, UnsupportedChildType
|
BasePolymorphicInlineFormSet,
|
||||||
|
PolymorphicFormSetChild,
|
||||||
|
UnsupportedChildType,
|
||||||
|
polymorphic_child_forms_factory,
|
||||||
|
)
|
||||||
from polymorphic.formsets.utils import add_media
|
from polymorphic.formsets.utils import add_media
|
||||||
|
|
||||||
from .helpers import PolymorphicInlineSupportMixin
|
from .helpers import PolymorphicInlineSupportMixin
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -31,14 +36,8 @@ class PolymorphicInlineModelAdmin(InlineModelAdmin):
|
||||||
#: The extra media to add for the polymorphic inlines effect.
|
#: The extra media to add for the polymorphic inlines effect.
|
||||||
#: This can be redefined for subclasses.
|
#: This can be redefined for subclasses.
|
||||||
polymorphic_media = Media(
|
polymorphic_media = Media(
|
||||||
js=(
|
js=("polymorphic/js/polymorphic_inlines.js",),
|
||||||
'polymorphic/js/polymorphic_inlines.js',
|
css={"all": ("polymorphic/css/polymorphic_inlines.css",)},
|
||||||
),
|
|
||||||
css={
|
|
||||||
'all': (
|
|
||||||
'polymorphic/css/polymorphic_inlines.css',
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
#: The extra forms to show
|
#: The extra forms to show
|
||||||
|
|
@ -90,7 +89,9 @@ class PolymorphicInlineModelAdmin(InlineModelAdmin):
|
||||||
try:
|
try:
|
||||||
return self._child_inlines_lookup[model]
|
return self._child_inlines_lookup[model]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
raise UnsupportedChildType("Model '{0}' not found in child_inlines".format(model.__name__))
|
raise UnsupportedChildType(
|
||||||
|
"Model '{0}' not found in child_inlines".format(model.__name__)
|
||||||
|
)
|
||||||
|
|
||||||
def get_formset(self, request, obj=None, **kwargs):
|
def get_formset(self, request, obj=None, **kwargs):
|
||||||
"""
|
"""
|
||||||
|
|
@ -101,7 +102,9 @@ class PolymorphicInlineModelAdmin(InlineModelAdmin):
|
||||||
:rtype: type
|
:rtype: type
|
||||||
"""
|
"""
|
||||||
# Construct the FormSet class
|
# Construct the FormSet class
|
||||||
FormSet = super(PolymorphicInlineModelAdmin, self).get_formset(request, obj=obj, **kwargs)
|
FormSet = super(PolymorphicInlineModelAdmin, self).get_formset(
|
||||||
|
request, obj=obj, **kwargs
|
||||||
|
)
|
||||||
|
|
||||||
# Instead of completely redefining super().get_formset(), we use
|
# Instead of completely redefining super().get_formset(), we use
|
||||||
# the regular inlineformset_factory(), and amend that with our extra bits.
|
# the regular inlineformset_factory(), and amend that with our extra bits.
|
||||||
|
|
@ -151,7 +154,10 @@ class PolymorphicInlineModelAdmin(InlineModelAdmin):
|
||||||
child_media = child_instance.media
|
child_media = child_instance.media
|
||||||
|
|
||||||
# Avoid adding the same media object again and again
|
# Avoid adding the same media object again and again
|
||||||
if child_media._css != base_media._css and child_media._js != base_media._js:
|
if (
|
||||||
|
child_media._css != base_media._css
|
||||||
|
and child_media._js != base_media._js
|
||||||
|
):
|
||||||
add_media(all_media, child_media)
|
add_media(all_media, child_media)
|
||||||
|
|
||||||
add_media(all_media, self.polymorphic_media)
|
add_media(all_media, self.polymorphic_media)
|
||||||
|
|
@ -170,12 +176,15 @@ class PolymorphicInlineModelAdmin(InlineModelAdmin):
|
||||||
|
|
||||||
The model form options however, will all be read.
|
The model form options however, will all be read.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
formset_child = PolymorphicFormSetChild
|
formset_child = PolymorphicFormSetChild
|
||||||
extra = 0 # TODO: currently unused for the children.
|
extra = 0 # TODO: currently unused for the children.
|
||||||
|
|
||||||
def __init__(self, parent_inline):
|
def __init__(self, parent_inline):
|
||||||
self.parent_inline = parent_inline
|
self.parent_inline = parent_inline
|
||||||
super(PolymorphicInlineModelAdmin.Child, self).__init__(parent_inline.parent_model, parent_inline.admin_site)
|
super(PolymorphicInlineModelAdmin.Child, self).__init__(
|
||||||
|
parent_inline.parent_model, parent_inline.admin_site
|
||||||
|
)
|
||||||
|
|
||||||
def get_formset(self, request, obj=None, **kwargs):
|
def get_formset(self, request, obj=None, **kwargs):
|
||||||
# The child inline is only used to construct the form,
|
# The child inline is only used to construct the form,
|
||||||
|
|
@ -209,8 +218,8 @@ class PolymorphicInlineModelAdmin(InlineModelAdmin):
|
||||||
#
|
#
|
||||||
# Transfer the local inline attributes to the formset child,
|
# Transfer the local inline attributes to the formset child,
|
||||||
# this allows overriding settings.
|
# this allows overriding settings.
|
||||||
if 'fields' in kwargs:
|
if "fields" in kwargs:
|
||||||
fields = kwargs.pop('fields')
|
fields = kwargs.pop("fields")
|
||||||
else:
|
else:
|
||||||
fields = flatten_fieldsets(self.get_fieldsets(request, obj))
|
fields = flatten_fieldsets(self.get_fieldsets(request, obj))
|
||||||
|
|
||||||
|
|
@ -220,9 +229,15 @@ class PolymorphicInlineModelAdmin(InlineModelAdmin):
|
||||||
exclude = list(self.exclude)
|
exclude = list(self.exclude)
|
||||||
|
|
||||||
exclude.extend(self.get_readonly_fields(request, obj))
|
exclude.extend(self.get_readonly_fields(request, obj))
|
||||||
exclude.append('polymorphic_ctype') # Django 1.10 blocks it, as it's a readonly field.
|
exclude.append(
|
||||||
|
"polymorphic_ctype"
|
||||||
|
) # Django 1.10 blocks it, as it's a readonly field.
|
||||||
|
|
||||||
if self.exclude is None and hasattr(self.form, '_meta') and self.form._meta.exclude:
|
if (
|
||||||
|
self.exclude is None
|
||||||
|
and hasattr(self.form, "_meta")
|
||||||
|
and self.form._meta.exclude
|
||||||
|
):
|
||||||
# Take the custom ModelForm's Meta.exclude into account only if the
|
# Take the custom ModelForm's Meta.exclude into account only if the
|
||||||
# InlineModelAdmin doesn't define its own.
|
# InlineModelAdmin doesn't define its own.
|
||||||
exclude.extend(self.form._meta.exclude)
|
exclude.extend(self.form._meta.exclude)
|
||||||
|
|
@ -232,7 +247,9 @@ class PolymorphicInlineModelAdmin(InlineModelAdmin):
|
||||||
"form": self.form,
|
"form": self.form,
|
||||||
"fields": fields,
|
"fields": fields,
|
||||||
"exclude": exclude or None,
|
"exclude": exclude or None,
|
||||||
"formfield_callback": partial(self.formfield_for_dbfield, request=request),
|
"formfield_callback": partial(
|
||||||
|
self.formfield_for_dbfield, request=request
|
||||||
|
),
|
||||||
}
|
}
|
||||||
defaults.update(kwargs)
|
defaults.update(kwargs)
|
||||||
|
|
||||||
|
|
@ -247,5 +264,6 @@ class StackedPolymorphicInline(PolymorphicInlineModelAdmin):
|
||||||
Stacked inline for django-polymorphic models.
|
Stacked inline for django-polymorphic models.
|
||||||
Since tabular doesn't make much sense with changed fields, just offer this one.
|
Since tabular doesn't make much sense with changed fields, just offer this one.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
#: The default template to use.
|
#: The default template to use.
|
||||||
template = 'admin/polymorphic/edit_inline/stacked.html'
|
template = "admin/polymorphic/edit_inline/stacked.html"
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ 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
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.core.exceptions import PermissionDenied, ImproperlyConfigured
|
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
|
||||||
|
|
@ -15,9 +15,10 @@ 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 .forms import PolymorphicModelChoiceForm
|
|
||||||
|
|
||||||
|
from polymorphic.utils import get_base_polymorphic_model
|
||||||
|
|
||||||
|
from .forms import PolymorphicModelChoiceForm
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Django 2.0+
|
# Django 2.0+
|
||||||
|
|
@ -73,7 +74,9 @@ class PolymorphicParentModelAdmin(admin.ModelAdmin):
|
||||||
pk_regex = r"(\d+|__fk__)"
|
pk_regex = r"(\d+|__fk__)"
|
||||||
|
|
||||||
def __init__(self, model, admin_site, *args, **kwargs):
|
def __init__(self, model, admin_site, *args, **kwargs):
|
||||||
super(PolymorphicParentModelAdmin, self).__init__(model, admin_site, *args, **kwargs)
|
super(PolymorphicParentModelAdmin, self).__init__(
|
||||||
|
model, admin_site, *args, **kwargs
|
||||||
|
)
|
||||||
self._is_setup = False
|
self._is_setup = False
|
||||||
|
|
||||||
if self.base_model is None:
|
if self.base_model is None:
|
||||||
|
|
@ -105,12 +108,22 @@ class PolymorphicParentModelAdmin(admin.ModelAdmin):
|
||||||
# After the get_urls() is called, the URLs of the child model can't be exposed anymore to the Django URLconf,
|
# After the get_urls() is called, the URLs of the child model can't be exposed anymore to the Django URLconf,
|
||||||
# which also means that a "Save and continue editing" button won't work.
|
# which also means that a "Save and continue editing" button won't work.
|
||||||
if self._is_setup:
|
if self._is_setup:
|
||||||
raise RegistrationClosed("The admin model can't be registered anymore at this point.")
|
raise RegistrationClosed(
|
||||||
|
"The admin model can't be registered anymore at this point."
|
||||||
|
)
|
||||||
|
|
||||||
if not issubclass(model, self.base_model):
|
if not issubclass(model, self.base_model):
|
||||||
raise TypeError("{0} should be a subclass of {1}".format(model.__name__, self.base_model.__name__))
|
raise TypeError(
|
||||||
|
"{0} should be a subclass of {1}".format(
|
||||||
|
model.__name__, self.base_model.__name__
|
||||||
|
)
|
||||||
|
)
|
||||||
if not issubclass(model_admin, admin.ModelAdmin):
|
if not issubclass(model_admin, admin.ModelAdmin):
|
||||||
raise TypeError("{0} should be a subclass of {1}".format(model_admin.__name__, admin.ModelAdmin.__name__))
|
raise TypeError(
|
||||||
|
"{0} should be a subclass of {1}".format(
|
||||||
|
model_admin.__name__, admin.ModelAdmin.__name__
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
self._child_admin_site.register(model, model_admin)
|
self._child_admin_site.register(model, model_admin)
|
||||||
|
|
||||||
|
|
@ -134,7 +147,7 @@ class PolymorphicParentModelAdmin(admin.ModelAdmin):
|
||||||
self._lazy_setup()
|
self._lazy_setup()
|
||||||
choices = []
|
choices = []
|
||||||
for model in self.get_child_models():
|
for model in self.get_child_models():
|
||||||
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):
|
||||||
|
|
@ -145,11 +158,16 @@ class PolymorphicParentModelAdmin(admin.ModelAdmin):
|
||||||
|
|
||||||
def _get_real_admin(self, object_id, super_if_self=True):
|
def _get_real_admin(self, object_id, super_if_self=True):
|
||||||
try:
|
try:
|
||||||
obj = self.model.objects.non_polymorphic() \
|
obj = (
|
||||||
.values('polymorphic_ctype').get(pk=object_id)
|
self.model.objects.non_polymorphic()
|
||||||
|
.values("polymorphic_ctype")
|
||||||
|
.get(pk=object_id)
|
||||||
|
)
|
||||||
except self.model.DoesNotExist:
|
except self.model.DoesNotExist:
|
||||||
raise Http404
|
raise Http404
|
||||||
return self._get_real_admin_by_ct(obj['polymorphic_ctype'], super_if_self=super_if_self)
|
return self._get_real_admin_by_ct(
|
||||||
|
obj["polymorphic_ctype"], super_if_self=super_if_self
|
||||||
|
)
|
||||||
|
|
||||||
def _get_real_admin_by_ct(self, ct_id, super_if_self=True):
|
def _get_real_admin_by_ct(self, ct_id, super_if_self=True):
|
||||||
try:
|
try:
|
||||||
|
|
@ -159,7 +177,9 @@ class PolymorphicParentModelAdmin(admin.ModelAdmin):
|
||||||
|
|
||||||
model_class = ct.model_class()
|
model_class = ct.model_class()
|
||||||
if not model_class:
|
if not model_class:
|
||||||
raise Http404("No model found for '{0}.{1}'.".format(*ct.natural_key())) # Handle model deletion
|
raise Http404(
|
||||||
|
"No model found for '{0}.{1}'.".format(*ct.natural_key())
|
||||||
|
) # Handle model deletion
|
||||||
|
|
||||||
return self._get_real_admin_by_model(model_class, super_if_self=super_if_self)
|
return self._get_real_admin_by_model(model_class, super_if_self=super_if_self)
|
||||||
|
|
||||||
|
|
@ -167,14 +187,22 @@ class PolymorphicParentModelAdmin(admin.ModelAdmin):
|
||||||
# In case of a ?ct_id=### parameter, the view is already checked for permissions.
|
# In case of a ?ct_id=### parameter, the view is already checked for permissions.
|
||||||
# Hence, make sure this is a derived object, or risk exposing other admin interfaces.
|
# Hence, make sure this is a derived object, or risk exposing other admin interfaces.
|
||||||
if model_class not in self._child_models:
|
if model_class not in self._child_models:
|
||||||
raise PermissionDenied("Invalid model '{0}', it must be registered as child model.".format(model_class))
|
raise PermissionDenied(
|
||||||
|
"Invalid model '{0}', it must be registered as child model.".format(
|
||||||
|
model_class
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# HACK: the only way to get the instance of an model admin,
|
# HACK: the only way to get the instance of an model admin,
|
||||||
# is to read the registry of the AdminSite.
|
# is to read the registry of the AdminSite.
|
||||||
real_admin = self._child_admin_site._registry[model_class]
|
real_admin = self._child_admin_site._registry[model_class]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
raise ChildAdminNotRegistered("No child admin site was registered for a '{0}' model.".format(model_class))
|
raise ChildAdminNotRegistered(
|
||||||
|
"No child admin site was registered for a '{0}' model.".format(
|
||||||
|
model_class
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
if super_if_self and real_admin is self:
|
if super_if_self and real_admin is self:
|
||||||
return super(PolymorphicParentModelAdmin, self)
|
return super(PolymorphicParentModelAdmin, self)
|
||||||
|
|
@ -188,19 +216,21 @@ class PolymorphicParentModelAdmin(admin.ModelAdmin):
|
||||||
qs = qs.non_polymorphic()
|
qs = qs.non_polymorphic()
|
||||||
return qs
|
return qs
|
||||||
|
|
||||||
def add_view(self, request, form_url='', extra_context=None):
|
def add_view(self, request, form_url="", extra_context=None):
|
||||||
"""Redirect the add view to the real admin."""
|
"""Redirect the add view to the real admin."""
|
||||||
ct_id = int(request.GET.get('ct_id', 0))
|
ct_id = int(request.GET.get("ct_id", 0))
|
||||||
if not ct_id:
|
if not ct_id:
|
||||||
# Display choices
|
# Display choices
|
||||||
return self.add_type_view(request)
|
return self.add_type_view(request)
|
||||||
else:
|
else:
|
||||||
real_admin = self._get_real_admin_by_ct(ct_id)
|
real_admin = self._get_real_admin_by_ct(ct_id)
|
||||||
# rebuild form_url, otherwise libraries below will override it.
|
# rebuild form_url, otherwise libraries below will override it.
|
||||||
form_url = add_preserved_filters({
|
form_url = add_preserved_filters(
|
||||||
'preserved_filters': urlencode({'ct_id': ct_id}),
|
{
|
||||||
'opts': self.model._meta},
|
"preserved_filters": urlencode({"ct_id": ct_id}),
|
||||||
form_url
|
"opts": self.model._meta,
|
||||||
|
},
|
||||||
|
form_url,
|
||||||
)
|
)
|
||||||
return real_admin.add_view(request, form_url, extra_context)
|
return real_admin.add_view(request, form_url, extra_context)
|
||||||
|
|
||||||
|
|
@ -218,7 +248,9 @@ class PolymorphicParentModelAdmin(admin.ModelAdmin):
|
||||||
return real_admin.changeform_view(request, object_id, *args, **kwargs)
|
return real_admin.changeform_view(request, object_id, *args, **kwargs)
|
||||||
else:
|
else:
|
||||||
# Add view. As it should already be handled via `add_view`, this means something custom is done here!
|
# Add view. As it should already be handled via `add_view`, this means something custom is done here!
|
||||||
return super(PolymorphicParentModelAdmin, self).changeform_view(request, object_id, *args, **kwargs)
|
return super(PolymorphicParentModelAdmin, self).changeform_view(
|
||||||
|
request, object_id, *args, **kwargs
|
||||||
|
)
|
||||||
|
|
||||||
def history_view(self, request, object_id, extra_context=None):
|
def history_view(self, request, object_id, extra_context=None):
|
||||||
"""Redirect the history view to the real admin."""
|
"""Redirect the history view to the real admin."""
|
||||||
|
|
@ -231,14 +263,14 @@ class PolymorphicParentModelAdmin(admin.ModelAdmin):
|
||||||
return real_admin.delete_view(request, object_id, extra_context)
|
return real_admin.delete_view(request, object_id, extra_context)
|
||||||
|
|
||||||
def get_preserved_filters(self, request):
|
def get_preserved_filters(self, request):
|
||||||
if '_changelist_filters' in request.GET:
|
if "_changelist_filters" in request.GET:
|
||||||
request.GET = request.GET.copy()
|
request.GET = request.GET.copy()
|
||||||
filters = request.GET.get('_changelist_filters')
|
filters = request.GET.get("_changelist_filters")
|
||||||
f = filters.split("&")
|
f = filters.split("&")
|
||||||
for x in f:
|
for x in f:
|
||||||
c = x.split('=')
|
c = x.split("=")
|
||||||
request.GET[c[0]] = c[1]
|
request.GET[c[0]] = c[1]
|
||||||
del request.GET['_changelist_filters']
|
del request.GET["_changelist_filters"]
|
||||||
return super(PolymorphicParentModelAdmin, self).get_preserved_filters(request)
|
return super(PolymorphicParentModelAdmin, self).get_preserved_filters(request)
|
||||||
|
|
||||||
def get_urls(self):
|
def get_urls(self):
|
||||||
|
|
@ -256,91 +288,100 @@ class PolymorphicParentModelAdmin(admin.ModelAdmin):
|
||||||
"""
|
"""
|
||||||
Forward any request to a custom view of the real admin.
|
Forward any request to a custom view of the real admin.
|
||||||
"""
|
"""
|
||||||
ct_id = int(request.GET.get('ct_id', 0))
|
ct_id = int(request.GET.get("ct_id", 0))
|
||||||
if not ct_id:
|
if not ct_id:
|
||||||
# See if the path started with an ID.
|
# See if the path started with an ID.
|
||||||
try:
|
try:
|
||||||
pos = path.find('/')
|
pos = path.find("/")
|
||||||
if pos == -1:
|
if pos == -1:
|
||||||
object_id = long(path)
|
object_id = long(path)
|
||||||
else:
|
else:
|
||||||
object_id = long(path[0:pos])
|
object_id = long(path[0:pos])
|
||||||
except ValueError:
|
except ValueError:
|
||||||
raise Http404("No ct_id parameter, unable to find admin subclass for path '{0}'.".format(path))
|
raise Http404(
|
||||||
|
"No ct_id parameter, unable to find admin subclass for path '{0}'.".format(
|
||||||
|
path
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
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 = URLResolver('^', 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))
|
||||||
|
|
||||||
return resolvermatch.func(request, *resolvermatch.args, **resolvermatch.kwargs)
|
return resolvermatch.func(request, *resolvermatch.args, **resolvermatch.kwargs)
|
||||||
|
|
||||||
def add_type_view(self, request, form_url=''):
|
def add_type_view(self, request, form_url=""):
|
||||||
"""
|
"""
|
||||||
Display a choice form to select which page type to add.
|
Display a choice form to select which page type to add.
|
||||||
"""
|
"""
|
||||||
if not self.has_add_permission(request):
|
if not self.has_add_permission(request):
|
||||||
raise PermissionDenied
|
raise PermissionDenied
|
||||||
|
|
||||||
extra_qs = ''
|
extra_qs = ""
|
||||||
if request.META['QUERY_STRING']:
|
if request.META["QUERY_STRING"]:
|
||||||
# QUERY_STRING is bytes in Python 3, using force_text() to decode it as string.
|
# QUERY_STRING is bytes in Python 3, using force_text() to decode it as string.
|
||||||
# See QueryDict how Django deals with that.
|
# See QueryDict how Django deals with that.
|
||||||
extra_qs = '&{0}'.format(force_text(request.META['QUERY_STRING']))
|
extra_qs = "&{0}".format(force_text(request.META["QUERY_STRING"]))
|
||||||
|
|
||||||
choices = self.get_child_type_choices(request, 'add')
|
choices = self.get_child_type_choices(request, "add")
|
||||||
if len(choices) == 1:
|
if len(choices) == 1:
|
||||||
return HttpResponseRedirect('?ct_id={0}{1}'.format(choices[0][0], extra_qs))
|
return HttpResponseRedirect("?ct_id={0}{1}".format(choices[0][0], extra_qs))
|
||||||
|
|
||||||
# Create form
|
# Create form
|
||||||
form = self.add_type_form(
|
form = self.add_type_form(
|
||||||
data=request.POST if request.method == 'POST' else None,
|
data=request.POST if request.method == "POST" else None,
|
||||||
initial={'ct_id': choices[0][0]}
|
initial={"ct_id": choices[0][0]},
|
||||||
)
|
)
|
||||||
form.fields['ct_id'].choices = choices
|
form.fields["ct_id"].choices = choices
|
||||||
|
|
||||||
if form.is_valid():
|
if form.is_valid():
|
||||||
return HttpResponseRedirect('?ct_id={0}{1}'.format(form.cleaned_data['ct_id'], extra_qs))
|
return HttpResponseRedirect(
|
||||||
|
"?ct_id={0}{1}".format(form.cleaned_data["ct_id"], extra_qs)
|
||||||
|
)
|
||||||
|
|
||||||
# Wrap in all admin layout
|
# Wrap in all admin layout
|
||||||
fieldsets = ((None, {'fields': ('ct_id',)}),)
|
fieldsets = ((None, {"fields": ("ct_id",)}),)
|
||||||
adminForm = AdminForm(form, fieldsets, {}, model_admin=self)
|
adminForm = AdminForm(form, fieldsets, {}, model_admin=self)
|
||||||
media = self.media + adminForm.media
|
media = self.media + adminForm.media
|
||||||
opts = self.model._meta
|
opts = self.model._meta
|
||||||
|
|
||||||
context = {
|
context = {
|
||||||
'title': _('Add %s') % force_text(opts.verbose_name),
|
"title": _("Add %s") % force_text(opts.verbose_name),
|
||||||
'adminform': adminForm,
|
"adminform": adminForm,
|
||||||
'is_popup': ("_popup" in request.POST or
|
"is_popup": ("_popup" in request.POST or "_popup" in request.GET),
|
||||||
"_popup" in request.GET),
|
"media": mark_safe(media),
|
||||||
'media': mark_safe(media),
|
"errors": AdminErrorList(form, ()),
|
||||||
'errors': AdminErrorList(form, ()),
|
"app_label": opts.app_label,
|
||||||
'app_label': opts.app_label,
|
|
||||||
}
|
}
|
||||||
return self.render_add_type_form(request, context, form_url)
|
return self.render_add_type_form(request, context, form_url)
|
||||||
|
|
||||||
def render_add_type_form(self, request, context, form_url=''):
|
def render_add_type_form(self, request, context, form_url=""):
|
||||||
"""
|
"""
|
||||||
Render the page type choice form.
|
Render the page type choice form.
|
||||||
"""
|
"""
|
||||||
opts = self.model._meta
|
opts = self.model._meta
|
||||||
app_label = opts.app_label
|
app_label = opts.app_label
|
||||||
context.update({
|
context.update(
|
||||||
'has_change_permission': self.has_change_permission(request),
|
{
|
||||||
'form_url': mark_safe(form_url),
|
"has_change_permission": self.has_change_permission(request),
|
||||||
'opts': opts,
|
"form_url": mark_safe(form_url),
|
||||||
'add': True,
|
"opts": opts,
|
||||||
'save_on_top': self.save_on_top,
|
"add": True,
|
||||||
})
|
"save_on_top": self.save_on_top,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
templates = self.add_type_template or [
|
templates = self.add_type_template or [
|
||||||
"admin/%s/%s/add_type_form.html" % (app_label, opts.object_name.lower()),
|
"admin/%s/%s/add_type_form.html" % (app_label, opts.object_name.lower()),
|
||||||
"admin/%s/add_type_form.html" % app_label,
|
"admin/%s/add_type_form.html" % app_label,
|
||||||
"admin/polymorphic/add_type_form.html", # added default here
|
"admin/polymorphic/add_type_form.html", # added default here
|
||||||
"admin/add_type_form.html"
|
"admin/add_type_form.html",
|
||||||
]
|
]
|
||||||
|
|
||||||
request.current_app = self.admin_site.name
|
request.current_app = self.admin_site.name
|
||||||
|
|
@ -359,7 +400,8 @@ class PolymorphicParentModelAdmin(admin.ModelAdmin):
|
||||||
"admin/%s/%s/change_list.html" % (app_label, opts.object_name.lower()),
|
"admin/%s/%s/change_list.html" % (app_label, opts.object_name.lower()),
|
||||||
"admin/%s/change_list.html" % app_label,
|
"admin/%s/change_list.html" % app_label,
|
||||||
# Added base class:
|
# Added base class:
|
||||||
"admin/%s/%s/change_list.html" % (base_app_label, base_opts.object_name.lower()),
|
"admin/%s/%s/change_list.html"
|
||||||
|
% (base_app_label, base_opts.object_name.lower()),
|
||||||
"admin/%s/change_list.html" % base_app_label,
|
"admin/%s/change_list.html" % base_app_label,
|
||||||
"admin/change_list.html"
|
"admin/change_list.html",
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -20,9 +20,11 @@ from .query import PolymorphicQuerySet
|
||||||
|
|
||||||
# PolymorphicQuerySet Q objects (and filter()) support these additional key words.
|
# PolymorphicQuerySet Q objects (and filter()) support these additional key words.
|
||||||
# These are forbidden as field names (a descriptive exception is raised)
|
# These are forbidden as field names (a descriptive exception is raised)
|
||||||
POLYMORPHIC_SPECIAL_Q_KWORDS = ['instance_of', 'not_instance_of']
|
POLYMORPHIC_SPECIAL_Q_KWORDS = ["instance_of", "not_instance_of"]
|
||||||
|
|
||||||
DUMPDATA_COMMAND = os.path.join('django', 'core', 'management', 'commands', 'dumpdata.py')
|
DUMPDATA_COMMAND = os.path.join(
|
||||||
|
"django", "core", "management", "commands", "dumpdata.py"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class ManagerInheritanceWarning(RuntimeWarning):
|
class ManagerInheritanceWarning(RuntimeWarning):
|
||||||
|
|
@ -32,6 +34,7 @@ class ManagerInheritanceWarning(RuntimeWarning):
|
||||||
###################################################################################
|
###################################################################################
|
||||||
# PolymorphicModel meta class
|
# PolymorphicModel meta class
|
||||||
|
|
||||||
|
|
||||||
class PolymorphicModelBase(ModelBase):
|
class PolymorphicModelBase(ModelBase):
|
||||||
"""
|
"""
|
||||||
Manager inheritance is a pretty complex topic which may need
|
Manager inheritance is a pretty complex topic which may need
|
||||||
|
|
@ -60,17 +63,21 @@ class PolymorphicModelBase(ModelBase):
|
||||||
# print; print '###', model_name, '- bases:', bases
|
# print; print '###', model_name, '- bases:', bases
|
||||||
|
|
||||||
# Workaround compatibility issue with six.with_metaclass() and custom Django model metaclasses:
|
# Workaround compatibility issue with six.with_metaclass() and custom Django model metaclasses:
|
||||||
if not attrs and model_name == 'NewBase':
|
if not attrs and model_name == "NewBase":
|
||||||
return super(PolymorphicModelBase, self).__new__(self, model_name, bases, attrs)
|
return super(PolymorphicModelBase, self).__new__(
|
||||||
|
self, model_name, bases, attrs
|
||||||
|
)
|
||||||
|
|
||||||
# Make sure that manager_inheritance_from_future is set, since django-polymorphic 1.x already
|
# Make sure that manager_inheritance_from_future is set, since django-polymorphic 1.x already
|
||||||
# simulated that behavior on the polymorphic manager to all subclasses behave like polymorphics
|
# simulated that behavior on the polymorphic manager to all subclasses behave like polymorphics
|
||||||
if django.VERSION < (2, 0):
|
if django.VERSION < (2, 0):
|
||||||
if 'Meta' in attrs:
|
if "Meta" in attrs:
|
||||||
if not hasattr(attrs['Meta'], 'manager_inheritance_from_future'):
|
if not hasattr(attrs["Meta"], "manager_inheritance_from_future"):
|
||||||
attrs['Meta'].manager_inheritance_from_future = True
|
attrs["Meta"].manager_inheritance_from_future = True
|
||||||
else:
|
else:
|
||||||
attrs['Meta'] = type('Meta', (object,), {'manager_inheritance_from_future': True})
|
attrs["Meta"] = type(
|
||||||
|
"Meta", (object,), {"manager_inheritance_from_future": True}
|
||||||
|
)
|
||||||
|
|
||||||
# create new model
|
# create new model
|
||||||
new_class = self.call_superclass_new_method(model_name, bases, attrs)
|
new_class = self.call_superclass_new_method(model_name, bases, attrs)
|
||||||
|
|
@ -103,17 +110,21 @@ class PolymorphicModelBase(ModelBase):
|
||||||
# We run into this problem if polymorphic.py is located in a top-level directory
|
# We run into this problem if polymorphic.py is located in a top-level directory
|
||||||
# which is directly in the python path. To work around this we temporarily set
|
# which is directly in the python path. To work around this we temporarily set
|
||||||
# app_label here for PolymorphicModel.
|
# app_label here for PolymorphicModel.
|
||||||
meta = attrs.get('Meta', None)
|
meta = attrs.get("Meta", None)
|
||||||
do_app_label_workaround = (meta
|
do_app_label_workaround = (
|
||||||
and attrs['__module__'] == 'polymorphic'
|
meta
|
||||||
and model_name == 'PolymorphicModel'
|
and attrs["__module__"] == "polymorphic"
|
||||||
and getattr(meta, 'app_label', None) is None)
|
and model_name == "PolymorphicModel"
|
||||||
|
and getattr(meta, "app_label", None) is None
|
||||||
|
)
|
||||||
|
|
||||||
if do_app_label_workaround:
|
if do_app_label_workaround:
|
||||||
meta.app_label = 'poly_dummy_app_label'
|
meta.app_label = "poly_dummy_app_label"
|
||||||
new_class = super(PolymorphicModelBase, self).__new__(self, model_name, bases, attrs)
|
new_class = super(PolymorphicModelBase, self).__new__(
|
||||||
|
self, model_name, bases, attrs
|
||||||
|
)
|
||||||
if do_app_label_workaround:
|
if do_app_label_workaround:
|
||||||
del(meta.app_label)
|
del meta.app_label
|
||||||
return new_class
|
return new_class
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
|
@ -133,17 +144,25 @@ class PolymorphicModelBase(ModelBase):
|
||||||
if django.VERSION < (2, 0):
|
if django.VERSION < (2, 0):
|
||||||
extra = "\nConsider using Meta.manager_inheritance_from_future = True for Django 1.x projects"
|
extra = "\nConsider using Meta.manager_inheritance_from_future = True for Django 1.x projects"
|
||||||
else:
|
else:
|
||||||
extra = ''
|
extra = ""
|
||||||
e = ('PolymorphicModel: "{0}.{1}" manager is of type "{2}", but must be a subclass of'
|
e = (
|
||||||
' PolymorphicManager.{extra} to support retrieving subclasses'.format(
|
'PolymorphicModel: "{0}.{1}" manager is of type "{2}", but must be a subclass of'
|
||||||
model_name, manager_name, type(manager).__name__, extra=extra))
|
" PolymorphicManager.{extra} to support retrieving subclasses".format(
|
||||||
|
model_name, manager_name, type(manager).__name__, extra=extra
|
||||||
|
)
|
||||||
|
)
|
||||||
warnings.warn(e, ManagerInheritanceWarning, stacklevel=3)
|
warnings.warn(e, ManagerInheritanceWarning, stacklevel=3)
|
||||||
return manager
|
return manager
|
||||||
|
|
||||||
if not getattr(manager, 'queryset_class', None) or not issubclass(manager.queryset_class, PolymorphicQuerySet):
|
if not getattr(manager, "queryset_class", None) or not issubclass(
|
||||||
e = ('PolymorphicModel: "{0}.{1}" has been instantiated with a queryset class '
|
manager.queryset_class, PolymorphicQuerySet
|
||||||
'which is not a subclass of PolymorphicQuerySet (which is required)'.format(
|
):
|
||||||
model_name, manager_name))
|
e = (
|
||||||
|
'PolymorphicModel: "{0}.{1}" has been instantiated with a queryset class '
|
||||||
|
"which is not a subclass of PolymorphicQuerySet (which is required)".format(
|
||||||
|
model_name, manager_name
|
||||||
|
)
|
||||||
|
)
|
||||||
warnings.warn(e, ManagerInheritanceWarning, stacklevel=3)
|
warnings.warn(e, ManagerInheritanceWarning, stacklevel=3)
|
||||||
return manager
|
return manager
|
||||||
|
|
||||||
|
|
@ -151,8 +170,12 @@ class PolymorphicModelBase(ModelBase):
|
||||||
def base_objects(self):
|
def base_objects(self):
|
||||||
warnings.warn(
|
warnings.warn(
|
||||||
"Using PolymorphicModel.base_objects is deprecated.\n"
|
"Using PolymorphicModel.base_objects is deprecated.\n"
|
||||||
"Use {0}.objects.non_polymorphic() instead.".format(self.__class__.__name__),
|
"Use {0}.objects.non_polymorphic() instead.".format(
|
||||||
DeprecationWarning, stacklevel=2)
|
self.__class__.__name__
|
||||||
|
),
|
||||||
|
DeprecationWarning,
|
||||||
|
stacklevel=2,
|
||||||
|
)
|
||||||
return self._base_objects
|
return self._base_objects
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
|
@ -162,13 +185,13 @@ class PolymorphicModelBase(ModelBase):
|
||||||
# manager as default manager for the third level of inheritance when
|
# manager as default manager for the third level of inheritance when
|
||||||
# that third level doesn't define a manager at all.
|
# that third level doesn't define a manager at all.
|
||||||
manager = models.Manager()
|
manager = models.Manager()
|
||||||
manager.name = 'base_objects'
|
manager.name = "base_objects"
|
||||||
manager.model = self
|
manager.model = self
|
||||||
return manager
|
return manager
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def _default_manager(self):
|
def _default_manager(self):
|
||||||
if len(sys.argv) > 1 and sys.argv[1] == 'dumpdata':
|
if len(sys.argv) > 1 and sys.argv[1] == "dumpdata":
|
||||||
# TODO: investigate Django how this can be avoided
|
# TODO: investigate Django how this can be avoided
|
||||||
# hack: a small patch to Django would be a better solution.
|
# hack: a small patch to Django would be a better solution.
|
||||||
# Django's management command 'dumpdata' relies on non-polymorphic
|
# Django's management command 'dumpdata' relies on non-polymorphic
|
||||||
|
|
@ -178,14 +201,19 @@ class PolymorphicModelBase(ModelBase):
|
||||||
# (non-polymorphic default manager is 'base_objects' for polymorphic models).
|
# (non-polymorphic default manager is 'base_objects' for polymorphic models).
|
||||||
# This way we don't need to patch django.core.management.commands.dumpdata
|
# This way we don't need to patch django.core.management.commands.dumpdata
|
||||||
# for all supported Django versions.
|
# for all supported Django versions.
|
||||||
frm = inspect.stack()[1] # frm[1] is caller file name, frm[3] is caller function name
|
frm = inspect.stack()[
|
||||||
|
1
|
||||||
|
] # frm[1] is caller file name, frm[3] is caller function name
|
||||||
if DUMPDATA_COMMAND in frm[1]:
|
if DUMPDATA_COMMAND in frm[1]:
|
||||||
return self._base_objects
|
return self._base_objects
|
||||||
|
|
||||||
manager = super(PolymorphicModelBase, self)._default_manager
|
manager = super(PolymorphicModelBase, self)._default_manager
|
||||||
if not isinstance(manager, PolymorphicManager):
|
if not isinstance(manager, PolymorphicManager):
|
||||||
warnings.warn("{0}._default_manager is not a PolymorphicManager".format(
|
warnings.warn(
|
||||||
|
"{0}._default_manager is not a PolymorphicManager".format(
|
||||||
self.__class__.__name__
|
self.__class__.__name__
|
||||||
), ManagerInheritanceWarning)
|
),
|
||||||
|
ManagerInheritanceWarning,
|
||||||
|
)
|
||||||
|
|
||||||
return manager
|
return manager
|
||||||
|
|
|
||||||
|
|
@ -1,21 +1,20 @@
|
||||||
"""Compatibility with Python 2 (taken from 'django.utils.six')"""
|
"""Compatibility with Python 2 (taken from 'django.utils.six')"""
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
|
|
||||||
PY2 = sys.version_info[0] == 2
|
PY2 = sys.version_info[0] == 2
|
||||||
PY3 = sys.version_info[0] == 3
|
PY3 = sys.version_info[0] == 3
|
||||||
|
|
||||||
|
|
||||||
if PY3:
|
if PY3:
|
||||||
string_types = str,
|
string_types = (str,)
|
||||||
integer_types = int,
|
integer_types = (int,)
|
||||||
class_types = type,
|
class_types = (type,)
|
||||||
text_type = str
|
text_type = str
|
||||||
binary_type = bytes
|
binary_type = bytes
|
||||||
|
|
||||||
MAXSIZE = sys.maxsize
|
MAXSIZE = sys.maxsize
|
||||||
else:
|
else:
|
||||||
string_types = basestring,
|
string_types = (basestring,)
|
||||||
integer_types = (int, long)
|
integer_types = (int, long)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -23,7 +22,8 @@ def with_metaclass(meta, *bases):
|
||||||
class metaclass(type):
|
class metaclass(type):
|
||||||
def __new__(cls, name, this_bases, d):
|
def __new__(cls, name, this_bases, d):
|
||||||
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):
|
def python_2_unicode_compatible(klass):
|
||||||
|
|
@ -35,10 +35,11 @@ def python_2_unicode_compatible(klass):
|
||||||
returning text and apply this decorator to the class.
|
returning text and apply this decorator to the class.
|
||||||
"""
|
"""
|
||||||
if PY2:
|
if PY2:
|
||||||
if '__str__' not in klass.__dict__:
|
if "__str__" not in klass.__dict__:
|
||||||
raise ValueError("@python_2_unicode_compatible cannot be applied "
|
raise ValueError(
|
||||||
"to %s because it doesn't define __str__()." %
|
"@python_2_unicode_compatible cannot be applied "
|
||||||
klass.__name__)
|
"to %s because it doesn't define __str__()." % klass.__name__
|
||||||
|
)
|
||||||
klass.__unicode__ = klass.__str__
|
klass.__unicode__ = klass.__str__
|
||||||
klass.__str__ = lambda self: self.__unicode__().encode('utf-8')
|
klass.__str__ = lambda self: self.__unicode__().encode("utf-8")
|
||||||
return klass
|
return klass
|
||||||
|
|
|
||||||
|
|
@ -5,15 +5,20 @@ 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
|
from __future__ import absolute_import
|
||||||
from django.core.exceptions import ImproperlyConfigured
|
|
||||||
import extra_views
|
|
||||||
from polymorphic.formsets import polymorphic_child_forms_factory, BasePolymorphicModelFormSet, BasePolymorphicInlineFormSet
|
|
||||||
|
|
||||||
|
import extra_views
|
||||||
|
from django.core.exceptions import ImproperlyConfigured
|
||||||
|
|
||||||
|
from polymorphic.formsets import (
|
||||||
|
BasePolymorphicInlineFormSet,
|
||||||
|
BasePolymorphicModelFormSet,
|
||||||
|
polymorphic_child_forms_factory,
|
||||||
|
)
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
'PolymorphicFormSetView',
|
"PolymorphicFormSetView",
|
||||||
'PolymorphicInlineFormSetView',
|
"PolymorphicInlineFormSetView",
|
||||||
'PolymorphicInlineFormSet',
|
"PolymorphicInlineFormSet",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -25,7 +30,7 @@ class PolymorphicFormSetMixin(object):
|
||||||
formset_class = BasePolymorphicModelFormSet
|
formset_class = BasePolymorphicModelFormSet
|
||||||
|
|
||||||
#: Default 0 extra forms
|
#: Default 0 extra forms
|
||||||
factory_kwargs = {'extra': 0}
|
factory_kwargs = {"extra": 0}
|
||||||
|
|
||||||
#: Define the children
|
#: Define the children
|
||||||
# :type: list[PolymorphicFormSetChild]
|
# :type: list[PolymorphicFormSetChild]
|
||||||
|
|
@ -36,7 +41,9 @@ class PolymorphicFormSetMixin(object):
|
||||||
:rtype: list[PolymorphicFormSetChild]
|
:rtype: list[PolymorphicFormSetChild]
|
||||||
"""
|
"""
|
||||||
if not self.formset_children:
|
if not self.formset_children:
|
||||||
raise ImproperlyConfigured("Define 'formset_children' as list of `PolymorphicFormSetChild`")
|
raise ImproperlyConfigured(
|
||||||
|
"Define 'formset_children' as list of `PolymorphicFormSetChild`"
|
||||||
|
)
|
||||||
return self.formset_children
|
return self.formset_children
|
||||||
|
|
||||||
def get_formset_child_kwargs(self):
|
def get_formset_child_kwargs(self):
|
||||||
|
|
@ -51,7 +58,9 @@ class PolymorphicFormSetMixin(object):
|
||||||
# reuse the standard factories, and then add `child_forms`, the same can be done here.
|
# reuse the standard factories, and then add `child_forms`, the same can be done here.
|
||||||
# This makes sure the base class construction is completely honored.
|
# This makes sure the base class construction is completely honored.
|
||||||
FormSet = super(PolymorphicFormSetMixin, self).get_formset()
|
FormSet = super(PolymorphicFormSetMixin, self).get_formset()
|
||||||
FormSet.child_forms = polymorphic_child_forms_factory(self.get_formset_children(), **self.get_formset_child_kwargs())
|
FormSet.child_forms = polymorphic_child_forms_factory(
|
||||||
|
self.get_formset_children(), **self.get_formset_child_kwargs()
|
||||||
|
)
|
||||||
return FormSet
|
return FormSet
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -72,10 +81,13 @@ class PolymorphicFormSetView(PolymorphicFormSetMixin, extra_views.ModelFormSetVi
|
||||||
]
|
]
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
formset_class = BasePolymorphicModelFormSet
|
formset_class = BasePolymorphicModelFormSet
|
||||||
|
|
||||||
|
|
||||||
class PolymorphicInlineFormSetView(PolymorphicFormSetMixin, extra_views.InlineFormSetView):
|
class PolymorphicInlineFormSetView(
|
||||||
|
PolymorphicFormSetMixin, extra_views.InlineFormSetView
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
A view that displays a single polymorphic formset - with one parent object.
|
A view that displays a single polymorphic formset - with one parent object.
|
||||||
This is a variation of the :mod:`extra_views` package classes for django-polymorphic.
|
This is a variation of the :mod:`extra_views` package classes for django-polymorphic.
|
||||||
|
|
@ -93,10 +105,13 @@ class PolymorphicInlineFormSetView(PolymorphicFormSetMixin, extra_views.InlineFo
|
||||||
PolymorphicFormSetChild(ItemSubclass2),
|
PolymorphicFormSetChild(ItemSubclass2),
|
||||||
]
|
]
|
||||||
"""
|
"""
|
||||||
|
|
||||||
formset_class = BasePolymorphicInlineFormSet
|
formset_class = BasePolymorphicInlineFormSet
|
||||||
|
|
||||||
|
|
||||||
class PolymorphicInlineFormSet(PolymorphicFormSetMixin, extra_views.InlineFormSetFactory):
|
class PolymorphicInlineFormSet(
|
||||||
|
PolymorphicFormSetMixin, extra_views.InlineFormSetFactory
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
An inline to add to the ``inlines`` of
|
An inline to add to the ``inlines`` of
|
||||||
the :class:`~extra_views.advanced.CreateWithInlinesView`
|
the :class:`~extra_views.advanced.CreateWithInlinesView`
|
||||||
|
|
@ -123,4 +138,5 @@ class PolymorphicInlineFormSet(PolymorphicFormSetMixin, extra_views.InlineFormSe
|
||||||
return self.object.get_absolute_url()
|
return self.object.get_absolute_url()
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
formset_class = BasePolymorphicInlineFormSet
|
formset_class = BasePolymorphicInlineFormSet
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ def get_polymorphic_base_content_type(obj):
|
||||||
|
|
||||||
https://django-guardian.readthedocs.io/en/latest/configuration.html#guardian-get-content-type
|
https://django-guardian.readthedocs.io/en/latest/configuration.html#guardian-get-content-type
|
||||||
"""
|
"""
|
||||||
if hasattr(obj, 'polymorphic_model_marker'):
|
if hasattr(obj, "polymorphic_model_marker"):
|
||||||
try:
|
try:
|
||||||
superclasses = list(obj.__class__.mro())
|
superclasses = list(obj.__class__.mro())
|
||||||
except TypeError:
|
except TypeError:
|
||||||
|
|
@ -19,11 +19,11 @@ def get_polymorphic_base_content_type(obj):
|
||||||
|
|
||||||
polymorphic_superclasses = list()
|
polymorphic_superclasses = list()
|
||||||
for sclass in superclasses:
|
for sclass in superclasses:
|
||||||
if hasattr(sclass, 'polymorphic_model_marker'):
|
if hasattr(sclass, "polymorphic_model_marker"):
|
||||||
polymorphic_superclasses.append(sclass)
|
polymorphic_superclasses.append(sclass)
|
||||||
|
|
||||||
# PolymorphicMPTT adds an additional class between polymorphic and base class.
|
# PolymorphicMPTT adds an additional class between polymorphic and base class.
|
||||||
if hasattr(obj, 'can_have_children'):
|
if hasattr(obj, "can_have_children"):
|
||||||
root_polymorphic_class = polymorphic_superclasses[-3]
|
root_polymorphic_class = polymorphic_superclasses[-3]
|
||||||
else:
|
else:
|
||||||
root_polymorphic_class = polymorphic_superclasses[-2]
|
root_polymorphic_class = polymorphic_superclasses[-2]
|
||||||
|
|
|
||||||
|
|
@ -8,31 +8,30 @@ For every child type, there is an :class:`PolymorphicFormSetChild` instance
|
||||||
that describes how to display and construct the child.
|
that describes how to display and construct the child.
|
||||||
It's parameters are very similar to the parent's factory method.
|
It's parameters are very similar to the parent's factory method.
|
||||||
"""
|
"""
|
||||||
from .models import (
|
from .generic import ( # Can import generic here, as polymorphic already depends on the 'contenttypes' app.
|
||||||
BasePolymorphicModelFormSet,
|
|
||||||
BasePolymorphicInlineFormSet,
|
|
||||||
PolymorphicFormSetChild,
|
|
||||||
UnsupportedChildType,
|
|
||||||
polymorphic_modelformset_factory,
|
|
||||||
polymorphic_inlineformset_factory,
|
|
||||||
polymorphic_child_forms_factory,
|
|
||||||
)
|
|
||||||
from .generic import (
|
|
||||||
# Can import generic here, as polymorphic already depends on the 'contenttypes' app.
|
|
||||||
BaseGenericPolymorphicInlineFormSet,
|
BaseGenericPolymorphicInlineFormSet,
|
||||||
GenericPolymorphicFormSetChild,
|
GenericPolymorphicFormSetChild,
|
||||||
generic_polymorphic_inlineformset_factory,
|
generic_polymorphic_inlineformset_factory,
|
||||||
)
|
)
|
||||||
|
from .models import (
|
||||||
|
BasePolymorphicInlineFormSet,
|
||||||
|
BasePolymorphicModelFormSet,
|
||||||
|
PolymorphicFormSetChild,
|
||||||
|
UnsupportedChildType,
|
||||||
|
polymorphic_child_forms_factory,
|
||||||
|
polymorphic_inlineformset_factory,
|
||||||
|
polymorphic_modelformset_factory,
|
||||||
|
)
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
'BasePolymorphicModelFormSet',
|
"BasePolymorphicModelFormSet",
|
||||||
'BasePolymorphicInlineFormSet',
|
"BasePolymorphicInlineFormSet",
|
||||||
'PolymorphicFormSetChild',
|
"PolymorphicFormSetChild",
|
||||||
'UnsupportedChildType',
|
"UnsupportedChildType",
|
||||||
'polymorphic_modelformset_factory',
|
"polymorphic_modelformset_factory",
|
||||||
'polymorphic_inlineformset_factory',
|
"polymorphic_inlineformset_factory",
|
||||||
'polymorphic_child_forms_factory',
|
"polymorphic_child_forms_factory",
|
||||||
'BaseGenericPolymorphicInlineFormSet',
|
"BaseGenericPolymorphicInlineFormSet",
|
||||||
'GenericPolymorphicFormSetChild',
|
"GenericPolymorphicFormSetChild",
|
||||||
'generic_polymorphic_inlineformset_factory',
|
"generic_polymorphic_inlineformset_factory",
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,16 @@
|
||||||
from django.contrib.contenttypes.forms import BaseGenericInlineFormSet, generic_inlineformset_factory
|
from django.contrib.contenttypes.forms import (
|
||||||
|
BaseGenericInlineFormSet,
|
||||||
|
generic_inlineformset_factory,
|
||||||
|
)
|
||||||
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.forms.models import ModelForm
|
from django.forms.models import ModelForm
|
||||||
|
|
||||||
from .models import BasePolymorphicModelFormSet, polymorphic_child_forms_factory, PolymorphicFormSetChild
|
from .models import (
|
||||||
|
BasePolymorphicModelFormSet,
|
||||||
|
PolymorphicFormSetChild,
|
||||||
|
polymorphic_child_forms_factory,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class GenericPolymorphicFormSetChild(PolymorphicFormSetChild):
|
class GenericPolymorphicFormSetChild(PolymorphicFormSetChild):
|
||||||
|
|
@ -12,8 +19,8 @@ class GenericPolymorphicFormSetChild(PolymorphicFormSetChild):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
self.ct_field = kwargs.pop('ct_field', 'content_type')
|
self.ct_field = kwargs.pop("ct_field", "content_type")
|
||||||
self.fk_field = kwargs.pop('fk_field', 'object_id')
|
self.fk_field = kwargs.pop("fk_field", "object_id")
|
||||||
super(GenericPolymorphicFormSetChild, self).__init__(*args, **kwargs)
|
super(GenericPolymorphicFormSetChild, self).__init__(*args, **kwargs)
|
||||||
|
|
||||||
def get_form(self, ct_field="content_type", fk_field="object_id", **kwargs):
|
def get_form(self, ct_field="content_type", fk_field="object_id", **kwargs):
|
||||||
|
|
@ -21,7 +28,7 @@ class GenericPolymorphicFormSetChild(PolymorphicFormSetChild):
|
||||||
Construct the form class for the formset child.
|
Construct the form class for the formset child.
|
||||||
"""
|
"""
|
||||||
exclude = list(self.exclude)
|
exclude = list(self.exclude)
|
||||||
extra_exclude = kwargs.pop('extra_exclude', None)
|
extra_exclude = kwargs.pop("extra_exclude", None)
|
||||||
if extra_exclude:
|
if extra_exclude:
|
||||||
exclude += list(extra_exclude)
|
exclude += list(extra_exclude)
|
||||||
|
|
||||||
|
|
@ -31,33 +38,52 @@ class GenericPolymorphicFormSetChild(PolymorphicFormSetChild):
|
||||||
opts = self.model._meta
|
opts = self.model._meta
|
||||||
ct_field = opts.get_field(self.ct_field)
|
ct_field = opts.get_field(self.ct_field)
|
||||||
|
|
||||||
if not isinstance(ct_field, models.ForeignKey) or ct_field.remote_field.model != ContentType:
|
if (
|
||||||
raise Exception("fk_name '%s' is not a ForeignKey to ContentType" % ct_field)
|
not isinstance(ct_field, models.ForeignKey)
|
||||||
|
or ct_field.remote_field.model != ContentType
|
||||||
|
):
|
||||||
|
raise Exception(
|
||||||
|
"fk_name '%s' is not a ForeignKey to ContentType" % ct_field
|
||||||
|
)
|
||||||
|
|
||||||
fk_field = opts.get_field(self.fk_field) # let the exception propagate
|
fk_field = opts.get_field(self.fk_field) # let the exception propagate
|
||||||
exclude.extend([ct_field.name, fk_field.name])
|
exclude.extend([ct_field.name, fk_field.name])
|
||||||
kwargs['exclude'] = exclude
|
kwargs["exclude"] = exclude
|
||||||
|
|
||||||
return super(GenericPolymorphicFormSetChild, self).get_form(**kwargs)
|
return super(GenericPolymorphicFormSetChild, self).get_form(**kwargs)
|
||||||
|
|
||||||
|
|
||||||
class BaseGenericPolymorphicInlineFormSet(BaseGenericInlineFormSet, BasePolymorphicModelFormSet):
|
class BaseGenericPolymorphicInlineFormSet(
|
||||||
|
BaseGenericInlineFormSet, BasePolymorphicModelFormSet
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
Polymorphic formset variation for inline generic formsets
|
Polymorphic formset variation for inline generic formsets
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
def generic_polymorphic_inlineformset_factory(model, formset_children, form=ModelForm,
|
def generic_polymorphic_inlineformset_factory(
|
||||||
|
model,
|
||||||
|
formset_children,
|
||||||
|
form=ModelForm,
|
||||||
formset=BaseGenericPolymorphicInlineFormSet,
|
formset=BaseGenericPolymorphicInlineFormSet,
|
||||||
ct_field="content_type", fk_field="object_id",
|
ct_field="content_type",
|
||||||
|
fk_field="object_id",
|
||||||
# Base form
|
# Base form
|
||||||
# TODO: should these fields be removed in favor of creating
|
# TODO: should these fields be removed in favor of creating
|
||||||
# the base form as a formset child too?
|
# the base form as a formset child too?
|
||||||
fields=None, exclude=None,
|
fields=None,
|
||||||
extra=1, can_order=False, can_delete=True,
|
exclude=None,
|
||||||
max_num=None, formfield_callback=None,
|
extra=1,
|
||||||
validate_max=False, for_concrete_model=True,
|
can_order=False,
|
||||||
min_num=None, validate_min=False, child_form_kwargs=None):
|
can_delete=True,
|
||||||
|
max_num=None,
|
||||||
|
formfield_callback=None,
|
||||||
|
validate_max=False,
|
||||||
|
for_concrete_model=True,
|
||||||
|
min_num=None,
|
||||||
|
validate_min=False,
|
||||||
|
child_form_kwargs=None,
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
Construct the class for a generic inline polymorphic formset.
|
Construct the class for a generic inline polymorphic formset.
|
||||||
|
|
||||||
|
|
@ -70,22 +96,22 @@ def generic_polymorphic_inlineformset_factory(model, formset_children, form=Mode
|
||||||
:rtype: type
|
:rtype: type
|
||||||
"""
|
"""
|
||||||
kwargs = {
|
kwargs = {
|
||||||
'model': model,
|
"model": model,
|
||||||
'form': form,
|
"form": form,
|
||||||
'formfield_callback': formfield_callback,
|
"formfield_callback": formfield_callback,
|
||||||
'formset': formset,
|
"formset": formset,
|
||||||
'ct_field': ct_field,
|
"ct_field": ct_field,
|
||||||
'fk_field': fk_field,
|
"fk_field": fk_field,
|
||||||
'extra': extra,
|
"extra": extra,
|
||||||
'can_delete': can_delete,
|
"can_delete": can_delete,
|
||||||
'can_order': can_order,
|
"can_order": can_order,
|
||||||
'fields': fields,
|
"fields": fields,
|
||||||
'exclude': exclude,
|
"exclude": exclude,
|
||||||
'min_num': min_num,
|
"min_num": min_num,
|
||||||
'max_num': max_num,
|
"max_num": max_num,
|
||||||
'validate_min': validate_min,
|
"validate_min": validate_min,
|
||||||
'validate_max': validate_max,
|
"validate_max": validate_max,
|
||||||
'for_concrete_model': for_concrete_model,
|
"for_concrete_model": for_concrete_model,
|
||||||
# 'localized_fields': localized_fields,
|
# 'localized_fields': localized_fields,
|
||||||
# 'labels': labels,
|
# 'labels': labels,
|
||||||
# 'help_texts': help_texts,
|
# 'help_texts': help_texts,
|
||||||
|
|
@ -97,12 +123,14 @@ def generic_polymorphic_inlineformset_factory(model, formset_children, form=Mode
|
||||||
|
|
||||||
child_kwargs = {
|
child_kwargs = {
|
||||||
# 'exclude': exclude,
|
# 'exclude': exclude,
|
||||||
'ct_field': ct_field,
|
"ct_field": ct_field,
|
||||||
'fk_field': fk_field,
|
"fk_field": fk_field,
|
||||||
}
|
}
|
||||||
if child_form_kwargs:
|
if child_form_kwargs:
|
||||||
child_kwargs.update(child_form_kwargs)
|
child_kwargs.update(child_form_kwargs)
|
||||||
|
|
||||||
FormSet = generic_inlineformset_factory(**kwargs)
|
FormSet = generic_inlineformset_factory(**kwargs)
|
||||||
FormSet.child_forms = polymorphic_child_forms_factory(formset_children, **child_kwargs)
|
FormSet.child_forms = polymorphic_child_forms_factory(
|
||||||
|
formset_children, **child_kwargs
|
||||||
|
)
|
||||||
return FormSet
|
return FormSet
|
||||||
|
|
|
||||||
|
|
@ -3,10 +3,18 @@ from collections import OrderedDict
|
||||||
from django import forms
|
from django import forms
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.core.exceptions import ImproperlyConfigured, ValidationError
|
from django.core.exceptions import ImproperlyConfigured, ValidationError
|
||||||
from django.forms.models import ModelForm, BaseModelFormSet, BaseInlineFormSet, modelform_factory, modelformset_factory, inlineformset_factory
|
from django.forms.models import (
|
||||||
|
BaseInlineFormSet,
|
||||||
|
BaseModelFormSet,
|
||||||
|
ModelForm,
|
||||||
|
inlineformset_factory,
|
||||||
|
modelform_factory,
|
||||||
|
modelformset_factory,
|
||||||
|
)
|
||||||
from django.utils.functional import cached_property
|
from django.utils.functional import cached_property
|
||||||
|
|
||||||
from polymorphic.models import PolymorphicModel
|
from polymorphic.models import PolymorphicModel
|
||||||
|
|
||||||
from .utils import add_media
|
from .utils import add_media
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -20,9 +28,19 @@ class PolymorphicFormSetChild(object):
|
||||||
Provide this information in the :func:'polymorphic_inlineformset_factory' construction.
|
Provide this information in the :func:'polymorphic_inlineformset_factory' construction.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, model, form=ModelForm, fields=None, exclude=None,
|
def __init__(
|
||||||
formfield_callback=None, widgets=None, localized_fields=None,
|
self,
|
||||||
labels=None, help_texts=None, error_messages=None):
|
model,
|
||||||
|
form=ModelForm,
|
||||||
|
fields=None,
|
||||||
|
exclude=None,
|
||||||
|
formfield_callback=None,
|
||||||
|
widgets=None,
|
||||||
|
localized_fields=None,
|
||||||
|
labels=None,
|
||||||
|
help_texts=None,
|
||||||
|
error_messages=None,
|
||||||
|
):
|
||||||
|
|
||||||
self.model = model
|
self.model = model
|
||||||
|
|
||||||
|
|
@ -59,20 +77,20 @@ class PolymorphicFormSetChild(object):
|
||||||
# we allow to define things like 'extra_...' fields that are amended to the current child settings.
|
# we allow to define things like 'extra_...' fields that are amended to the current child settings.
|
||||||
|
|
||||||
exclude = list(self.exclude)
|
exclude = list(self.exclude)
|
||||||
extra_exclude = kwargs.pop('extra_exclude', None)
|
extra_exclude = kwargs.pop("extra_exclude", None)
|
||||||
if extra_exclude:
|
if extra_exclude:
|
||||||
exclude += list(extra_exclude)
|
exclude += list(extra_exclude)
|
||||||
|
|
||||||
defaults = {
|
defaults = {
|
||||||
'form': self._form_base,
|
"form": self._form_base,
|
||||||
'formfield_callback': self.formfield_callback,
|
"formfield_callback": self.formfield_callback,
|
||||||
'fields': self.fields,
|
"fields": self.fields,
|
||||||
'exclude': exclude,
|
"exclude": exclude,
|
||||||
# 'for_concrete_model': for_concrete_model,
|
# 'for_concrete_model': for_concrete_model,
|
||||||
'localized_fields': self.localized_fields,
|
"localized_fields": self.localized_fields,
|
||||||
'labels': self.labels,
|
"labels": self.labels,
|
||||||
'help_texts': self.help_texts,
|
"help_texts": self.help_texts,
|
||||||
'error_messages': self.error_messages,
|
"error_messages": self.error_messages,
|
||||||
# 'field_classes': field_classes,
|
# 'field_classes': field_classes,
|
||||||
}
|
}
|
||||||
defaults.update(kwargs)
|
defaults.update(kwargs)
|
||||||
|
|
@ -125,61 +143,71 @@ class BasePolymorphicModelFormSet(BaseModelFormSet):
|
||||||
pk_field = self.model._meta.pk
|
pk_field = self.model._meta.pk
|
||||||
to_python = self._get_to_python(pk_field)
|
to_python = self._get_to_python(pk_field)
|
||||||
pk = to_python(pk)
|
pk = to_python(pk)
|
||||||
kwargs['instance'] = self._existing_object(pk)
|
kwargs["instance"] = self._existing_object(pk)
|
||||||
if i < self.initial_form_count() and 'instance' not in kwargs:
|
if i < self.initial_form_count() and "instance" not in kwargs:
|
||||||
kwargs['instance'] = self.get_queryset()[i]
|
kwargs["instance"] = self.get_queryset()[i]
|
||||||
if i >= self.initial_form_count() and self.initial_extra:
|
if i >= self.initial_form_count() and self.initial_extra:
|
||||||
# Set initial values for extra forms
|
# Set initial values for extra forms
|
||||||
try:
|
try:
|
||||||
kwargs['initial'] = self.initial_extra[i - self.initial_form_count()]
|
kwargs["initial"] = self.initial_extra[i - self.initial_form_count()]
|
||||||
except IndexError:
|
except IndexError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# BaseFormSet logic, with custom formset_class
|
# BaseFormSet logic, with custom formset_class
|
||||||
defaults = {
|
defaults = {
|
||||||
'auto_id': self.auto_id,
|
"auto_id": self.auto_id,
|
||||||
'prefix': self.add_prefix(i),
|
"prefix": self.add_prefix(i),
|
||||||
'error_class': self.error_class,
|
"error_class": self.error_class,
|
||||||
}
|
}
|
||||||
if self.is_bound:
|
if self.is_bound:
|
||||||
defaults['data'] = self.data
|
defaults["data"] = self.data
|
||||||
defaults['files'] = self.files
|
defaults["files"] = self.files
|
||||||
if self.initial and 'initial' not in kwargs:
|
if self.initial and "initial" not in kwargs:
|
||||||
try:
|
try:
|
||||||
defaults['initial'] = self.initial[i]
|
defaults["initial"] = self.initial[i]
|
||||||
except IndexError:
|
except IndexError:
|
||||||
pass
|
pass
|
||||||
# Allow extra forms to be empty, unless they're part of
|
# Allow extra forms to be empty, unless they're part of
|
||||||
# the minimum forms.
|
# the minimum forms.
|
||||||
if i >= self.initial_form_count() and i >= self.min_num:
|
if i >= self.initial_form_count() and i >= self.min_num:
|
||||||
defaults['empty_permitted'] = True
|
defaults["empty_permitted"] = True
|
||||||
defaults['use_required_attribute'] = False
|
defaults["use_required_attribute"] = False
|
||||||
defaults.update(kwargs)
|
defaults.update(kwargs)
|
||||||
|
|
||||||
# Need to find the model that will be displayed in this form.
|
# Need to find the model that will be displayed in this form.
|
||||||
# Hence, peeking in the self.queryset_data beforehand.
|
# Hence, peeking in the self.queryset_data beforehand.
|
||||||
if self.is_bound:
|
if self.is_bound:
|
||||||
if 'instance' in defaults:
|
if "instance" in defaults:
|
||||||
# Object is already bound to a model, won't change the content type
|
# Object is already bound to a model, won't change the content type
|
||||||
model = defaults['instance'].get_real_instance_class() # allow proxy models
|
model = defaults[
|
||||||
|
"instance"
|
||||||
|
].get_real_instance_class() # allow proxy models
|
||||||
else:
|
else:
|
||||||
# Extra or empty form, use the provided type.
|
# Extra or empty form, use the provided type.
|
||||||
# Note this completely tru
|
# Note this completely tru
|
||||||
prefix = defaults['prefix']
|
prefix = defaults["prefix"]
|
||||||
try:
|
try:
|
||||||
ct_id = int(self.data["{0}-polymorphic_ctype".format(prefix)])
|
ct_id = int(self.data["{0}-polymorphic_ctype".format(prefix)])
|
||||||
except (KeyError, ValueError):
|
except (KeyError, ValueError):
|
||||||
raise ValidationError("Formset row {0} has no 'polymorphic_ctype' defined!".format(prefix))
|
raise ValidationError(
|
||||||
|
"Formset row {0} has no 'polymorphic_ctype' defined!".format(
|
||||||
|
prefix
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
model = ContentType.objects.get_for_id(ct_id).model_class()
|
model = ContentType.objects.get_for_id(ct_id).model_class()
|
||||||
if model not in self.child_forms:
|
if model not in self.child_forms:
|
||||||
# Perform basic validation, as we skip the ChoiceField here.
|
# Perform basic validation, as we skip the ChoiceField here.
|
||||||
raise UnsupportedChildType("Child model type {0} is not part of the formset".format(model))
|
raise UnsupportedChildType(
|
||||||
|
"Child model type {0} is not part of the formset".format(model)
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
if 'instance' in defaults:
|
if "instance" in defaults:
|
||||||
model = defaults['instance'].get_real_instance_class() # allow proxy models
|
model = defaults[
|
||||||
elif 'polymorphic_ctype' in defaults.get('initial', {}):
|
"instance"
|
||||||
model = defaults['initial']['polymorphic_ctype'].model_class()
|
].get_real_instance_class() # allow proxy models
|
||||||
|
elif "polymorphic_ctype" in defaults.get("initial", {}):
|
||||||
|
model = defaults["initial"]["polymorphic_ctype"].model_class()
|
||||||
elif i < len(self.queryset_data):
|
elif i < len(self.queryset_data):
|
||||||
model = self.queryset_data[i].__class__
|
model = self.queryset_data[i].__class__
|
||||||
else:
|
else:
|
||||||
|
|
@ -196,9 +224,13 @@ class BasePolymorphicModelFormSet(BaseModelFormSet):
|
||||||
|
|
||||||
def add_fields(self, form, index):
|
def add_fields(self, form, index):
|
||||||
"""Add a hidden field for the content type."""
|
"""Add a hidden field for the content type."""
|
||||||
ct = ContentType.objects.get_for_model(form._meta.model, for_concrete_model=False)
|
ct = ContentType.objects.get_for_model(
|
||||||
|
form._meta.model, for_concrete_model=False
|
||||||
|
)
|
||||||
choices = [(ct.pk, ct)] # Single choice, existing forms can't change the value.
|
choices = [(ct.pk, ct)] # Single choice, existing forms can't change the value.
|
||||||
form.fields['polymorphic_ctype'] = forms.ChoiceField(choices=choices, initial=ct.pk, required=False, widget=forms.HiddenInput)
|
form.fields["polymorphic_ctype"] = forms.ChoiceField(
|
||||||
|
choices=choices, initial=ct.pk, required=False, widget=forms.HiddenInput
|
||||||
|
)
|
||||||
super(BasePolymorphicModelFormSet, self).add_fields(form, index)
|
super(BasePolymorphicModelFormSet, self).add_fields(form, index)
|
||||||
|
|
||||||
def get_form_class(self, model):
|
def get_form_class(self, model):
|
||||||
|
|
@ -206,7 +238,9 @@ class BasePolymorphicModelFormSet(BaseModelFormSet):
|
||||||
Return the proper form class for the given model.
|
Return the proper form class for the given model.
|
||||||
"""
|
"""
|
||||||
if not self.child_forms:
|
if not self.child_forms:
|
||||||
raise ImproperlyConfigured("No 'child_forms' defined in {0}".format(self.__class__.__name__))
|
raise ImproperlyConfigured(
|
||||||
|
"No 'child_forms' defined in {0}".format(self.__class__.__name__)
|
||||||
|
)
|
||||||
if not issubclass(model, PolymorphicModel):
|
if not issubclass(model, PolymorphicModel):
|
||||||
raise TypeError("Expect polymorphic model type, not {0}".format(model))
|
raise TypeError("Expect polymorphic model type, not {0}".format(model))
|
||||||
|
|
||||||
|
|
@ -218,7 +252,8 @@ class BasePolymorphicModelFormSet(BaseModelFormSet):
|
||||||
"The '{0}' found a '{1}' model in the queryset, "
|
"The '{0}' found a '{1}' model in the queryset, "
|
||||||
"but no form class is registered to display it.".format(
|
"but no form class is registered to display it.".format(
|
||||||
self.__class__.__name__, model.__name__
|
self.__class__.__name__, model.__name__
|
||||||
))
|
)
|
||||||
|
)
|
||||||
|
|
||||||
def is_multipart(self):
|
def is_multipart(self):
|
||||||
"""
|
"""
|
||||||
|
|
@ -247,7 +282,7 @@ class BasePolymorphicModelFormSet(BaseModelFormSet):
|
||||||
|
|
||||||
form = form_class(
|
form = form_class(
|
||||||
auto_id=self.auto_id,
|
auto_id=self.auto_id,
|
||||||
prefix=self.add_prefix('__prefix__'),
|
prefix=self.add_prefix("__prefix__"),
|
||||||
empty_permitted=True,
|
empty_permitted=True,
|
||||||
use_required_attribute=False,
|
use_required_attribute=False,
|
||||||
**kwargs
|
**kwargs
|
||||||
|
|
@ -259,20 +294,37 @@ class BasePolymorphicModelFormSet(BaseModelFormSet):
|
||||||
@property
|
@property
|
||||||
def empty_form(self):
|
def empty_form(self):
|
||||||
# TODO: make an exception when can_add_base is defined?
|
# TODO: make an exception when can_add_base is defined?
|
||||||
raise RuntimeError("'empty_form' is not used in polymorphic formsets, use 'empty_forms' instead.")
|
raise RuntimeError(
|
||||||
|
"'empty_form' is not used in polymorphic formsets, use 'empty_forms' instead."
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def polymorphic_modelformset_factory(model, formset_children,
|
def polymorphic_modelformset_factory(
|
||||||
|
model,
|
||||||
|
formset_children,
|
||||||
formset=BasePolymorphicModelFormSet,
|
formset=BasePolymorphicModelFormSet,
|
||||||
# Base field
|
# Base field
|
||||||
# TODO: should these fields be removed in favor of creating
|
# TODO: should these fields be removed in favor of creating
|
||||||
# the base form as a formset child too?
|
# the base form as a formset child too?
|
||||||
form=ModelForm,
|
form=ModelForm,
|
||||||
fields=None, exclude=None, extra=1, can_order=False,
|
fields=None,
|
||||||
can_delete=True, max_num=None, formfield_callback=None,
|
exclude=None,
|
||||||
widgets=None, validate_max=False, localized_fields=None,
|
extra=1,
|
||||||
labels=None, help_texts=None, error_messages=None,
|
can_order=False,
|
||||||
min_num=None, validate_min=False, field_classes=None, child_form_kwargs=None):
|
can_delete=True,
|
||||||
|
max_num=None,
|
||||||
|
formfield_callback=None,
|
||||||
|
widgets=None,
|
||||||
|
validate_max=False,
|
||||||
|
localized_fields=None,
|
||||||
|
labels=None,
|
||||||
|
help_texts=None,
|
||||||
|
error_messages=None,
|
||||||
|
min_num=None,
|
||||||
|
validate_min=False,
|
||||||
|
field_classes=None,
|
||||||
|
child_form_kwargs=None,
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
Construct the class for an polymorphic model formset.
|
Construct the class for an polymorphic model formset.
|
||||||
|
|
||||||
|
|
@ -285,25 +337,25 @@ def polymorphic_modelformset_factory(model, formset_children,
|
||||||
:rtype: type
|
:rtype: type
|
||||||
"""
|
"""
|
||||||
kwargs = {
|
kwargs = {
|
||||||
'model': model,
|
"model": model,
|
||||||
'form': form,
|
"form": form,
|
||||||
'formfield_callback': formfield_callback,
|
"formfield_callback": formfield_callback,
|
||||||
'formset': formset,
|
"formset": formset,
|
||||||
'extra': extra,
|
"extra": extra,
|
||||||
'can_delete': can_delete,
|
"can_delete": can_delete,
|
||||||
'can_order': can_order,
|
"can_order": can_order,
|
||||||
'fields': fields,
|
"fields": fields,
|
||||||
'exclude': exclude,
|
"exclude": exclude,
|
||||||
'min_num': min_num,
|
"min_num": min_num,
|
||||||
'max_num': max_num,
|
"max_num": max_num,
|
||||||
'widgets': widgets,
|
"widgets": widgets,
|
||||||
'validate_min': validate_min,
|
"validate_min": validate_min,
|
||||||
'validate_max': validate_max,
|
"validate_max": validate_max,
|
||||||
'localized_fields': localized_fields,
|
"localized_fields": localized_fields,
|
||||||
'labels': labels,
|
"labels": labels,
|
||||||
'help_texts': help_texts,
|
"help_texts": help_texts,
|
||||||
'error_messages': error_messages,
|
"error_messages": error_messages,
|
||||||
'field_classes': field_classes,
|
"field_classes": field_classes,
|
||||||
}
|
}
|
||||||
FormSet = modelformset_factory(**kwargs)
|
FormSet = modelformset_factory(**kwargs)
|
||||||
|
|
||||||
|
|
@ -313,7 +365,9 @@ def polymorphic_modelformset_factory(model, formset_children,
|
||||||
if child_form_kwargs:
|
if child_form_kwargs:
|
||||||
child_kwargs.update(child_form_kwargs)
|
child_kwargs.update(child_form_kwargs)
|
||||||
|
|
||||||
FormSet.child_forms = polymorphic_child_forms_factory(formset_children, **child_kwargs)
|
FormSet.child_forms = polymorphic_child_forms_factory(
|
||||||
|
formset_children, **child_kwargs
|
||||||
|
)
|
||||||
return FormSet
|
return FormSet
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -326,17 +380,34 @@ class BasePolymorphicInlineFormSet(BaseInlineFormSet, BasePolymorphicModelFormSe
|
||||||
return super(BasePolymorphicInlineFormSet, self)._construct_form(i, **kwargs)
|
return super(BasePolymorphicInlineFormSet, self)._construct_form(i, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
def polymorphic_inlineformset_factory(parent_model, model, formset_children,
|
def polymorphic_inlineformset_factory(
|
||||||
formset=BasePolymorphicInlineFormSet, fk_name=None,
|
parent_model,
|
||||||
|
model,
|
||||||
|
formset_children,
|
||||||
|
formset=BasePolymorphicInlineFormSet,
|
||||||
|
fk_name=None,
|
||||||
# Base field
|
# Base field
|
||||||
# TODO: should these fields be removed in favor of creating
|
# TODO: should these fields be removed in favor of creating
|
||||||
# the base form as a formset child too?
|
# the base form as a formset child too?
|
||||||
form=ModelForm,
|
form=ModelForm,
|
||||||
fields=None, exclude=None, extra=1, can_order=False,
|
fields=None,
|
||||||
can_delete=True, max_num=None, formfield_callback=None,
|
exclude=None,
|
||||||
widgets=None, validate_max=False, localized_fields=None,
|
extra=1,
|
||||||
labels=None, help_texts=None, error_messages=None,
|
can_order=False,
|
||||||
min_num=None, validate_min=False, field_classes=None, child_form_kwargs=None):
|
can_delete=True,
|
||||||
|
max_num=None,
|
||||||
|
formfield_callback=None,
|
||||||
|
widgets=None,
|
||||||
|
validate_max=False,
|
||||||
|
localized_fields=None,
|
||||||
|
labels=None,
|
||||||
|
help_texts=None,
|
||||||
|
error_messages=None,
|
||||||
|
min_num=None,
|
||||||
|
validate_min=False,
|
||||||
|
field_classes=None,
|
||||||
|
child_form_kwargs=None,
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
Construct the class for an inline polymorphic formset.
|
Construct the class for an inline polymorphic formset.
|
||||||
|
|
||||||
|
|
@ -349,27 +420,27 @@ def polymorphic_inlineformset_factory(parent_model, model, formset_children,
|
||||||
:rtype: type
|
:rtype: type
|
||||||
"""
|
"""
|
||||||
kwargs = {
|
kwargs = {
|
||||||
'parent_model': parent_model,
|
"parent_model": parent_model,
|
||||||
'model': model,
|
"model": model,
|
||||||
'form': form,
|
"form": form,
|
||||||
'formfield_callback': formfield_callback,
|
"formfield_callback": formfield_callback,
|
||||||
'formset': formset,
|
"formset": formset,
|
||||||
'fk_name': fk_name,
|
"fk_name": fk_name,
|
||||||
'extra': extra,
|
"extra": extra,
|
||||||
'can_delete': can_delete,
|
"can_delete": can_delete,
|
||||||
'can_order': can_order,
|
"can_order": can_order,
|
||||||
'fields': fields,
|
"fields": fields,
|
||||||
'exclude': exclude,
|
"exclude": exclude,
|
||||||
'min_num': min_num,
|
"min_num": min_num,
|
||||||
'max_num': max_num,
|
"max_num": max_num,
|
||||||
'widgets': widgets,
|
"widgets": widgets,
|
||||||
'validate_min': validate_min,
|
"validate_min": validate_min,
|
||||||
'validate_max': validate_max,
|
"validate_max": validate_max,
|
||||||
'localized_fields': localized_fields,
|
"localized_fields": localized_fields,
|
||||||
'labels': labels,
|
"labels": labels,
|
||||||
'help_texts': help_texts,
|
"help_texts": help_texts,
|
||||||
'error_messages': error_messages,
|
"error_messages": error_messages,
|
||||||
'field_classes': field_classes,
|
"field_classes": field_classes,
|
||||||
}
|
}
|
||||||
FormSet = inlineformset_factory(**kwargs)
|
FormSet = inlineformset_factory(**kwargs)
|
||||||
|
|
||||||
|
|
@ -379,5 +450,7 @@ def polymorphic_inlineformset_factory(parent_model, model, formset_children,
|
||||||
if child_form_kwargs:
|
if child_form_kwargs:
|
||||||
child_kwargs.update(child_form_kwargs)
|
child_kwargs.update(child_form_kwargs)
|
||||||
|
|
||||||
FormSet.child_forms = polymorphic_child_forms_factory(formset_children, **child_kwargs)
|
FormSet.child_forms = polymorphic_child_forms_factory(
|
||||||
|
formset_children, **child_kwargs
|
||||||
|
)
|
||||||
return FormSet
|
return FormSet
|
||||||
|
|
|
||||||
|
|
@ -5,14 +5,11 @@ The manager class for use in the models.
|
||||||
from __future__ import unicode_literals
|
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.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
|
@python_2_unicode_compatible
|
||||||
|
|
@ -23,12 +20,17 @@ class PolymorphicManager(models.Manager):
|
||||||
Usually not explicitly needed, except if a custom manager or
|
Usually not explicitly needed, except if a custom manager or
|
||||||
a custom queryset class is to be used.
|
a custom queryset class is to be used.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
queryset_class = PolymorphicQuerySet
|
queryset_class = PolymorphicQuerySet
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_queryset(cls, queryset_class, class_name=None):
|
def from_queryset(cls, queryset_class, class_name=None):
|
||||||
manager = super(PolymorphicManager, cls).from_queryset(queryset_class, class_name=class_name)
|
manager = super(PolymorphicManager, cls).from_queryset(
|
||||||
manager.queryset_class = queryset_class # also set our version, Django uses _queryset_class
|
queryset_class, class_name=class_name
|
||||||
|
)
|
||||||
|
manager.queryset_class = (
|
||||||
|
queryset_class
|
||||||
|
) # also set our version, Django uses _queryset_class
|
||||||
return manager
|
return manager
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
|
|
@ -38,7 +40,10 @@ class PolymorphicManager(models.Manager):
|
||||||
return qs
|
return qs
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return '%s (PolymorphicManager) using %s' % (self.__class__.__name__, self.queryset_class.__name__)
|
return "%s (PolymorphicManager) using %s" % (
|
||||||
|
self.__class__.__name__,
|
||||||
|
self.queryset_class.__name__,
|
||||||
|
)
|
||||||
|
|
||||||
# Proxied methods
|
# Proxied methods
|
||||||
def non_polymorphic(self):
|
def non_polymorphic(self):
|
||||||
|
|
|
||||||
|
|
@ -6,10 +6,14 @@ 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.models.fields.related import (
|
||||||
|
ForwardManyToOneDescriptor,
|
||||||
|
ReverseOneToOneDescriptor,
|
||||||
|
)
|
||||||
from django.db.utils import DEFAULT_DB_ALIAS
|
from django.db.utils import DEFAULT_DB_ALIAS
|
||||||
|
|
||||||
from polymorphic.compat import with_metaclass
|
from polymorphic.compat import with_metaclass
|
||||||
|
|
||||||
from .base import PolymorphicModelBase
|
from .base import PolymorphicModelBase
|
||||||
from .managers import PolymorphicManager
|
from .managers import PolymorphicManager
|
||||||
from .query_translate import translate_polymorphic_Q_object
|
from .query_translate import translate_polymorphic_Q_object
|
||||||
|
|
@ -44,12 +48,15 @@ class PolymorphicModel(with_metaclass(PolymorphicModelBase, models.Model)):
|
||||||
# avoid ContentType related field accessor clash (an error emitted by model validation)
|
# avoid ContentType related field accessor clash (an error emitted by model validation)
|
||||||
#: The model field that stores the :class:`~django.contrib.contenttypes.models.ContentType` reference to the actual class.
|
#: The model field that stores the :class:`~django.contrib.contenttypes.models.ContentType` reference to the actual class.
|
||||||
polymorphic_ctype = models.ForeignKey(
|
polymorphic_ctype = models.ForeignKey(
|
||||||
ContentType, null=True, editable=False, on_delete=models.CASCADE,
|
ContentType,
|
||||||
related_name='polymorphic_%(app_label)s.%(class)s_set+'
|
null=True,
|
||||||
|
editable=False,
|
||||||
|
on_delete=models.CASCADE,
|
||||||
|
related_name="polymorphic_%(app_label)s.%(class)s_set+",
|
||||||
)
|
)
|
||||||
|
|
||||||
# some applications want to know the name of the fields that are added to its models
|
# some applications want to know the name of the fields that are added to its models
|
||||||
polymorphic_internal_model_fields = ['polymorphic_ctype']
|
polymorphic_internal_model_fields = ["polymorphic_ctype"]
|
||||||
|
|
||||||
# Note that Django 1.5 removes these managers because the model is abstract.
|
# Note that Django 1.5 removes these managers because the model is abstract.
|
||||||
# They are pretended to be there by the metaclass in PolymorphicModelBase.get_inherited_managers()
|
# They are pretended to be there by the metaclass in PolymorphicModelBase.get_inherited_managers()
|
||||||
|
|
@ -73,14 +80,18 @@ class PolymorphicModel(with_metaclass(PolymorphicModelBase, models.Model)):
|
||||||
# field to figure out the real class of this object
|
# field to figure out the real class of this object
|
||||||
# (used by PolymorphicQuerySet._get_real_instances)
|
# (used by PolymorphicQuerySet._get_real_instances)
|
||||||
if not self.polymorphic_ctype_id:
|
if not self.polymorphic_ctype_id:
|
||||||
self.polymorphic_ctype = ContentType.objects.db_manager(using).get_for_model(self, for_concrete_model=False)
|
self.polymorphic_ctype = ContentType.objects.db_manager(
|
||||||
|
using
|
||||||
|
).get_for_model(self, for_concrete_model=False)
|
||||||
|
|
||||||
pre_save_polymorphic.alters_data = True
|
pre_save_polymorphic.alters_data = True
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
"""Calls :meth:`pre_save_polymorphic` and saves the model."""
|
"""Calls :meth:`pre_save_polymorphic` and saves the model."""
|
||||||
using = kwargs.get('using', self._state.db or DEFAULT_DB_ALIAS)
|
using = kwargs.get("using", self._state.db or DEFAULT_DB_ALIAS)
|
||||||
self.pre_save_polymorphic(using=using)
|
self.pre_save_polymorphic(using=using)
|
||||||
return super(PolymorphicModel, self).save(*args, **kwargs)
|
return super(PolymorphicModel, self).save(*args, **kwargs)
|
||||||
|
|
||||||
save.alters_data = True
|
save.alters_data = True
|
||||||
|
|
||||||
def get_real_instance_class(self):
|
def get_real_instance_class(self):
|
||||||
|
|
@ -92,28 +103,40 @@ class PolymorphicModel(with_metaclass(PolymorphicModelBase, models.Model)):
|
||||||
determined using this method.
|
determined using this method.
|
||||||
"""
|
"""
|
||||||
if self.polymorphic_ctype_id is None:
|
if self.polymorphic_ctype_id is None:
|
||||||
raise PolymorphicTypeUndefined((
|
raise PolymorphicTypeUndefined(
|
||||||
|
(
|
||||||
"The model {}#{} does not have a `polymorphic_ctype_id` value defined.\n"
|
"The model {}#{} does not have a `polymorphic_ctype_id` value defined.\n"
|
||||||
"If you created models outside polymorphic, e.g. through an import or migration, "
|
"If you created models outside polymorphic, e.g. through an import or migration, "
|
||||||
"make sure the `polymorphic_ctype_id` field points to the ContentType ID of the model subclass."
|
"make sure the `polymorphic_ctype_id` field points to the ContentType ID of the model subclass."
|
||||||
).format(self.__class__.__name__, self.pk))
|
).format(self.__class__.__name__, self.pk)
|
||||||
|
)
|
||||||
|
|
||||||
# the following line would be the easiest way to do this, but it produces sql queries
|
# the following line would be the easiest way to do this, but it produces sql queries
|
||||||
# return self.polymorphic_ctype.model_class()
|
# return self.polymorphic_ctype.model_class()
|
||||||
# so we use the following version, which uses the ContentType manager cache.
|
# so we use the following version, which uses the ContentType manager cache.
|
||||||
# Note that model_class() can return None for stale content types;
|
# Note that model_class() can return None for stale content types;
|
||||||
# when the content type record still exists but no longer refers to an existing model.
|
# when the content type record still exists but no longer refers to an existing model.
|
||||||
model = ContentType.objects.db_manager(self._state.db).get_for_id(self.polymorphic_ctype_id).model_class()
|
model = (
|
||||||
|
ContentType.objects.db_manager(self._state.db)
|
||||||
|
.get_for_id(self.polymorphic_ctype_id)
|
||||||
|
.model_class()
|
||||||
|
)
|
||||||
|
|
||||||
# Protect against bad imports (dumpdata without --natural) or other
|
# Protect against bad imports (dumpdata without --natural) or other
|
||||||
# issues missing with the ContentType models.
|
# issues missing with the ContentType models.
|
||||||
if model is not None \
|
if (
|
||||||
and not issubclass(model, self.__class__) \
|
model is not None
|
||||||
and (self.__class__._meta.proxy_for_model is None \
|
and not issubclass(model, self.__class__)
|
||||||
or not issubclass(model, self.__class__._meta.proxy_for_model)):
|
and (
|
||||||
raise PolymorphicTypeInvalid("ContentType {0} for {1} #{2} does not point to a subclass!".format(
|
self.__class__._meta.proxy_for_model is None
|
||||||
self.polymorphic_ctype_id, model, self.pk,
|
or not issubclass(model, self.__class__._meta.proxy_for_model)
|
||||||
))
|
)
|
||||||
|
):
|
||||||
|
raise PolymorphicTypeInvalid(
|
||||||
|
"ContentType {0} for {1} #{2} does not point to a subclass!".format(
|
||||||
|
self.polymorphic_ctype_id, model, self.pk
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
return model
|
return model
|
||||||
|
|
||||||
|
|
@ -121,13 +144,21 @@ class PolymorphicModel(with_metaclass(PolymorphicModelBase, models.Model)):
|
||||||
model_class = self.get_real_instance_class()
|
model_class = self.get_real_instance_class()
|
||||||
if model_class is None:
|
if model_class is None:
|
||||||
return None
|
return None
|
||||||
return ContentType.objects.db_manager(self._state.db).get_for_model(model_class, for_concrete_model=True).pk
|
return (
|
||||||
|
ContentType.objects.db_manager(self._state.db)
|
||||||
|
.get_for_model(model_class, for_concrete_model=True)
|
||||||
|
.pk
|
||||||
|
)
|
||||||
|
|
||||||
def get_real_concrete_instance_class(self):
|
def get_real_concrete_instance_class(self):
|
||||||
model_class = self.get_real_instance_class()
|
model_class = self.get_real_instance_class()
|
||||||
if model_class is None:
|
if model_class is None:
|
||||||
return None
|
return None
|
||||||
return ContentType.objects.db_manager(self._state.db).get_for_model(model_class, for_concrete_model=True).model_class()
|
return (
|
||||||
|
ContentType.objects.db_manager(self._state.db)
|
||||||
|
.get_for_model(model_class, for_concrete_model=True)
|
||||||
|
.model_class()
|
||||||
|
)
|
||||||
|
|
||||||
def get_real_instance(self):
|
def get_real_instance(self):
|
||||||
"""
|
"""
|
||||||
|
|
@ -176,15 +207,25 @@ class PolymorphicModel(with_metaclass(PolymorphicModelBase, models.Model)):
|
||||||
def accessor_function(self):
|
def accessor_function(self):
|
||||||
attr = model._base_objects.get(pk=self.pk)
|
attr = model._base_objects.get(pk=self.pk)
|
||||||
return attr
|
return attr
|
||||||
|
|
||||||
return accessor_function
|
return accessor_function
|
||||||
|
|
||||||
subclasses_and_superclasses_accessors = self._get_inheritance_relation_fields_and_models()
|
subclasses_and_superclasses_accessors = (
|
||||||
|
self._get_inheritance_relation_fields_and_models()
|
||||||
|
)
|
||||||
|
|
||||||
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)
|
||||||
if issubclass(type(orig_accessor), (ReverseOneToOneDescriptor, ForwardManyToOneDescriptor)):
|
if issubclass(
|
||||||
setattr(self.__class__, name, property(create_accessor_function_for_model(model, name)))
|
type(orig_accessor),
|
||||||
|
(ReverseOneToOneDescriptor, ForwardManyToOneDescriptor),
|
||||||
|
):
|
||||||
|
setattr(
|
||||||
|
self.__class__,
|
||||||
|
name,
|
||||||
|
property(create_accessor_function_for_model(model, name)),
|
||||||
|
)
|
||||||
|
|
||||||
def _get_inheritance_relation_fields_and_models(self):
|
def _get_inheritance_relation_fields_and_models(self):
|
||||||
"""helper function for __init__:
|
"""helper function for __init__:
|
||||||
|
|
@ -194,31 +235,45 @@ class PolymorphicModel(with_metaclass(PolymorphicModelBase, models.Model)):
|
||||||
result[field_name] = model
|
result[field_name] = model
|
||||||
|
|
||||||
def add_model_if_regular(model, field_name, result):
|
def add_model_if_regular(model, field_name, result):
|
||||||
if (issubclass(model, models.Model)
|
if (
|
||||||
|
issubclass(model, models.Model)
|
||||||
and model != models.Model
|
and model != models.Model
|
||||||
and model != self.__class__
|
and model != self.__class__
|
||||||
and model != PolymorphicModel):
|
and model != PolymorphicModel
|
||||||
|
):
|
||||||
add_model(model, field_name, result)
|
add_model(model, field_name, result)
|
||||||
|
|
||||||
def add_all_super_models(model, result):
|
def add_all_super_models(model, result):
|
||||||
for super_cls, field_to_super in model._meta.parents.items():
|
for super_cls, field_to_super in model._meta.parents.items():
|
||||||
if field_to_super is not None: # if not a link to a proxy model
|
if field_to_super is not None: # if not a link to a proxy model
|
||||||
field_name = field_to_super.name # the field on model can have a different name to super_cls._meta.module_name, if the field is created manually using 'parent_link'
|
field_name = (
|
||||||
|
field_to_super.name
|
||||||
|
) # the field on model can have a different name to super_cls._meta.module_name, if the field is created manually using 'parent_link'
|
||||||
add_model_if_regular(super_cls, field_name, result)
|
add_model_if_regular(super_cls, field_name, result)
|
||||||
add_all_super_models(super_cls, result)
|
add_all_super_models(super_cls, result)
|
||||||
|
|
||||||
def add_all_sub_models(super_cls, result):
|
def add_all_sub_models(super_cls, result):
|
||||||
for sub_cls in super_cls.__subclasses__(): # go through all subclasses of model
|
for (
|
||||||
if super_cls in sub_cls._meta.parents: # super_cls may not be in sub_cls._meta.parents if super_cls is a proxy model
|
sub_cls
|
||||||
field_to_super = sub_cls._meta.parents[super_cls] # get the field that links sub_cls to super_cls
|
) in super_cls.__subclasses__(): # go through all subclasses of model
|
||||||
if field_to_super is not None: # if filed_to_super is not a link to a proxy model
|
if (
|
||||||
|
super_cls in sub_cls._meta.parents
|
||||||
|
): # super_cls may not be in sub_cls._meta.parents if super_cls is a proxy model
|
||||||
|
field_to_super = sub_cls._meta.parents[
|
||||||
|
super_cls
|
||||||
|
] # get the field that links sub_cls to super_cls
|
||||||
|
if (
|
||||||
|
field_to_super is not None
|
||||||
|
): # if filed_to_super is not a link to a proxy model
|
||||||
super_to_sub_related_field = field_to_super.remote_field
|
super_to_sub_related_field = field_to_super.remote_field
|
||||||
if super_to_sub_related_field.related_name is None:
|
if super_to_sub_related_field.related_name is None:
|
||||||
# if related name is None the related field is the name of the subclass
|
# if related name is None the related field is the name of the subclass
|
||||||
to_subclass_fieldname = sub_cls.__name__.lower()
|
to_subclass_fieldname = sub_cls.__name__.lower()
|
||||||
else:
|
else:
|
||||||
# otherwise use the given related name
|
# otherwise use the given related name
|
||||||
to_subclass_fieldname = super_to_sub_related_field.related_name
|
to_subclass_fieldname = (
|
||||||
|
super_to_sub_related_field.related_name
|
||||||
|
)
|
||||||
|
|
||||||
add_model_if_regular(sub_cls, to_subclass_fieldname, result)
|
add_model_if_regular(sub_cls, to_subclass_fieldname, result)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,8 +12,12 @@ from django.db.models import FieldDoesNotExist
|
||||||
from django.db.models.query import ModelIterable, Q, QuerySet
|
from django.db.models.query import ModelIterable, Q, QuerySet
|
||||||
|
|
||||||
from . import compat
|
from . import compat
|
||||||
from .query_translate import translate_polymorphic_filter_definitions_in_kwargs, translate_polymorphic_filter_definitions_in_args
|
from .query_translate import (
|
||||||
from .query_translate import translate_polymorphic_field_path, translate_polymorphic_Q_object
|
translate_polymorphic_field_path,
|
||||||
|
translate_polymorphic_filter_definitions_in_args,
|
||||||
|
translate_polymorphic_filter_definitions_in_kwargs,
|
||||||
|
translate_polymorphic_Q_object,
|
||||||
|
)
|
||||||
|
|
||||||
# chunk-size: maximum number of objects requested per db-request
|
# chunk-size: maximum number of objects requested per db-request
|
||||||
# by the polymorphic queryset.iterator() implementation
|
# by the polymorphic queryset.iterator() implementation
|
||||||
|
|
@ -72,7 +76,7 @@ def transmogrify(cls, obj):
|
||||||
"""
|
"""
|
||||||
Upcast a class to a different type without asking questions.
|
Upcast a class to a different type without asking questions.
|
||||||
"""
|
"""
|
||||||
if '__init__' not in obj.__dict__:
|
if "__init__" not in obj.__dict__:
|
||||||
# Just assign __class__ to a different value.
|
# Just assign __class__ to a different value.
|
||||||
new = obj
|
new = obj
|
||||||
new.__class__ = cls
|
new.__class__ = cls
|
||||||
|
|
@ -87,6 +91,7 @@ def transmogrify(cls, obj):
|
||||||
###################################################################################
|
###################################################################################
|
||||||
# PolymorphicQuerySet
|
# PolymorphicQuerySet
|
||||||
|
|
||||||
|
|
||||||
class PolymorphicQuerySet(QuerySet):
|
class PolymorphicQuerySet(QuerySet):
|
||||||
"""
|
"""
|
||||||
QuerySet for PolymorphicModel
|
QuerySet for PolymorphicModel
|
||||||
|
|
@ -115,14 +120,17 @@ class PolymorphicQuerySet(QuerySet):
|
||||||
new.polymorphic_disabled = self.polymorphic_disabled
|
new.polymorphic_disabled = self.polymorphic_disabled
|
||||||
new.polymorphic_deferred_loading = (
|
new.polymorphic_deferred_loading = (
|
||||||
copy.copy(self.polymorphic_deferred_loading[0]),
|
copy.copy(self.polymorphic_deferred_loading[0]),
|
||||||
self.polymorphic_deferred_loading[1])
|
self.polymorphic_deferred_loading[1],
|
||||||
|
)
|
||||||
return new
|
return new
|
||||||
|
|
||||||
def as_manager(cls):
|
def as_manager(cls):
|
||||||
from .managers import PolymorphicManager
|
from .managers import PolymorphicManager
|
||||||
|
|
||||||
manager = PolymorphicManager.from_queryset(cls)()
|
manager = PolymorphicManager.from_queryset(cls)()
|
||||||
manager._built_with_as_manager = True
|
manager._built_with_as_manager = True
|
||||||
return manager
|
return manager
|
||||||
|
|
||||||
as_manager.queryset_only = True
|
as_manager.queryset_only = True
|
||||||
as_manager = classmethod(as_manager)
|
as_manager = classmethod(as_manager)
|
||||||
|
|
||||||
|
|
@ -154,15 +162,22 @@ class PolymorphicQuerySet(QuerySet):
|
||||||
|
|
||||||
def _filter_or_exclude(self, negate, *args, **kwargs):
|
def _filter_or_exclude(self, negate, *args, **kwargs):
|
||||||
# We override this internal Django functon as it is used for all filter member functions.
|
# We override this internal Django functon as it is used for all filter member functions.
|
||||||
q_objects = translate_polymorphic_filter_definitions_in_args(self.model, args, using=self.db) # the Q objects
|
q_objects = translate_polymorphic_filter_definitions_in_args(
|
||||||
additional_args = translate_polymorphic_filter_definitions_in_kwargs(self.model, kwargs, using=self.db) # filter_field='data'
|
self.model, args, using=self.db
|
||||||
return super(PolymorphicQuerySet, self)._filter_or_exclude(negate, *(list(q_objects) + additional_args), **kwargs)
|
) # the Q objects
|
||||||
|
additional_args = translate_polymorphic_filter_definitions_in_kwargs(
|
||||||
|
self.model, kwargs, using=self.db
|
||||||
|
) # filter_field='data'
|
||||||
|
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) else a # allow expressions to pass unchanged
|
if isinstance(a, compat.string_types)
|
||||||
|
else a # allow expressions to pass unchanged
|
||||||
for a in field_names
|
for a in field_names
|
||||||
]
|
]
|
||||||
return super(PolymorphicQuerySet, self).order_by(*field_names)
|
return super(PolymorphicQuerySet, self).order_by(*field_names)
|
||||||
|
|
@ -213,8 +228,8 @@ class PolymorphicQuerySet(QuerySet):
|
||||||
"""
|
"""
|
||||||
existing, defer = self.polymorphic_deferred_loading
|
existing, defer = self.polymorphic_deferred_loading
|
||||||
field_names = set(field_names)
|
field_names = set(field_names)
|
||||||
if 'pk' in field_names:
|
if "pk" in field_names:
|
||||||
field_names.remove('pk')
|
field_names.remove("pk")
|
||||||
field_names.add(self.model._meta.pk.name)
|
field_names.add(self.model._meta.pk.name)
|
||||||
|
|
||||||
if defer:
|
if defer:
|
||||||
|
|
@ -228,14 +243,14 @@ class PolymorphicQuerySet(QuerySet):
|
||||||
def _process_aggregate_args(self, args, kwargs):
|
def _process_aggregate_args(self, args, kwargs):
|
||||||
"""for aggregate and annotate kwargs: allow ModelX___field syntax for kwargs, forbid it for args.
|
"""for aggregate and annotate kwargs: allow ModelX___field syntax for kwargs, forbid it for args.
|
||||||
Modifies kwargs if needed (these are Aggregate objects, we translate the lookup member variable)"""
|
Modifies kwargs if needed (these are Aggregate objects, we translate the lookup member variable)"""
|
||||||
___lookup_assert_msg = 'PolymorphicModel: annotate()/aggregate(): ___ model lookup supported for keyword arguments only'
|
___lookup_assert_msg = "PolymorphicModel: annotate()/aggregate(): ___ model lookup supported for keyword arguments only"
|
||||||
|
|
||||||
def patch_lookup(a):
|
def patch_lookup(a):
|
||||||
# The field on which the aggregate operates is
|
# The field on which the aggregate operates is
|
||||||
# stored inside a complex query expression.
|
# stored inside a complex query expression.
|
||||||
if isinstance(a, Q):
|
if isinstance(a, Q):
|
||||||
translate_polymorphic_Q_object(self.model, a)
|
translate_polymorphic_Q_object(self.model, a)
|
||||||
elif hasattr(a, 'get_source_expressions'):
|
elif hasattr(a, "get_source_expressions"):
|
||||||
for source_expression in a.get_source_expressions():
|
for source_expression in a.get_source_expressions():
|
||||||
if source_expression is not None:
|
if source_expression is not None:
|
||||||
patch_lookup(source_expression)
|
patch_lookup(source_expression)
|
||||||
|
|
@ -246,6 +261,7 @@ class PolymorphicQuerySet(QuerySet):
|
||||||
""" *args might be complex expressions too in django 1.8 so
|
""" *args might be complex expressions too in django 1.8 so
|
||||||
the testing for a '___' is rather complex on this one """
|
the testing for a '___' is rather complex on this one """
|
||||||
if isinstance(a, Q):
|
if isinstance(a, Q):
|
||||||
|
|
||||||
def tree_node_test___lookup(my_model, node):
|
def tree_node_test___lookup(my_model, node):
|
||||||
" process all children of this Q node "
|
" process all children of this Q node "
|
||||||
for i in range(len(node.children)):
|
for i in range(len(node.children)):
|
||||||
|
|
@ -253,17 +269,17 @@ class PolymorphicQuerySet(QuerySet):
|
||||||
|
|
||||||
if type(child) == tuple:
|
if type(child) == tuple:
|
||||||
# this Q object child is a tuple => a kwarg like Q( instance_of=ModelB )
|
# this Q object child is a tuple => a kwarg like Q( instance_of=ModelB )
|
||||||
assert '___' not in child[0], ___lookup_assert_msg
|
assert "___" not in child[0], ___lookup_assert_msg
|
||||||
else:
|
else:
|
||||||
# this Q object child is another Q object, recursively process this as well
|
# this Q object child is another Q object, recursively process this as well
|
||||||
tree_node_test___lookup(my_model, child)
|
tree_node_test___lookup(my_model, child)
|
||||||
|
|
||||||
tree_node_test___lookup(self.model, a)
|
tree_node_test___lookup(self.model, a)
|
||||||
elif hasattr(a, 'get_source_expressions'):
|
elif hasattr(a, "get_source_expressions"):
|
||||||
for source_expression in a.get_source_expressions():
|
for source_expression in a.get_source_expressions():
|
||||||
test___lookup(source_expression)
|
test___lookup(source_expression)
|
||||||
else:
|
else:
|
||||||
assert '___' not in a.name, ___lookup_assert_msg
|
assert "___" not in a.name, ___lookup_assert_msg
|
||||||
|
|
||||||
for a in args:
|
for a in args:
|
||||||
test___lookup(a)
|
test___lookup(a)
|
||||||
|
|
@ -344,8 +360,12 @@ class PolymorphicQuerySet(QuerySet):
|
||||||
# - sort base_result_object ids into idlist_per_model lists, depending on their real class;
|
# - sort base_result_object ids into idlist_per_model lists, depending on their real class;
|
||||||
# - store objects that already have the correct class into "results"
|
# - store objects that already have the correct class into "results"
|
||||||
content_type_manager = ContentType.objects.db_manager(self.db)
|
content_type_manager = ContentType.objects.db_manager(self.db)
|
||||||
self_model_class_id = content_type_manager.get_for_model(self.model, for_concrete_model=False).pk
|
self_model_class_id = content_type_manager.get_for_model(
|
||||||
self_concrete_model_class_id = content_type_manager.get_for_model(self.model, for_concrete_model=True).pk
|
self.model, for_concrete_model=False
|
||||||
|
).pk
|
||||||
|
self_concrete_model_class_id = content_type_manager.get_for_model(
|
||||||
|
self.model, for_concrete_model=True
|
||||||
|
).pk
|
||||||
|
|
||||||
for i, base_object in enumerate(base_result_objects):
|
for i, base_object in enumerate(base_result_objects):
|
||||||
|
|
||||||
|
|
@ -354,7 +374,9 @@ class PolymorphicQuerySet(QuerySet):
|
||||||
resultlist.append(base_object)
|
resultlist.append(base_object)
|
||||||
else:
|
else:
|
||||||
real_concrete_class = base_object.get_real_instance_class()
|
real_concrete_class = base_object.get_real_instance_class()
|
||||||
real_concrete_class_id = base_object.get_real_concrete_instance_class_id()
|
real_concrete_class_id = (
|
||||||
|
base_object.get_real_concrete_instance_class_id()
|
||||||
|
)
|
||||||
|
|
||||||
if real_concrete_class_id is None:
|
if real_concrete_class_id is None:
|
||||||
# Dealing with a stale content type
|
# Dealing with a stale content type
|
||||||
|
|
@ -365,9 +387,15 @@ class PolymorphicQuerySet(QuerySet):
|
||||||
resultlist.append(transmogrify(real_concrete_class, base_object))
|
resultlist.append(transmogrify(real_concrete_class, base_object))
|
||||||
else:
|
else:
|
||||||
# This model has a concrete derived class, track it for bulk retrieval.
|
# This model has a concrete derived class, track it for bulk retrieval.
|
||||||
real_concrete_class = content_type_manager.get_for_id(real_concrete_class_id).model_class()
|
real_concrete_class = content_type_manager.get_for_id(
|
||||||
idlist_per_model[real_concrete_class].append(getattr(base_object, pk_name))
|
real_concrete_class_id
|
||||||
indexlist_per_model[real_concrete_class].append((i, len(resultlist)))
|
).model_class()
|
||||||
|
idlist_per_model[real_concrete_class].append(
|
||||||
|
getattr(base_object, pk_name)
|
||||||
|
)
|
||||||
|
indexlist_per_model[real_concrete_class].append(
|
||||||
|
(i, len(resultlist))
|
||||||
|
)
|
||||||
resultlist.append(None)
|
resultlist.append(None)
|
||||||
|
|
||||||
# For each model in "idlist_per_model" request its objects (the real model)
|
# For each model in "idlist_per_model" request its objects (the real model)
|
||||||
|
|
@ -377,10 +405,12 @@ class PolymorphicQuerySet(QuerySet):
|
||||||
# TODO: defer(), only(): support for these would be around here
|
# TODO: defer(), only(): support for these would be around here
|
||||||
for real_concrete_class, idlist in idlist_per_model.items():
|
for real_concrete_class, idlist in idlist_per_model.items():
|
||||||
indices = indexlist_per_model[real_concrete_class]
|
indices = indexlist_per_model[real_concrete_class]
|
||||||
real_objects = real_concrete_class._base_objects.db_manager(self.db).filter(**{
|
real_objects = real_concrete_class._base_objects.db_manager(self.db).filter(
|
||||||
('%s__in' % pk_name): idlist,
|
**{("%s__in" % pk_name): idlist}
|
||||||
})
|
)
|
||||||
real_objects.query.select_related = self.query.select_related # copy select related configuration to new qs
|
real_objects.query.select_related = (
|
||||||
|
self.query.select_related
|
||||||
|
) # copy select related configuration to new qs
|
||||||
|
|
||||||
# Copy deferred fields configuration to the new queryset
|
# Copy deferred fields configuration to the new queryset
|
||||||
deferred_loading_fields = []
|
deferred_loading_fields = []
|
||||||
|
|
@ -388,14 +418,15 @@ class PolymorphicQuerySet(QuerySet):
|
||||||
for field in existing_fields:
|
for field in existing_fields:
|
||||||
try:
|
try:
|
||||||
translated_field_name = translate_polymorphic_field_path(
|
translated_field_name = translate_polymorphic_field_path(
|
||||||
real_concrete_class, field)
|
real_concrete_class, field
|
||||||
|
)
|
||||||
except AssertionError:
|
except AssertionError:
|
||||||
if '___' in field:
|
if "___" in field:
|
||||||
# The originally passed argument to .defer() or .only()
|
# The originally passed argument to .defer() or .only()
|
||||||
# was in the form Model2B___field2, where Model2B is
|
# was in the form Model2B___field2, where Model2B is
|
||||||
# now a superclass of real_concrete_class. Thus it's
|
# now a superclass of real_concrete_class. Thus it's
|
||||||
# sufficient to just use the field name.
|
# sufficient to just use the field name.
|
||||||
translated_field_name = field.rpartition('___')[-1]
|
translated_field_name = field.rpartition("___")[-1]
|
||||||
|
|
||||||
# Check if the field does exist.
|
# Check if the field does exist.
|
||||||
# Ignore deferred fields that don't exist in this subclass type.
|
# Ignore deferred fields that don't exist in this subclass type.
|
||||||
|
|
@ -407,7 +438,10 @@ class PolymorphicQuerySet(QuerySet):
|
||||||
raise
|
raise
|
||||||
|
|
||||||
deferred_loading_fields.append(translated_field_name)
|
deferred_loading_fields.append(translated_field_name)
|
||||||
real_objects.query.deferred_loading = (set(deferred_loading_fields), self.query.deferred_loading[1])
|
real_objects.query.deferred_loading = (
|
||||||
|
set(deferred_loading_fields),
|
||||||
|
self.query.deferred_loading[1],
|
||||||
|
)
|
||||||
|
|
||||||
real_objects_dict = {
|
real_objects_dict = {
|
||||||
getattr(real_object, pk_name): real_object
|
getattr(real_object, pk_name): real_object
|
||||||
|
|
@ -445,13 +479,17 @@ class PolymorphicQuerySet(QuerySet):
|
||||||
|
|
||||||
# set polymorphic_annotate_names in all objects (currently just used for debugging/printing)
|
# set polymorphic_annotate_names in all objects (currently just used for debugging/printing)
|
||||||
if self.query.annotations:
|
if self.query.annotations:
|
||||||
annotate_names = list(self.query.annotations.keys()) # get annotate field list
|
annotate_names = list(
|
||||||
|
self.query.annotations.keys()
|
||||||
|
) # get annotate field list
|
||||||
for real_object in resultlist:
|
for real_object in resultlist:
|
||||||
real_object.polymorphic_annotate_names = annotate_names
|
real_object.polymorphic_annotate_names = annotate_names
|
||||||
|
|
||||||
# set polymorphic_extra_select_names in all objects (currently just used for debugging/printing)
|
# set polymorphic_extra_select_names in all objects (currently just used for debugging/printing)
|
||||||
if self.query.extra_select:
|
if self.query.extra_select:
|
||||||
extra_select_names = list(self.query.extra_select.keys()) # get extra select field list
|
extra_select_names = list(
|
||||||
|
self.query.extra_select.keys()
|
||||||
|
) # get extra select field list
|
||||||
for real_object in resultlist:
|
for real_object in resultlist:
|
||||||
real_object.polymorphic_extra_select_names = extra_select_names
|
real_object.polymorphic_extra_select_names = extra_select_names
|
||||||
|
|
||||||
|
|
@ -460,15 +498,14 @@ class PolymorphicQuerySet(QuerySet):
|
||||||
def __repr__(self, *args, **kwargs):
|
def __repr__(self, *args, **kwargs):
|
||||||
if self.model.polymorphic_query_multiline_output:
|
if self.model.polymorphic_query_multiline_output:
|
||||||
result = [repr(o) for o in self.all()]
|
result = [repr(o) for o in self.all()]
|
||||||
return '[ ' + ',\n '.join(result) + ' ]'
|
return "[ " + ",\n ".join(result) + " ]"
|
||||||
else:
|
else:
|
||||||
return super(PolymorphicQuerySet, self).__repr__(*args, **kwargs)
|
return super(PolymorphicQuerySet, self).__repr__(*args, **kwargs)
|
||||||
|
|
||||||
class _p_list_class(list):
|
class _p_list_class(list):
|
||||||
|
|
||||||
def __repr__(self, *args, **kwargs):
|
def __repr__(self, *args, **kwargs):
|
||||||
result = [repr(o) for o in self]
|
result = [repr(o) for o in self]
|
||||||
return '[ ' + ',\n '.join(result) + ' ]'
|
return "[ " + ",\n ".join(result) + " ]"
|
||||||
|
|
||||||
def get_real_instances(self, base_result_objects=None):
|
def get_real_instances(self, base_result_objects=None):
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
|
|
@ -15,18 +15,19 @@ from django.db.models import Q
|
||||||
from django.db.models.fields.related import ForeignObjectRel, RelatedField
|
from django.db.models.fields.related import ForeignObjectRel, RelatedField
|
||||||
from django.db.utils import DEFAULT_DB_ALIAS
|
from django.db.utils import DEFAULT_DB_ALIAS
|
||||||
|
|
||||||
|
|
||||||
###################################################################################
|
|
||||||
# PolymorphicQuerySet support functions
|
|
||||||
|
|
||||||
# These functions implement the additional filter- and Q-object functionality.
|
# These functions implement the additional filter- and Q-object functionality.
|
||||||
# They form a kind of small framework for easily adding more
|
# They form a kind of small framework for easily adding more
|
||||||
# functionality to filters and Q objects.
|
# functionality to filters and Q objects.
|
||||||
# Probably a more general queryset enhancement class could be made out of them.
|
# Probably a more general queryset enhancement class could be made out of them.
|
||||||
from polymorphic import compat
|
from polymorphic import compat
|
||||||
|
|
||||||
|
###################################################################################
|
||||||
|
# PolymorphicQuerySet support functions
|
||||||
|
|
||||||
def translate_polymorphic_filter_definitions_in_kwargs(queryset_model, kwargs, using=DEFAULT_DB_ALIAS):
|
|
||||||
|
def translate_polymorphic_filter_definitions_in_kwargs(
|
||||||
|
queryset_model, kwargs, using=DEFAULT_DB_ALIAS
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
Translate the keyword argument list for PolymorphicQuerySet.filter()
|
Translate the keyword argument list for PolymorphicQuerySet.filter()
|
||||||
|
|
||||||
|
|
@ -43,21 +44,25 @@ def translate_polymorphic_filter_definitions_in_kwargs(queryset_model, kwargs, u
|
||||||
additional_args = []
|
additional_args = []
|
||||||
for field_path, val in kwargs.copy().items(): # Python 3 needs copy
|
for field_path, val in kwargs.copy().items(): # Python 3 needs copy
|
||||||
|
|
||||||
new_expr = _translate_polymorphic_filter_definition(queryset_model, field_path, val, using=using)
|
new_expr = _translate_polymorphic_filter_definition(
|
||||||
|
queryset_model, field_path, val, using=using
|
||||||
|
)
|
||||||
|
|
||||||
if type(new_expr) == tuple:
|
if type(new_expr) == tuple:
|
||||||
# replace kwargs element
|
# replace kwargs element
|
||||||
del(kwargs[field_path])
|
del kwargs[field_path]
|
||||||
kwargs[new_expr[0]] = new_expr[1]
|
kwargs[new_expr[0]] = new_expr[1]
|
||||||
|
|
||||||
elif isinstance(new_expr, models.Q):
|
elif isinstance(new_expr, models.Q):
|
||||||
del(kwargs[field_path])
|
del kwargs[field_path]
|
||||||
additional_args.append(new_expr)
|
additional_args.append(new_expr)
|
||||||
|
|
||||||
return additional_args
|
return additional_args
|
||||||
|
|
||||||
|
|
||||||
def translate_polymorphic_Q_object(queryset_model, potential_q_object, using=DEFAULT_DB_ALIAS):
|
def translate_polymorphic_Q_object(
|
||||||
|
queryset_model, potential_q_object, using=DEFAULT_DB_ALIAS
|
||||||
|
):
|
||||||
def tree_node_correct_field_specs(my_model, node):
|
def tree_node_correct_field_specs(my_model, node):
|
||||||
" process all children of this Q node "
|
" process all children of this Q node "
|
||||||
for i in range(len(node.children)):
|
for i in range(len(node.children)):
|
||||||
|
|
@ -66,7 +71,9 @@ def translate_polymorphic_Q_object(queryset_model, potential_q_object, using=DEF
|
||||||
if type(child) == tuple:
|
if type(child) == tuple:
|
||||||
# this Q object child is a tuple => a kwarg like Q( instance_of=ModelB )
|
# this Q object child is a tuple => a kwarg like Q( instance_of=ModelB )
|
||||||
key, val = child
|
key, val = child
|
||||||
new_expr = _translate_polymorphic_filter_definition(my_model, key, val, using=using)
|
new_expr = _translate_polymorphic_filter_definition(
|
||||||
|
my_model, key, val, using=using
|
||||||
|
)
|
||||||
if new_expr:
|
if new_expr:
|
||||||
node.children[i] = new_expr
|
node.children[i] = new_expr
|
||||||
else:
|
else:
|
||||||
|
|
@ -79,7 +86,9 @@ def translate_polymorphic_Q_object(queryset_model, potential_q_object, using=DEF
|
||||||
return potential_q_object
|
return potential_q_object
|
||||||
|
|
||||||
|
|
||||||
def translate_polymorphic_filter_definitions_in_args(queryset_model, args, using=DEFAULT_DB_ALIAS):
|
def translate_polymorphic_filter_definitions_in_args(
|
||||||
|
queryset_model, args, using=DEFAULT_DB_ALIAS
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
Translate the non-keyword argument list for PolymorphicQuerySet.filter()
|
Translate the non-keyword argument list for PolymorphicQuerySet.filter()
|
||||||
|
|
||||||
|
|
@ -90,10 +99,15 @@ def translate_polymorphic_filter_definitions_in_args(queryset_model, args, using
|
||||||
|
|
||||||
Returns: modified Q objects
|
Returns: modified Q objects
|
||||||
"""
|
"""
|
||||||
return [translate_polymorphic_Q_object(queryset_model, copy.deepcopy(q), using=using) for q in args]
|
return [
|
||||||
|
translate_polymorphic_Q_object(queryset_model, copy.deepcopy(q), using=using)
|
||||||
|
for q in args
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
def _translate_polymorphic_filter_definition(queryset_model, field_path, field_val, using=DEFAULT_DB_ALIAS):
|
def _translate_polymorphic_filter_definition(
|
||||||
|
queryset_model, field_path, field_val, using=DEFAULT_DB_ALIAS
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
Translate a keyword argument (field_path=field_val), as used for
|
Translate a keyword argument (field_path=field_val), as used for
|
||||||
PolymorphicQuerySet.filter()-like functions (and Q objects).
|
PolymorphicQuerySet.filter()-like functions (and Q objects).
|
||||||
|
|
@ -107,11 +121,11 @@ def _translate_polymorphic_filter_definition(queryset_model, field_path, field_v
|
||||||
|
|
||||||
# handle instance_of expressions or alternatively,
|
# handle instance_of expressions or alternatively,
|
||||||
# if this is a normal Django filter expression, return None
|
# if this is a normal Django filter expression, return None
|
||||||
if field_path == 'instance_of':
|
if field_path == "instance_of":
|
||||||
return create_instanceof_q(field_val, using=using)
|
return create_instanceof_q(field_val, using=using)
|
||||||
elif field_path == 'not_instance_of':
|
elif field_path == "not_instance_of":
|
||||||
return create_instanceof_q(field_val, not_instance_of=True, using=using)
|
return create_instanceof_q(field_val, not_instance_of=True, using=using)
|
||||||
elif '___' not in field_path:
|
elif "___" not in field_path:
|
||||||
return None # no change
|
return None # no change
|
||||||
|
|
||||||
# filter expression contains '___' (i.e. filter for polymorphic field)
|
# filter expression contains '___' (i.e. filter for polymorphic field)
|
||||||
|
|
@ -133,23 +147,32 @@ def translate_polymorphic_field_path(queryset_model, field_path):
|
||||||
if not isinstance(field_path, compat.string_types):
|
if not isinstance(field_path, compat.string_types):
|
||||||
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("___")
|
||||||
if not sep:
|
if not sep:
|
||||||
return field_path
|
return field_path
|
||||||
assert classname, 'PolymorphicModel: %s: bad field specification' % field_path
|
assert classname, "PolymorphicModel: %s: bad field specification" % field_path
|
||||||
|
|
||||||
negated = False
|
negated = False
|
||||||
if classname[0] == '-':
|
if classname[0] == "-":
|
||||||
negated = True
|
negated = True
|
||||||
classname = classname.lstrip('-')
|
classname = classname.lstrip("-")
|
||||||
|
|
||||||
if '__' in classname:
|
if "__" in classname:
|
||||||
# the user has app label prepended to class name via __ => use Django's get_model function
|
# the user has app label prepended to class name via __ => use Django's get_model function
|
||||||
appname, sep, classname = classname.partition('__')
|
appname, sep, classname = classname.partition("__")
|
||||||
model = apps.get_model(appname, classname)
|
model = apps.get_model(appname, classname)
|
||||||
assert model, 'PolymorphicModel: model %s (in app %s) not found!' % (model.__name__, appname)
|
assert model, "PolymorphicModel: model %s (in app %s) not found!" % (
|
||||||
|
model.__name__,
|
||||||
|
appname,
|
||||||
|
)
|
||||||
if not issubclass(model, queryset_model):
|
if not issubclass(model, queryset_model):
|
||||||
e = 'PolymorphicModel: queryset filter error: "' + model.__name__ + '" is not derived from "' + queryset_model.__name__ + '"'
|
e = (
|
||||||
|
'PolymorphicModel: queryset filter error: "'
|
||||||
|
+ model.__name__
|
||||||
|
+ '" is not derived from "'
|
||||||
|
+ queryset_model.__name__
|
||||||
|
+ '"'
|
||||||
|
)
|
||||||
raise AssertionError(e)
|
raise AssertionError(e)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
|
|
@ -171,18 +194,21 @@ def translate_polymorphic_field_path(queryset_model, field_path):
|
||||||
|
|
||||||
submodels = _get_all_sub_models(queryset_model)
|
submodels = _get_all_sub_models(queryset_model)
|
||||||
model = submodels.get(classname, None)
|
model = submodels.get(classname, None)
|
||||||
assert model, 'PolymorphicModel: model %s not found (not a subclass of %s)!' % (classname, queryset_model.__name__)
|
assert model, "PolymorphicModel: model %s not found (not a subclass of %s)!" % (
|
||||||
|
classname,
|
||||||
|
queryset_model.__name__,
|
||||||
|
)
|
||||||
|
|
||||||
basepath = _create_base_path(queryset_model, model)
|
basepath = _create_base_path(queryset_model, model)
|
||||||
|
|
||||||
if negated:
|
if negated:
|
||||||
newpath = '-'
|
newpath = "-"
|
||||||
else:
|
else:
|
||||||
newpath = ''
|
newpath = ""
|
||||||
|
|
||||||
newpath += basepath
|
newpath += basepath
|
||||||
if basepath:
|
if basepath:
|
||||||
newpath += '__'
|
newpath += "__"
|
||||||
|
|
||||||
newpath += pure_field_path
|
newpath += pure_field_path
|
||||||
return newpath
|
return newpath
|
||||||
|
|
@ -199,11 +225,13 @@ def _get_all_sub_models(base_model):
|
||||||
# model name is occurring twice in submodel inheritance tree => Error
|
# model name is occurring twice in submodel inheritance tree => Error
|
||||||
if model.__name__ in result and model != result[model.__name__]:
|
if model.__name__ in result and model != result[model.__name__]:
|
||||||
raise FieldError(
|
raise FieldError(
|
||||||
'PolymorphicModel: model name alone is ambiguous: %s.%s and %s.%s match!\n'
|
"PolymorphicModel: model name alone is ambiguous: %s.%s and %s.%s match!\n"
|
||||||
'In this case, please use the syntax: applabel__ModelName___field' % (
|
"In this case, please use the syntax: applabel__ModelName___field"
|
||||||
model._meta.app_label, model.__name__,
|
% (
|
||||||
|
model._meta.app_label,
|
||||||
|
model.__name__,
|
||||||
result[model.__name__]._meta.app_label,
|
result[model.__name__]._meta.app_label,
|
||||||
result[model.__name__].__name__
|
result[model.__name__].__name__,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -225,8 +253,8 @@ def _create_base_path(baseclass, myclass):
|
||||||
if b._meta.abstract or b._meta.proxy:
|
if b._meta.abstract or b._meta.proxy:
|
||||||
return _get_query_related_name(myclass)
|
return _get_query_related_name(myclass)
|
||||||
else:
|
else:
|
||||||
return path + '__' + _get_query_related_name(myclass)
|
return path + "__" + _get_query_related_name(myclass)
|
||||||
return ''
|
return ""
|
||||||
|
|
||||||
|
|
||||||
def _get_query_related_name(myclass):
|
def _get_query_related_name(myclass):
|
||||||
|
|
@ -256,12 +284,13 @@ def create_instanceof_q(modellist, not_instance_of=False, using=DEFAULT_DB_ALIAS
|
||||||
|
|
||||||
if not isinstance(modellist, (list, tuple)):
|
if not isinstance(modellist, (list, tuple)):
|
||||||
from .models import PolymorphicModel
|
from .models import PolymorphicModel
|
||||||
|
|
||||||
if issubclass(modellist, PolymorphicModel):
|
if issubclass(modellist, PolymorphicModel):
|
||||||
modellist = [modellist]
|
modellist = [modellist]
|
||||||
else:
|
else:
|
||||||
raise TypeError(
|
raise TypeError(
|
||||||
'PolymorphicModel: instance_of expects a list of (polymorphic) '
|
"PolymorphicModel: instance_of expects a list of (polymorphic) "
|
||||||
'models or a single (polymorphic) model'
|
"models or a single (polymorphic) model"
|
||||||
)
|
)
|
||||||
|
|
||||||
contenttype_ids = _get_mro_content_type_ids(modellist, using)
|
contenttype_ids = _get_mro_content_type_ids(modellist, using)
|
||||||
|
|
@ -274,7 +303,9 @@ def create_instanceof_q(modellist, not_instance_of=False, using=DEFAULT_DB_ALIAS
|
||||||
def _get_mro_content_type_ids(models, using):
|
def _get_mro_content_type_ids(models, using):
|
||||||
contenttype_ids = set()
|
contenttype_ids = set()
|
||||||
for model in models:
|
for model in models:
|
||||||
ct = ContentType.objects.db_manager(using).get_for_model(model, for_concrete_model=False)
|
ct = ContentType.objects.db_manager(using).get_for_model(
|
||||||
|
model, for_concrete_model=False
|
||||||
|
)
|
||||||
contenttype_ids.add(ct.pk)
|
contenttype_ids.add(ct.pk)
|
||||||
subclasses = model.__subclasses__()
|
subclasses = model.__subclasses__()
|
||||||
if subclasses:
|
if subclasses:
|
||||||
|
|
|
||||||
|
|
@ -6,15 +6,16 @@ from django.db import models
|
||||||
from . import compat
|
from . import compat
|
||||||
from .compat import python_2_unicode_compatible
|
from .compat import python_2_unicode_compatible
|
||||||
|
|
||||||
|
RE_DEFERRED = re.compile("_Deferred_.*")
|
||||||
RE_DEFERRED = re.compile('_Deferred_.*')
|
|
||||||
|
|
||||||
|
|
||||||
@python_2_unicode_compatible
|
@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 """
|
||||||
|
|
||||||
polymorphic_query_multiline_output = True # cause nicer multiline PolymorphicQuery output
|
polymorphic_query_multiline_output = (
|
||||||
|
True
|
||||||
|
) # cause nicer multiline PolymorphicQuery output
|
||||||
|
|
||||||
polymorphic_showfield_type = False
|
polymorphic_showfield_type = False
|
||||||
polymorphic_showfield_content = False
|
polymorphic_showfield_content = False
|
||||||
|
|
@ -32,24 +33,24 @@ class ShowFieldBase(object):
|
||||||
"helper for __unicode__"
|
"helper for __unicode__"
|
||||||
content = getattr(self, field_name)
|
content = getattr(self, field_name)
|
||||||
if self.polymorphic_showfield_old_format:
|
if self.polymorphic_showfield_old_format:
|
||||||
out = ': '
|
out = ": "
|
||||||
else:
|
else:
|
||||||
out = ' '
|
out = " "
|
||||||
if issubclass(field_type, models.ForeignKey):
|
if issubclass(field_type, models.ForeignKey):
|
||||||
if content is None:
|
if content is None:
|
||||||
out += 'None'
|
out += "None"
|
||||||
else:
|
else:
|
||||||
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, compat.integer_types):
|
||||||
out += str(content)
|
out += str(content)
|
||||||
elif content is None:
|
elif content is None:
|
||||||
out += 'None'
|
out += "None"
|
||||||
else:
|
else:
|
||||||
txt = str(content)
|
txt = str(content)
|
||||||
if len(txt) > self.polymorphic_showfield_max_field_width:
|
if len(txt) > self.polymorphic_showfield_max_field_width:
|
||||||
txt = txt[:self.polymorphic_showfield_max_field_width - 2] + '..'
|
txt = txt[: self.polymorphic_showfield_max_field_width - 2] + ".."
|
||||||
out += '"' + txt + '"'
|
out += '"' + txt + '"'
|
||||||
return out
|
return out
|
||||||
|
|
||||||
|
|
@ -57,7 +58,10 @@ class ShowFieldBase(object):
|
||||||
"helper for __unicode__"
|
"helper for __unicode__"
|
||||||
done_fields = set()
|
done_fields = set()
|
||||||
for field in self._meta.fields + self._meta.many_to_many:
|
for field in self._meta.fields + self._meta.many_to_many:
|
||||||
if field.name in self.polymorphic_internal_model_fields or '_ptr' in field.name:
|
if (
|
||||||
|
field.name in self.polymorphic_internal_model_fields
|
||||||
|
or "_ptr" in field.name
|
||||||
|
):
|
||||||
continue
|
continue
|
||||||
if field.name in done_fields:
|
if field.name in done_fields:
|
||||||
continue # work around django diamond inheritance problem
|
continue # work around django diamond inheritance problem
|
||||||
|
|
@ -66,78 +70,90 @@ class ShowFieldBase(object):
|
||||||
out = field.name
|
out = field.name
|
||||||
|
|
||||||
# if this is the standard primary key named "id", print it as we did with older versions of django_polymorphic
|
# if this is the standard primary key named "id", print it as we did with older versions of django_polymorphic
|
||||||
if field.primary_key and field.name == 'id' and type(field) == models.AutoField:
|
if (
|
||||||
out += ' ' + str(getattr(self, field.name))
|
field.primary_key
|
||||||
|
and field.name == "id"
|
||||||
|
and type(field) == models.AutoField
|
||||||
|
):
|
||||||
|
out += " " + str(getattr(self, field.name))
|
||||||
|
|
||||||
# otherwise, display it just like all other fields (with correct type, shortened content etc.)
|
# otherwise, display it just like all other fields (with correct type, shortened content etc.)
|
||||||
else:
|
else:
|
||||||
if self.polymorphic_showfield_type:
|
if self.polymorphic_showfield_type:
|
||||||
out += ' (' + type(field).__name__
|
out += " (" + type(field).__name__
|
||||||
if field.primary_key:
|
if field.primary_key:
|
||||||
out += '/pk'
|
out += "/pk"
|
||||||
out += ')'
|
out += ")"
|
||||||
|
|
||||||
if self.polymorphic_showfield_content:
|
if self.polymorphic_showfield_content:
|
||||||
out += self._showfields_get_content(field.name, type(field))
|
out += self._showfields_get_content(field.name, type(field))
|
||||||
|
|
||||||
parts.append((False, out, ','))
|
parts.append((False, out, ","))
|
||||||
|
|
||||||
def _showfields_add_dynamic_fields(self, field_list, title, parts):
|
def _showfields_add_dynamic_fields(self, field_list, title, parts):
|
||||||
"helper for __unicode__"
|
"helper for __unicode__"
|
||||||
parts.append((True, '- ' + title, ':'))
|
parts.append((True, "- " + title, ":"))
|
||||||
for field_name in field_list:
|
for field_name in field_list:
|
||||||
out = field_name
|
out = field_name
|
||||||
content = getattr(self, field_name)
|
content = getattr(self, field_name)
|
||||||
if self.polymorphic_showfield_type:
|
if self.polymorphic_showfield_type:
|
||||||
out += ' (' + type(content).__name__ + ')'
|
out += " (" + type(content).__name__ + ")"
|
||||||
if self.polymorphic_showfield_content:
|
if self.polymorphic_showfield_content:
|
||||||
out += self._showfields_get_content(field_name)
|
out += self._showfields_get_content(field_name)
|
||||||
|
|
||||||
parts.append((False, out, ','))
|
parts.append((False, out, ","))
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
# create list ("parts") containing one tuple for each title/field:
|
# create list ("parts") containing one tuple for each title/field:
|
||||||
# ( bool: new section , item-text , separator to use after item )
|
# ( bool: new section , item-text , separator to use after item )
|
||||||
|
|
||||||
# start with model name
|
# start with model name
|
||||||
parts = [(True, RE_DEFERRED.sub('', self.__class__.__name__), ':')]
|
parts = [(True, RE_DEFERRED.sub("", self.__class__.__name__), ":")]
|
||||||
|
|
||||||
# add all regular fields
|
# add all regular fields
|
||||||
self._showfields_add_regular_fields(parts)
|
self._showfields_add_regular_fields(parts)
|
||||||
|
|
||||||
# add annotate fields
|
# add annotate fields
|
||||||
if hasattr(self, 'polymorphic_annotate_names'):
|
if hasattr(self, "polymorphic_annotate_names"):
|
||||||
self._showfields_add_dynamic_fields(self.polymorphic_annotate_names, 'Ann', parts)
|
self._showfields_add_dynamic_fields(
|
||||||
|
self.polymorphic_annotate_names, "Ann", parts
|
||||||
|
)
|
||||||
|
|
||||||
# add extra() select fields
|
# add extra() select fields
|
||||||
if hasattr(self, 'polymorphic_extra_select_names'):
|
if hasattr(self, "polymorphic_extra_select_names"):
|
||||||
self._showfields_add_dynamic_fields(self.polymorphic_extra_select_names, 'Extra', parts)
|
self._showfields_add_dynamic_fields(
|
||||||
|
self.polymorphic_extra_select_names, "Extra", parts
|
||||||
|
)
|
||||||
|
|
||||||
if self.polymorphic_showfield_deferred:
|
if self.polymorphic_showfield_deferred:
|
||||||
fields = self.get_deferred_fields()
|
fields = self.get_deferred_fields()
|
||||||
if fields:
|
if fields:
|
||||||
parts.append((False, "deferred[{0}]".format(",".join(sorted(fields))), ''))
|
parts.append(
|
||||||
|
(False, "deferred[{0}]".format(",".join(sorted(fields))), "")
|
||||||
|
)
|
||||||
|
|
||||||
# format result
|
# format result
|
||||||
|
|
||||||
indent = len(self.__class__.__name__) + 5
|
indent = len(self.__class__.__name__) + 5
|
||||||
indentstr = ''.rjust(indent)
|
indentstr = "".rjust(indent)
|
||||||
out = ''
|
out = ""
|
||||||
xpos = 0
|
xpos = 0
|
||||||
possible_line_break_pos = None
|
possible_line_break_pos = None
|
||||||
|
|
||||||
for i in range(len(parts)):
|
for i in range(len(parts)):
|
||||||
new_section, p, separator = parts[i]
|
new_section, p, separator = parts[i]
|
||||||
final = (i == len(parts) - 1)
|
final = i == len(parts) - 1
|
||||||
if not final:
|
if not final:
|
||||||
next_new_section, _, _ = parts[i + 1]
|
next_new_section, _, _ = parts[i + 1]
|
||||||
|
|
||||||
if (self.polymorphic_showfield_max_line_width
|
if (
|
||||||
|
self.polymorphic_showfield_max_line_width
|
||||||
and xpos + len(p) > self.polymorphic_showfield_max_line_width
|
and xpos + len(p) > self.polymorphic_showfield_max_line_width
|
||||||
and possible_line_break_pos is not None):
|
and possible_line_break_pos is not None
|
||||||
|
):
|
||||||
rest = out[possible_line_break_pos:]
|
rest = out[possible_line_break_pos:]
|
||||||
out = out[:possible_line_break_pos]
|
out = out[:possible_line_break_pos]
|
||||||
out += '\n' + indentstr + rest
|
out += "\n" + indentstr + rest
|
||||||
xpos = indent + len(rest)
|
xpos = indent + len(rest)
|
||||||
|
|
||||||
out += p
|
out += p
|
||||||
|
|
@ -147,26 +163,29 @@ class ShowFieldBase(object):
|
||||||
if not next_new_section:
|
if not next_new_section:
|
||||||
out += separator
|
out += separator
|
||||||
xpos += len(separator)
|
xpos += len(separator)
|
||||||
out += ' '
|
out += " "
|
||||||
xpos += 1
|
xpos += 1
|
||||||
|
|
||||||
if not new_section:
|
if not new_section:
|
||||||
possible_line_break_pos = len(out)
|
possible_line_break_pos = len(out)
|
||||||
|
|
||||||
return '<' + out + '>'
|
return "<" + out + ">"
|
||||||
|
|
||||||
|
|
||||||
class ShowFieldType(ShowFieldBase):
|
class ShowFieldType(ShowFieldBase):
|
||||||
""" model mixin that shows the object's class and it's field types """
|
""" model mixin that shows the object's class and it's field types """
|
||||||
|
|
||||||
polymorphic_showfield_type = True
|
polymorphic_showfield_type = True
|
||||||
|
|
||||||
|
|
||||||
class ShowFieldContent(ShowFieldBase):
|
class ShowFieldContent(ShowFieldBase):
|
||||||
""" model mixin that shows the object's class, it's fields and field contents """
|
""" model mixin that shows the object's class, it's fields and field contents """
|
||||||
|
|
||||||
polymorphic_showfield_content = True
|
polymorphic_showfield_content = True
|
||||||
|
|
||||||
|
|
||||||
class ShowFieldTypeAndContent(ShowFieldBase):
|
class ShowFieldTypeAndContent(ShowFieldBase):
|
||||||
""" model mixin, like ShowFieldContent, but also show field types """
|
""" model mixin, like ShowFieldContent, but also show field types """
|
||||||
|
|
||||||
polymorphic_showfield_type = True
|
polymorphic_showfield_type = True
|
||||||
polymorphic_showfield_content = True
|
polymorphic_showfield_content = True
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
from django.template import Library, Node, TemplateSyntaxError
|
from django.template import Library, Node, TemplateSyntaxError
|
||||||
|
|
||||||
from polymorphic import compat
|
from polymorphic import compat
|
||||||
|
|
||||||
register = Library()
|
register = Library()
|
||||||
|
|
||||||
|
|
||||||
class BreadcrumbScope(Node):
|
class BreadcrumbScope(Node):
|
||||||
|
|
||||||
def __init__(self, base_opts, nodelist):
|
def __init__(self, base_opts, nodelist):
|
||||||
self.base_opts = base_opts
|
self.base_opts = base_opts
|
||||||
self.nodelist = nodelist # Note, takes advantage of Node.child_nodelists
|
self.nodelist = nodelist # Note, takes advantage of Node.child_nodelists
|
||||||
|
|
@ -16,15 +16,14 @@ class BreadcrumbScope(Node):
|
||||||
if len(bits) == 2:
|
if len(bits) == 2:
|
||||||
(tagname, base_opts) = bits
|
(tagname, base_opts) = bits
|
||||||
base_opts = parser.compile_filter(base_opts)
|
base_opts = parser.compile_filter(base_opts)
|
||||||
nodelist = parser.parse(('endbreadcrumb_scope',))
|
nodelist = parser.parse(("endbreadcrumb_scope",))
|
||||||
parser.delete_first_token()
|
parser.delete_first_token()
|
||||||
|
|
||||||
return cls(
|
return cls(base_opts=base_opts, nodelist=nodelist)
|
||||||
base_opts=base_opts,
|
|
||||||
nodelist=nodelist
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
raise TemplateSyntaxError("{0} tag expects 1 argument".format(token.contents[0]))
|
raise TemplateSyntaxError(
|
||||||
|
"{0} tag expects 1 argument".format(token.contents[0])
|
||||||
|
)
|
||||||
|
|
||||||
def render(self, context):
|
def render(self, context):
|
||||||
# app_label is really hard to overwrite in the standard Django ModelAdmin.
|
# app_label is really hard to overwrite in the standard Django ModelAdmin.
|
||||||
|
|
@ -34,8 +33,8 @@ class BreadcrumbScope(Node):
|
||||||
new_vars = {}
|
new_vars = {}
|
||||||
if base_opts and not isinstance(base_opts, compat.string_types):
|
if base_opts and not isinstance(base_opts, compat.string_types):
|
||||||
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,
|
||||||
}
|
}
|
||||||
|
|
||||||
new_scope = context.push()
|
new_scope = context.push()
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,6 @@ from django.utils.translation import ugettext
|
||||||
|
|
||||||
from polymorphic.formsets import BasePolymorphicModelFormSet
|
from polymorphic.formsets import BasePolymorphicModelFormSet
|
||||||
|
|
||||||
|
|
||||||
register = Library()
|
register = Library()
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -19,7 +18,7 @@ def include_empty_form(formset):
|
||||||
for form in formset:
|
for form in formset:
|
||||||
yield form
|
yield form
|
||||||
|
|
||||||
if hasattr(formset, 'empty_forms'):
|
if hasattr(formset, "empty_forms"):
|
||||||
# BasePolymorphicModelFormSet
|
# BasePolymorphicModelFormSet
|
||||||
for form in formset.empty_forms:
|
for form in formset.empty_forms:
|
||||||
yield form
|
yield form
|
||||||
|
|
@ -40,24 +39,25 @@ def as_script_options(formset):
|
||||||
- ``add_text``
|
- ``add_text``
|
||||||
- ``show_add_button``
|
- ``show_add_button``
|
||||||
"""
|
"""
|
||||||
verbose_name = getattr(formset, 'verbose_name', formset.model._meta.verbose_name)
|
verbose_name = getattr(formset, "verbose_name", formset.model._meta.verbose_name)
|
||||||
options = {
|
options = {
|
||||||
'prefix': formset.prefix,
|
"prefix": formset.prefix,
|
||||||
'pkFieldName': formset.model._meta.pk.name,
|
"pkFieldName": formset.model._meta.pk.name,
|
||||||
'addText': getattr(formset, 'add_text', None) or ugettext('Add another %(verbose_name)s') % {
|
"addText": getattr(formset, "add_text", None)
|
||||||
'verbose_name': capfirst(verbose_name),
|
or ugettext("Add another %(verbose_name)s")
|
||||||
},
|
% {"verbose_name": capfirst(verbose_name)},
|
||||||
'showAddButton': getattr(formset, 'show_add_button', True),
|
"showAddButton": getattr(formset, "show_add_button", True),
|
||||||
'deleteText': ugettext('Delete'),
|
"deleteText": ugettext("Delete"),
|
||||||
}
|
}
|
||||||
|
|
||||||
if isinstance(formset, BasePolymorphicModelFormSet):
|
if isinstance(formset, BasePolymorphicModelFormSet):
|
||||||
# Allow to add different types
|
# Allow to add different types
|
||||||
options['childTypes'] = [
|
options["childTypes"] = [
|
||||||
{
|
{
|
||||||
'name': force_text(model._meta.verbose_name),
|
"name": force_text(model._meta.verbose_name),
|
||||||
'type': model._meta.model_name,
|
"type": model._meta.model_name,
|
||||||
} for model in formset.child_forms.keys()
|
}
|
||||||
|
for model in formset.child_forms.keys()
|
||||||
]
|
]
|
||||||
|
|
||||||
return json.dumps(options)
|
return json.dumps(options)
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ class AdminTestCase(TestCase):
|
||||||
"""
|
"""
|
||||||
Testing the admin site
|
Testing the admin site
|
||||||
"""
|
"""
|
||||||
|
|
||||||
#: The model to test
|
#: The model to test
|
||||||
model = None
|
model = None
|
||||||
#: The admin class to test
|
#: The admin class to test
|
||||||
|
|
@ -20,7 +21,9 @@ class AdminTestCase(TestCase):
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpClass(cls):
|
def setUpClass(cls):
|
||||||
super(AdminTestCase, cls).setUpClass()
|
super(AdminTestCase, cls).setUpClass()
|
||||||
cls.admin_user = User.objects.create_superuser('admin', 'admin@example.org', password='admin')
|
cls.admin_user = User.objects.create_superuser(
|
||||||
|
"admin", "admin@example.org", password="admin"
|
||||||
|
)
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(AdminTestCase, self).setUp()
|
super(AdminTestCase, self).setUp()
|
||||||
|
|
@ -37,9 +40,11 @@ class AdminTestCase(TestCase):
|
||||||
|
|
||||||
def register(self, model):
|
def register(self, model):
|
||||||
"""Decorator, like admin.register()"""
|
"""Decorator, like admin.register()"""
|
||||||
|
|
||||||
def _dec(admin_class):
|
def _dec(admin_class):
|
||||||
self.admin_register(model, admin_class)
|
self.admin_register(model, admin_class)
|
||||||
return admin_class
|
return admin_class
|
||||||
|
|
||||||
return _dec
|
return _dec
|
||||||
|
|
||||||
def admin_register(self, model, admin_site):
|
def admin_register(self, model, admin_site):
|
||||||
|
|
@ -48,9 +53,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/", self.admin_site.urls)]))
|
||||||
url('^tmp-admin/', self.admin_site.urls)
|
|
||||||
]))
|
|
||||||
|
|
||||||
def get_admin_instance(self, model):
|
def get_admin_instance(self, model):
|
||||||
try:
|
try:
|
||||||
|
|
@ -66,40 +69,42 @@ class AdminTestCase(TestCase):
|
||||||
|
|
||||||
def get_add_url(self, model):
|
def get_add_url(self, model):
|
||||||
admin_instance = self.get_admin_instance(model)
|
admin_instance = self.get_admin_instance(model)
|
||||||
return reverse(admin_urlname(admin_instance.opts, 'add'))
|
return reverse(admin_urlname(admin_instance.opts, "add"))
|
||||||
|
|
||||||
def get_changelist_url(self, model):
|
def get_changelist_url(self, model):
|
||||||
admin_instance = self.get_admin_instance(model)
|
admin_instance = self.get_admin_instance(model)
|
||||||
return reverse(admin_urlname(admin_instance.opts, 'changelist'))
|
return reverse(admin_urlname(admin_instance.opts, "changelist"))
|
||||||
|
|
||||||
def get_change_url(self, model, object_id):
|
def get_change_url(self, model, object_id):
|
||||||
admin_instance = self.get_admin_instance(model)
|
admin_instance = self.get_admin_instance(model)
|
||||||
return reverse(admin_urlname(admin_instance.opts, 'change'), args=(object_id,))
|
return reverse(admin_urlname(admin_instance.opts, "change"), args=(object_id,))
|
||||||
|
|
||||||
def get_history_url(self, model, object_id):
|
def get_history_url(self, model, object_id):
|
||||||
admin_instance = self.get_admin_instance(model)
|
admin_instance = self.get_admin_instance(model)
|
||||||
return reverse(admin_urlname(admin_instance.opts, 'history'), args=(object_id,))
|
return reverse(admin_urlname(admin_instance.opts, "history"), args=(object_id,))
|
||||||
|
|
||||||
def get_delete_url(self, model, object_id):
|
def get_delete_url(self, model, object_id):
|
||||||
admin_instance = self.get_admin_instance(model)
|
admin_instance = self.get_admin_instance(model)
|
||||||
return reverse(admin_urlname(admin_instance.opts, 'delete'), args=(object_id,))
|
return reverse(admin_urlname(admin_instance.opts, "delete"), args=(object_id,))
|
||||||
|
|
||||||
def admin_get_add(self, model, qs=''):
|
def admin_get_add(self, model, qs=""):
|
||||||
"""
|
"""
|
||||||
Make a direct "add" call to the admin page, circumvening login checks.
|
Make a direct "add" call to the admin page, circumvening login checks.
|
||||||
"""
|
"""
|
||||||
admin_instance = self.get_admin_instance(model)
|
admin_instance = self.get_admin_instance(model)
|
||||||
request = self.create_admin_request('get', self.get_add_url(model) + qs)
|
request = self.create_admin_request("get", self.get_add_url(model) + qs)
|
||||||
response = admin_instance.add_view(request)
|
response = admin_instance.add_view(request)
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
return response
|
return response
|
||||||
|
|
||||||
def admin_post_add(self, model, formdata, qs=''):
|
def admin_post_add(self, model, formdata, qs=""):
|
||||||
"""
|
"""
|
||||||
Make a direct "add" call to the admin page, circumvening login checks.
|
Make a direct "add" call to the admin page, circumvening login checks.
|
||||||
"""
|
"""
|
||||||
admin_instance = self.get_admin_instance(model)
|
admin_instance = self.get_admin_instance(model)
|
||||||
request = self.create_admin_request('post', self.get_add_url(model) + qs, data=formdata)
|
request = self.create_admin_request(
|
||||||
|
"post", self.get_add_url(model) + qs, data=formdata
|
||||||
|
)
|
||||||
response = admin_instance.add_view(request)
|
response = admin_instance.add_view(request)
|
||||||
self.assertFormSuccess(request.path, response)
|
self.assertFormSuccess(request.path, response)
|
||||||
return response
|
return response
|
||||||
|
|
@ -109,7 +114,7 @@ class AdminTestCase(TestCase):
|
||||||
Make a direct "add" call to the admin page, circumvening login checks.
|
Make a direct "add" call to the admin page, circumvening login checks.
|
||||||
"""
|
"""
|
||||||
admin_instance = self.get_admin_instance(model)
|
admin_instance = self.get_admin_instance(model)
|
||||||
request = self.create_admin_request('get', self.get_changelist_url(model))
|
request = self.create_admin_request("get", self.get_changelist_url(model))
|
||||||
response = admin_instance.changelist_view(request)
|
response = admin_instance.changelist_view(request)
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
return response
|
return response
|
||||||
|
|
@ -119,7 +124,9 @@ class AdminTestCase(TestCase):
|
||||||
Perform a GET request on the admin page
|
Perform a GET request on the admin page
|
||||||
"""
|
"""
|
||||||
admin_instance = self.get_admin_instance(model)
|
admin_instance = self.get_admin_instance(model)
|
||||||
request = self.create_admin_request('get', self.get_change_url(model, object_id), data=query, **extra)
|
request = self.create_admin_request(
|
||||||
|
"get", self.get_change_url(model, object_id), data=query, **extra
|
||||||
|
)
|
||||||
response = admin_instance.change_view(request, str(object_id))
|
response = admin_instance.change_view(request, str(object_id))
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
return response
|
return response
|
||||||
|
|
@ -129,7 +136,9 @@ class AdminTestCase(TestCase):
|
||||||
Make a direct "add" call to the admin page, circumvening login checks.
|
Make a direct "add" call to the admin page, circumvening login checks.
|
||||||
"""
|
"""
|
||||||
admin_instance = self.get_admin_instance(model)
|
admin_instance = self.get_admin_instance(model)
|
||||||
request = self.create_admin_request('post', self.get_change_url(model, object_id), data=formdata, **extra)
|
request = self.create_admin_request(
|
||||||
|
"post", self.get_change_url(model, object_id), data=formdata, **extra
|
||||||
|
)
|
||||||
response = admin_instance.change_view(request, str(object_id))
|
response = admin_instance.change_view(request, str(object_id))
|
||||||
self.assertFormSuccess(request.path, response)
|
self.assertFormSuccess(request.path, response)
|
||||||
return response
|
return response
|
||||||
|
|
@ -139,7 +148,9 @@ class AdminTestCase(TestCase):
|
||||||
Perform a GET request on the admin page
|
Perform a GET request on the admin page
|
||||||
"""
|
"""
|
||||||
admin_instance = self.get_admin_instance(model)
|
admin_instance = self.get_admin_instance(model)
|
||||||
request = self.create_admin_request('get', self.get_history_url(model, object_id), data=query, **extra)
|
request = self.create_admin_request(
|
||||||
|
"get", self.get_history_url(model, object_id), data=query, **extra
|
||||||
|
)
|
||||||
response = admin_instance.history_view(request, str(object_id))
|
response = admin_instance.history_view(request, str(object_id))
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
return response
|
return response
|
||||||
|
|
@ -149,7 +160,9 @@ class AdminTestCase(TestCase):
|
||||||
Perform a GET request on the admin delete page
|
Perform a GET request on the admin delete page
|
||||||
"""
|
"""
|
||||||
admin_instance = self.get_admin_instance(model)
|
admin_instance = self.get_admin_instance(model)
|
||||||
request = self.create_admin_request('get', self.get_delete_url(model, object_id), data=query, **extra)
|
request = self.create_admin_request(
|
||||||
|
"get", self.get_delete_url(model, object_id), data=query, **extra
|
||||||
|
)
|
||||||
response = admin_instance.delete_view(request, str(object_id))
|
response = admin_instance.delete_view(request, str(object_id))
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
return response
|
return response
|
||||||
|
|
@ -159,12 +172,16 @@ class AdminTestCase(TestCase):
|
||||||
Make a direct "add" call to the admin page, circumvening login checks.
|
Make a direct "add" call to the admin page, circumvening login checks.
|
||||||
"""
|
"""
|
||||||
if not extra:
|
if not extra:
|
||||||
extra = {'data': {'post': 'yes'}}
|
extra = {"data": {"post": "yes"}}
|
||||||
|
|
||||||
admin_instance = self.get_admin_instance(model)
|
admin_instance = self.get_admin_instance(model)
|
||||||
request = self.create_admin_request('post', self.get_delete_url(model, object_id), **extra)
|
request = self.create_admin_request(
|
||||||
|
"post", self.get_delete_url(model, object_id), **extra
|
||||||
|
)
|
||||||
response = admin_instance.delete_view(request, str(object_id))
|
response = admin_instance.delete_view(request, str(object_id))
|
||||||
self.assertEqual(response.status_code, 302, "Form errors in calling {0}".format(request.path))
|
self.assertEqual(
|
||||||
|
response.status_code, 302, "Form errors in calling {0}".format(request.path)
|
||||||
|
)
|
||||||
return response
|
return response
|
||||||
|
|
||||||
def create_admin_request(self, method, url, data=None, **extra):
|
def create_admin_request(self, method, url, data=None, **extra):
|
||||||
|
|
@ -174,8 +191,8 @@ class AdminTestCase(TestCase):
|
||||||
factory_method = getattr(RequestFactory(), method)
|
factory_method = getattr(RequestFactory(), method)
|
||||||
|
|
||||||
if data is not None:
|
if data is not None:
|
||||||
if method != 'get':
|
if method != "get":
|
||||||
data['csrfmiddlewaretoken'] = 'foo'
|
data["csrfmiddlewaretoken"] = "foo"
|
||||||
dummy_request = factory_method(url, data=data)
|
dummy_request = factory_method(url, data=data)
|
||||||
dummy_request.user = self.admin_user
|
dummy_request.user = self.admin_user
|
||||||
|
|
||||||
|
|
@ -185,7 +202,7 @@ class AdminTestCase(TestCase):
|
||||||
# data = base_data
|
# data = base_data
|
||||||
|
|
||||||
request = factory_method(url, data=data, **extra)
|
request = factory_method(url, data=data, **extra)
|
||||||
request.COOKIES[settings.CSRF_COOKIE_NAME] = 'foo'
|
request.COOKIES[settings.CSRF_COOKIE_NAME] = "foo"
|
||||||
request.csrf_processing_done = True
|
request.csrf_processing_done = True
|
||||||
|
|
||||||
# Add properties which middleware would typically do
|
# Add properties which middleware would typically do
|
||||||
|
|
@ -201,14 +218,21 @@ class AdminTestCase(TestCase):
|
||||||
self.assertIn(response.status_code, [200, 302])
|
self.assertIn(response.status_code, [200, 302])
|
||||||
if response.status_code != 302:
|
if response.status_code != 302:
|
||||||
context_data = response.context_data
|
context_data = response.context_data
|
||||||
if 'errors' in context_data:
|
if "errors" in context_data:
|
||||||
errors = response.context_data['errors']
|
errors = response.context_data["errors"]
|
||||||
elif 'form' in context_data:
|
elif "form" in context_data:
|
||||||
errors = context_data['form'].errors
|
errors = context_data["form"].errors
|
||||||
else:
|
else:
|
||||||
raise KeyError("Unknown field for errors in the TemplateResponse!")
|
raise KeyError("Unknown field for errors in the TemplateResponse!")
|
||||||
|
|
||||||
self.assertEqual(response.status_code, 302,
|
self.assertEqual(
|
||||||
"Form errors in calling {0}:\n{1}".format(request_url, errors.as_text()))
|
response.status_code,
|
||||||
self.assertTrue('/login/?next=' not in response['Location'],
|
302,
|
||||||
"Received login response for {0}".format(request_url))
|
"Form errors in calling {0}:\n{1}".format(
|
||||||
|
request_url, errors.as_text()
|
||||||
|
),
|
||||||
|
)
|
||||||
|
self.assertTrue(
|
||||||
|
"/login/?next=" not in response["Location"],
|
||||||
|
"Received login response for {0}".format(request_url),
|
||||||
|
)
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -1,7 +1,7 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
import django
|
|
||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
|
import django
|
||||||
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.query import QuerySet
|
from django.db.models.query import QuerySet
|
||||||
|
|
@ -9,7 +9,11 @@ from django.db.models.query import QuerySet
|
||||||
from polymorphic.managers import PolymorphicManager
|
from polymorphic.managers import PolymorphicManager
|
||||||
from polymorphic.models import PolymorphicModel
|
from polymorphic.models import PolymorphicModel
|
||||||
from polymorphic.query import PolymorphicQuerySet
|
from polymorphic.query import PolymorphicQuerySet
|
||||||
from polymorphic.showfields import ShowFieldContent, ShowFieldType, ShowFieldTypeAndContent
|
from polymorphic.showfields import (
|
||||||
|
ShowFieldContent,
|
||||||
|
ShowFieldType,
|
||||||
|
ShowFieldTypeAndContent,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class PlainA(models.Model):
|
class PlainA(models.Model):
|
||||||
|
|
@ -59,17 +63,17 @@ class ModelExtraExternal(models.Model):
|
||||||
|
|
||||||
class ModelShow1(ShowFieldType, PolymorphicModel):
|
class ModelShow1(ShowFieldType, PolymorphicModel):
|
||||||
field1 = models.CharField(max_length=10)
|
field1 = models.CharField(max_length=10)
|
||||||
m2m = models.ManyToManyField('self')
|
m2m = models.ManyToManyField("self")
|
||||||
|
|
||||||
|
|
||||||
class ModelShow2(ShowFieldContent, PolymorphicModel):
|
class ModelShow2(ShowFieldContent, PolymorphicModel):
|
||||||
field1 = models.CharField(max_length=10)
|
field1 = models.CharField(max_length=10)
|
||||||
m2m = models.ManyToManyField('self')
|
m2m = models.ManyToManyField("self")
|
||||||
|
|
||||||
|
|
||||||
class ModelShow3(ShowFieldTypeAndContent, PolymorphicModel):
|
class ModelShow3(ShowFieldTypeAndContent, PolymorphicModel):
|
||||||
field1 = models.CharField(max_length=10)
|
field1 = models.CharField(max_length=10)
|
||||||
m2m = models.ManyToManyField('self')
|
m2m = models.ManyToManyField("self")
|
||||||
|
|
||||||
|
|
||||||
class ModelShow1_plain(PolymorphicModel):
|
class ModelShow1_plain(PolymorphicModel):
|
||||||
|
|
@ -108,8 +112,10 @@ class Enhance_Inherit(Enhance_Base, Enhance_Plain):
|
||||||
|
|
||||||
class RelationBase(ShowFieldTypeAndContent, PolymorphicModel):
|
class RelationBase(ShowFieldTypeAndContent, PolymorphicModel):
|
||||||
field_base = models.CharField(max_length=10)
|
field_base = models.CharField(max_length=10)
|
||||||
fk = models.ForeignKey('self', on_delete=models.CASCADE, null=True, related_name='relationbase_set')
|
fk = models.ForeignKey(
|
||||||
m2m = models.ManyToManyField('self')
|
"self", on_delete=models.CASCADE, null=True, related_name="relationbase_set"
|
||||||
|
)
|
||||||
|
m2m = models.ManyToManyField("self")
|
||||||
|
|
||||||
|
|
||||||
class RelationA(RelationBase):
|
class RelationA(RelationBase):
|
||||||
|
|
@ -143,21 +149,24 @@ class ModelUnderRelParent(PolymorphicModel):
|
||||||
|
|
||||||
|
|
||||||
class ModelUnderRelChild(PolymorphicModel):
|
class ModelUnderRelChild(PolymorphicModel):
|
||||||
parent = models.ForeignKey(ModelUnderRelParent, on_delete=models.CASCADE, related_name='children')
|
parent = models.ForeignKey(
|
||||||
|
ModelUnderRelParent, on_delete=models.CASCADE, related_name="children"
|
||||||
|
)
|
||||||
_private2 = models.CharField(max_length=10)
|
_private2 = models.CharField(max_length=10)
|
||||||
|
|
||||||
|
|
||||||
class MyManagerQuerySet(PolymorphicQuerySet):
|
class MyManagerQuerySet(PolymorphicQuerySet):
|
||||||
|
|
||||||
def my_queryset_foo(self):
|
def my_queryset_foo(self):
|
||||||
return self.all() # Just a method to prove the existance of the custom queryset.
|
return (
|
||||||
|
self.all()
|
||||||
|
) # Just a method to prove the existance of the custom queryset.
|
||||||
|
|
||||||
|
|
||||||
class MyManager(PolymorphicManager):
|
class MyManager(PolymorphicManager):
|
||||||
queryset_class = MyManagerQuerySet
|
queryset_class = MyManagerQuerySet
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
return super(MyManager, self).get_queryset().order_by('-field1')
|
return super(MyManager, self).get_queryset().order_by("-field1")
|
||||||
|
|
||||||
def my_queryset_foo(self):
|
def my_queryset_foo(self):
|
||||||
return self.all().my_queryset_foo()
|
return self.all().my_queryset_foo()
|
||||||
|
|
@ -196,12 +205,15 @@ class MROBase2(MROBase1):
|
||||||
|
|
||||||
|
|
||||||
class MROBase3(models.Model):
|
class MROBase3(models.Model):
|
||||||
base_3_id = models.AutoField(primary_key=True) # make sure 'id' field doesn't clash, detected by Django 1.11
|
base_3_id = models.AutoField(
|
||||||
|
primary_key=True
|
||||||
|
) # make sure 'id' field doesn't clash, detected by Django 1.11
|
||||||
objects = models.Manager()
|
objects = models.Manager()
|
||||||
|
|
||||||
|
|
||||||
class MRODerived(MROBase2, MROBase3):
|
class MRODerived(MROBase2, MROBase3):
|
||||||
if django.VERSION < (3, 0):
|
if django.VERSION < (3, 0):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
manager_inheritance_from_future = True
|
manager_inheritance_from_future = True
|
||||||
|
|
||||||
|
|
@ -212,18 +224,20 @@ 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:
|
||||||
fk = models.ForeignKey(ParentModelWithManager, on_delete=models.CASCADE, related_name='childmodel_set')
|
fk = models.ForeignKey(
|
||||||
|
ParentModelWithManager, on_delete=models.CASCADE, related_name="childmodel_set"
|
||||||
|
)
|
||||||
objects = MyManager()
|
objects = MyManager()
|
||||||
|
|
||||||
|
|
||||||
class PlainMyManagerQuerySet(QuerySet):
|
class PlainMyManagerQuerySet(QuerySet):
|
||||||
|
|
||||||
def my_queryset_foo(self):
|
def my_queryset_foo(self):
|
||||||
return self.all() # Just a method to prove the existence of the custom queryset.
|
return (
|
||||||
|
self.all()
|
||||||
|
) # Just a method to prove the existence of the custom queryset.
|
||||||
|
|
||||||
|
|
||||||
class PlainMyManager(models.Manager):
|
class PlainMyManager(models.Manager):
|
||||||
|
|
||||||
def my_queryset_foo(self):
|
def my_queryset_foo(self):
|
||||||
return self.get_queryset().my_queryset_foo()
|
return self.get_queryset().my_queryset_foo()
|
||||||
|
|
||||||
|
|
@ -236,7 +250,11 @@ class PlainParentModelWithManager(models.Model):
|
||||||
|
|
||||||
|
|
||||||
class PlainChildModelWithManager(models.Model):
|
class PlainChildModelWithManager(models.Model):
|
||||||
fk = models.ForeignKey(PlainParentModelWithManager, on_delete=models.CASCADE, related_name='childmodel_set')
|
fk = models.ForeignKey(
|
||||||
|
PlainParentModelWithManager,
|
||||||
|
on_delete=models.CASCADE,
|
||||||
|
related_name="childmodel_set",
|
||||||
|
)
|
||||||
objects = PlainMyManager()
|
objects = PlainMyManager()
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -270,14 +288,14 @@ class InitTestModel(ShowFieldType, PolymorphicModel):
|
||||||
bar = models.CharField(max_length=100)
|
bar = models.CharField(max_length=100)
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
kwargs['bar'] = self.x()
|
kwargs["bar"] = self.x()
|
||||||
super(InitTestModel, self).__init__(*args, **kwargs)
|
super(InitTestModel, self).__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
class InitTestModelSubclass(InitTestModel):
|
class InitTestModelSubclass(InitTestModel):
|
||||||
|
|
||||||
def x(self):
|
def x(self):
|
||||||
return 'XYZ'
|
return "XYZ"
|
||||||
|
|
||||||
|
|
||||||
# models from github issue
|
# models from github issue
|
||||||
|
|
||||||
|
|
@ -319,6 +337,7 @@ class UUIDPlainB(UUIDPlainA):
|
||||||
class UUIDPlainC(UUIDPlainB):
|
class UUIDPlainC(UUIDPlainB):
|
||||||
field3 = models.CharField(max_length=10)
|
field3 = models.CharField(max_length=10)
|
||||||
|
|
||||||
|
|
||||||
# base -> proxy
|
# base -> proxy
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -327,7 +346,6 @@ class ProxyBase(PolymorphicModel):
|
||||||
|
|
||||||
|
|
||||||
class ProxyChild(ProxyBase):
|
class ProxyChild(ProxyBase):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
proxy = True
|
proxy = True
|
||||||
|
|
||||||
|
|
@ -335,6 +353,7 @@ class ProxyChild(ProxyBase):
|
||||||
class NonProxyChild(ProxyBase):
|
class NonProxyChild(ProxyBase):
|
||||||
name = models.CharField(max_length=10)
|
name = models.CharField(max_length=10)
|
||||||
|
|
||||||
|
|
||||||
# base -> proxy -> real models
|
# base -> proxy -> real models
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -343,7 +362,6 @@ class ProxiedBase(ShowFieldTypeAndContent, PolymorphicModel):
|
||||||
|
|
||||||
|
|
||||||
class ProxyModelBase(ProxiedBase):
|
class ProxyModelBase(ProxiedBase):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
proxy = True
|
proxy = True
|
||||||
|
|
||||||
|
|
@ -364,14 +382,20 @@ class ProxyModelB(ProxyModelBase):
|
||||||
# with related field 'ContentType.relatednameclash_set'." (reported by Andrew Ingram)
|
# with related field 'ContentType.relatednameclash_set'." (reported by Andrew Ingram)
|
||||||
# fixed with related_name
|
# fixed with related_name
|
||||||
class RelatedNameClash(ShowFieldType, PolymorphicModel):
|
class RelatedNameClash(ShowFieldType, PolymorphicModel):
|
||||||
ctype = models.ForeignKey(ContentType, on_delete=models.CASCADE, null=True, editable=False)
|
ctype = models.ForeignKey(
|
||||||
|
ContentType, on_delete=models.CASCADE, null=True, editable=False
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
# class with a parent_link to superclass, and a related_name back to subclass
|
# class with a parent_link to superclass, and a related_name back to subclass
|
||||||
|
|
||||||
|
|
||||||
class TestParentLinkAndRelatedName(ModelShow1_plain):
|
class TestParentLinkAndRelatedName(ModelShow1_plain):
|
||||||
superclass = models.OneToOneField(
|
superclass = models.OneToOneField(
|
||||||
ModelShow1_plain, on_delete=models.CASCADE, parent_link=True, related_name='related_name_subclass'
|
ModelShow1_plain,
|
||||||
|
on_delete=models.CASCADE,
|
||||||
|
parent_link=True,
|
||||||
|
related_name="related_name_subclass",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -398,7 +422,7 @@ class AbstractModel(PolymorphicModel):
|
||||||
|
|
||||||
class SwappableModel(AbstractModel):
|
class SwappableModel(AbstractModel):
|
||||||
class Meta:
|
class Meta:
|
||||||
swappable = 'POLYMORPHIC_TEST_SWAPPABLE'
|
swappable = "POLYMORPHIC_TEST_SWAPPABLE"
|
||||||
|
|
||||||
|
|
||||||
class SwappedModel(AbstractModel):
|
class SwappedModel(AbstractModel):
|
||||||
|
|
@ -410,7 +434,9 @@ class InlineParent(models.Model):
|
||||||
|
|
||||||
|
|
||||||
class InlineModelA(PolymorphicModel):
|
class InlineModelA(PolymorphicModel):
|
||||||
parent = models.ForeignKey(InlineParent, related_name='inline_children', on_delete=models.CASCADE)
|
parent = models.ForeignKey(
|
||||||
|
InlineParent, related_name="inline_children", on_delete=models.CASCADE
|
||||||
|
)
|
||||||
field1 = models.CharField(max_length=10)
|
field1 = models.CharField(max_length=10)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -434,13 +460,11 @@ class Duck(PolymorphicModel):
|
||||||
|
|
||||||
|
|
||||||
class RedheadDuck(Duck):
|
class RedheadDuck(Duck):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
proxy = True
|
proxy = True
|
||||||
|
|
||||||
|
|
||||||
class RubberDuck(Duck):
|
class RubberDuck(Duck):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
proxy = True
|
proxy = True
|
||||||
|
|
||||||
|
|
@ -454,22 +478,22 @@ class MultiTableDerived(MultiTableBase):
|
||||||
|
|
||||||
|
|
||||||
class SubclassSelectorAbstractBaseModel(PolymorphicModel):
|
class SubclassSelectorAbstractBaseModel(PolymorphicModel):
|
||||||
base_field = models.CharField(max_length=10, default='test_bf')
|
base_field = models.CharField(max_length=10, default="test_bf")
|
||||||
|
|
||||||
|
|
||||||
class SubclassSelectorAbstractModel(SubclassSelectorAbstractBaseModel):
|
class SubclassSelectorAbstractModel(SubclassSelectorAbstractBaseModel):
|
||||||
abstract_field = models.CharField(max_length=10, default='test_af')
|
abstract_field = models.CharField(max_length=10, default="test_af")
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
abstract = True
|
abstract = True
|
||||||
|
|
||||||
|
|
||||||
class SubclassSelectorAbstractConcreteModel(SubclassSelectorAbstractModel):
|
class SubclassSelectorAbstractConcreteModel(SubclassSelectorAbstractModel):
|
||||||
concrete_field = models.CharField(max_length=10, default='test_cf')
|
concrete_field = models.CharField(max_length=10, default="test_cf")
|
||||||
|
|
||||||
|
|
||||||
class SubclassSelectorProxyBaseModel(PolymorphicModel):
|
class SubclassSelectorProxyBaseModel(PolymorphicModel):
|
||||||
base_field = models.CharField(max_length=10, default='test_bf')
|
base_field = models.CharField(max_length=10, default="test_bf")
|
||||||
|
|
||||||
|
|
||||||
class SubclassSelectorProxyModel(SubclassSelectorProxyBaseModel):
|
class SubclassSelectorProxyModel(SubclassSelectorProxyBaseModel):
|
||||||
|
|
@ -478,4 +502,4 @@ class SubclassSelectorProxyModel(SubclassSelectorProxyBaseModel):
|
||||||
|
|
||||||
|
|
||||||
class SubclassSelectorProxyConcreteModel(SubclassSelectorProxyModel):
|
class SubclassSelectorProxyConcreteModel(SubclassSelectorProxyModel):
|
||||||
concrete_field = models.CharField(max_length=10, default='test_cf')
|
concrete_field = models.CharField(max_length=10, default="test_cf")
|
||||||
|
|
|
||||||
|
|
@ -2,18 +2,31 @@ from django.contrib import admin
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.utils.html import escape
|
from django.utils.html import escape
|
||||||
|
|
||||||
from polymorphic.admin import PolymorphicChildModelAdmin, PolymorphicChildModelFilter, PolymorphicInlineSupportMixin, \
|
from polymorphic.admin import (
|
||||||
PolymorphicParentModelAdmin, StackedPolymorphicInline
|
PolymorphicChildModelAdmin,
|
||||||
|
PolymorphicChildModelFilter,
|
||||||
|
PolymorphicInlineSupportMixin,
|
||||||
|
PolymorphicParentModelAdmin,
|
||||||
|
StackedPolymorphicInline,
|
||||||
|
)
|
||||||
from polymorphic.tests.admintestcase import AdminTestCase
|
from polymorphic.tests.admintestcase import AdminTestCase
|
||||||
from polymorphic.tests.models import InlineModelA, InlineModelB, InlineParent, Model2A, Model2B, Model2C, Model2D
|
from polymorphic.tests.models import (
|
||||||
|
InlineModelA,
|
||||||
|
InlineModelB,
|
||||||
|
InlineParent,
|
||||||
|
Model2A,
|
||||||
|
Model2B,
|
||||||
|
Model2C,
|
||||||
|
Model2D,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class PolymorphicAdminTests(AdminTestCase):
|
class PolymorphicAdminTests(AdminTestCase):
|
||||||
|
|
||||||
def test_admin_registration(self):
|
def test_admin_registration(self):
|
||||||
"""
|
"""
|
||||||
Test how the registration works
|
Test how the registration works
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@self.register(Model2A)
|
@self.register(Model2A)
|
||||||
class Model2Admin(PolymorphicParentModelAdmin):
|
class Model2Admin(PolymorphicParentModelAdmin):
|
||||||
base_model = Model2A
|
base_model = Model2A
|
||||||
|
|
@ -25,48 +38,42 @@ class PolymorphicAdminTests(AdminTestCase):
|
||||||
@self.register(Model2D)
|
@self.register(Model2D)
|
||||||
class Model2ChildAdmin(PolymorphicChildModelAdmin):
|
class Model2ChildAdmin(PolymorphicChildModelAdmin):
|
||||||
base_model = Model2A
|
base_model = Model2A
|
||||||
base_fieldsets = (
|
base_fieldsets = (("Base fields", {"fields": ("field1",)}),)
|
||||||
("Base fields", {
|
|
||||||
'fields': ('field1',)
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
|
|
||||||
# -- add page
|
# -- add page
|
||||||
ct_id = ContentType.objects.get_for_model(Model2D).pk
|
ct_id = ContentType.objects.get_for_model(Model2D).pk
|
||||||
self.admin_get_add(Model2A) # shows type page
|
self.admin_get_add(Model2A) # shows type page
|
||||||
self.admin_get_add(Model2A, qs='?ct_id={}'.format(ct_id)) # shows type page
|
self.admin_get_add(Model2A, qs="?ct_id={}".format(ct_id)) # shows type page
|
||||||
|
|
||||||
self.admin_get_add(Model2A) # shows type page
|
self.admin_get_add(Model2A) # shows type page
|
||||||
self.admin_post_add(Model2A, {
|
self.admin_post_add(
|
||||||
'field1': 'A',
|
Model2A,
|
||||||
'field2': 'B',
|
{"field1": "A", "field2": "B", "field3": "C", "field4": "D"},
|
||||||
'field3': 'C',
|
qs="?ct_id={}".format(ct_id),
|
||||||
'field4': 'D'
|
)
|
||||||
}, qs='?ct_id={}'.format(ct_id))
|
|
||||||
|
|
||||||
d_obj = Model2A.objects.all()[0]
|
d_obj = Model2A.objects.all()[0]
|
||||||
self.assertEqual(d_obj.__class__, Model2D)
|
self.assertEqual(d_obj.__class__, Model2D)
|
||||||
self.assertEqual(d_obj.field1, 'A')
|
self.assertEqual(d_obj.field1, "A")
|
||||||
self.assertEqual(d_obj.field2, 'B')
|
self.assertEqual(d_obj.field2, "B")
|
||||||
|
|
||||||
# -- list page
|
# -- list page
|
||||||
self.admin_get_changelist(Model2A) # asserts 200
|
self.admin_get_changelist(Model2A) # asserts 200
|
||||||
|
|
||||||
# -- edit
|
# -- edit
|
||||||
response = self.admin_get_change(Model2A, d_obj.pk)
|
response = self.admin_get_change(Model2A, d_obj.pk)
|
||||||
self.assertContains(response, 'field4')
|
self.assertContains(response, "field4")
|
||||||
self.admin_post_change(Model2A, d_obj.pk, {
|
self.admin_post_change(
|
||||||
'field1': 'A2',
|
Model2A,
|
||||||
'field2': 'B2',
|
d_obj.pk,
|
||||||
'field3': 'C2',
|
{"field1": "A2", "field2": "B2", "field3": "C2", "field4": "D2"},
|
||||||
'field4': 'D2'
|
)
|
||||||
})
|
|
||||||
|
|
||||||
d_obj.refresh_from_db()
|
d_obj.refresh_from_db()
|
||||||
self.assertEqual(d_obj.field1, 'A2')
|
self.assertEqual(d_obj.field1, "A2")
|
||||||
self.assertEqual(d_obj.field2, 'B2')
|
self.assertEqual(d_obj.field2, "B2")
|
||||||
self.assertEqual(d_obj.field3, 'C2')
|
self.assertEqual(d_obj.field3, "C2")
|
||||||
self.assertEqual(d_obj.field4, 'D2')
|
self.assertEqual(d_obj.field4, "D2")
|
||||||
|
|
||||||
# -- history
|
# -- history
|
||||||
self.admin_get_history(Model2A, d_obj.pk)
|
self.admin_get_history(Model2A, d_obj.pk)
|
||||||
|
|
@ -80,6 +87,7 @@ class PolymorphicAdminTests(AdminTestCase):
|
||||||
"""
|
"""
|
||||||
Test the registration of inline models.
|
Test the registration of inline models.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
class InlineModelAChild(StackedPolymorphicInline.Child):
|
class InlineModelAChild(StackedPolymorphicInline.Child):
|
||||||
model = InlineModelA
|
model = InlineModelA
|
||||||
|
|
||||||
|
|
@ -88,43 +96,46 @@ class PolymorphicAdminTests(AdminTestCase):
|
||||||
|
|
||||||
class Inline(StackedPolymorphicInline):
|
class Inline(StackedPolymorphicInline):
|
||||||
model = InlineModelA
|
model = InlineModelA
|
||||||
child_inlines = (
|
child_inlines = (InlineModelAChild, InlineModelBChild)
|
||||||
InlineModelAChild,
|
|
||||||
InlineModelBChild,
|
|
||||||
)
|
|
||||||
|
|
||||||
@self.register(InlineParent)
|
@self.register(InlineParent)
|
||||||
class InlineParentAdmin(PolymorphicInlineSupportMixin, admin.ModelAdmin):
|
class InlineParentAdmin(PolymorphicInlineSupportMixin, admin.ModelAdmin):
|
||||||
inlines = (Inline,)
|
inlines = (Inline,)
|
||||||
|
|
||||||
parent = InlineParent.objects.create(title='FOO')
|
parent = InlineParent.objects.create(title="FOO")
|
||||||
self.assertEqual(parent.inline_children.count(), 0)
|
self.assertEqual(parent.inline_children.count(), 0)
|
||||||
|
|
||||||
# -- get edit page
|
# -- get edit page
|
||||||
response = self.admin_get_change(InlineParent, parent.pk)
|
response = self.admin_get_change(InlineParent, parent.pk)
|
||||||
|
|
||||||
# Make sure the fieldset has the right data exposed in data-inline-formset
|
# Make sure the fieldset has the right data exposed in data-inline-formset
|
||||||
self.assertContains(response, 'childTypes')
|
self.assertContains(response, "childTypes")
|
||||||
self.assertContains(response, escape('"type": "inlinemodela"'))
|
self.assertContains(response, escape('"type": "inlinemodela"'))
|
||||||
self.assertContains(response, escape('"type": "inlinemodelb"'))
|
self.assertContains(response, escape('"type": "inlinemodelb"'))
|
||||||
|
|
||||||
# -- post edit page
|
# -- post edit page
|
||||||
self.admin_post_change(InlineParent, parent.pk, {
|
self.admin_post_change(
|
||||||
'title': 'FOO2',
|
InlineParent,
|
||||||
'inline_children-INITIAL_FORMS': 0,
|
parent.pk,
|
||||||
'inline_children-TOTAL_FORMS': 1,
|
{
|
||||||
'inline_children-MIN_NUM_FORMS': 0,
|
"title": "FOO2",
|
||||||
'inline_children-MAX_NUM_FORMS': 1000,
|
"inline_children-INITIAL_FORMS": 0,
|
||||||
'inline_children-0-parent': parent.pk,
|
"inline_children-TOTAL_FORMS": 1,
|
||||||
'inline_children-0-polymorphic_ctype': ContentType.objects.get_for_model(InlineModelB).pk,
|
"inline_children-MIN_NUM_FORMS": 0,
|
||||||
'inline_children-0-field1': 'A2',
|
"inline_children-MAX_NUM_FORMS": 1000,
|
||||||
'inline_children-0-field2': 'B2',
|
"inline_children-0-parent": parent.pk,
|
||||||
})
|
"inline_children-0-polymorphic_ctype": ContentType.objects.get_for_model(
|
||||||
|
InlineModelB
|
||||||
|
).pk,
|
||||||
|
"inline_children-0-field1": "A2",
|
||||||
|
"inline_children-0-field2": "B2",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
parent.refresh_from_db()
|
parent.refresh_from_db()
|
||||||
self.assertEqual(parent.title, 'FOO2')
|
self.assertEqual(parent.title, "FOO2")
|
||||||
self.assertEqual(parent.inline_children.count(), 1)
|
self.assertEqual(parent.inline_children.count(), 1)
|
||||||
child = parent.inline_children.all()[0]
|
child = parent.inline_children.all()[0]
|
||||||
self.assertEqual(child.__class__, InlineModelB)
|
self.assertEqual(child.__class__, InlineModelB)
|
||||||
self.assertEqual(child.field1, 'A2')
|
self.assertEqual(child.field1, "A2")
|
||||||
self.assertEqual(child.field2, 'B2')
|
self.assertEqual(child.field2, "B2")
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,7 @@
|
||||||
from unittest import TestCase
|
from unittest import TestCase
|
||||||
|
|
||||||
from polymorphic.contrib.guardian import get_polymorphic_base_content_type
|
from polymorphic.contrib.guardian import get_polymorphic_base_content_type
|
||||||
from polymorphic.tests.models import (
|
from polymorphic.tests.models import Model2D, PlainC
|
||||||
Model2D,
|
|
||||||
PlainC,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class ContribTests(TestCase):
|
class ContribTests(TestCase):
|
||||||
|
|
@ -12,20 +9,19 @@ class ContribTests(TestCase):
|
||||||
The test suite
|
The test suite
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
def test_contrib_guardian(self):
|
def test_contrib_guardian(self):
|
||||||
# Regular Django inheritance should return the child model content type.
|
# Regular Django inheritance should return the child model content type.
|
||||||
obj = PlainC()
|
obj = PlainC()
|
||||||
ctype = get_polymorphic_base_content_type(obj)
|
ctype = get_polymorphic_base_content_type(obj)
|
||||||
self.assertEqual(ctype.name, 'plain c')
|
self.assertEqual(ctype.name, "plain c")
|
||||||
|
|
||||||
ctype = get_polymorphic_base_content_type(PlainC)
|
ctype = get_polymorphic_base_content_type(PlainC)
|
||||||
self.assertEqual(ctype.name, 'plain c')
|
self.assertEqual(ctype.name, "plain c")
|
||||||
|
|
||||||
# Polymorphic inheritance should return the parent model content type.
|
# Polymorphic inheritance should return the parent model content type.
|
||||||
obj = Model2D()
|
obj = Model2D()
|
||||||
ctype = get_polymorphic_base_content_type(obj)
|
ctype = get_polymorphic_base_content_type(obj)
|
||||||
self.assertEqual(ctype.name, 'model2a')
|
self.assertEqual(ctype.name, "model2a")
|
||||||
|
|
||||||
ctype = get_polymorphic_base_content_type(Model2D)
|
ctype = get_polymorphic_base_content_type(Model2D)
|
||||||
self.assertEqual(ctype.name, 'model2a')
|
self.assertEqual(ctype.name, "model2a")
|
||||||
|
|
|
||||||
|
|
@ -23,29 +23,29 @@ class MultipleDatabasesTests(TestCase):
|
||||||
multi_db = True
|
multi_db = True
|
||||||
|
|
||||||
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")
|
||||||
Model2C(field1='C1', field2='C2', field3='C3').save(using='secondary')
|
Model2C(field1="C1", field2="C2", field3="C3").save(using="secondary")
|
||||||
Model2B.objects.create(field1='B1', field2='B2')
|
Model2B.objects.create(field1="B1", field2="B2")
|
||||||
Model2D(field1='D1', field2='D2', field3='D3', field4='D4').save()
|
Model2D(field1="D1", field2="D2", field3="D3", field4="D4").save()
|
||||||
|
|
||||||
self.assertQuerysetEqual(
|
self.assertQuerysetEqual(
|
||||||
Model2A.objects.order_by('id'),
|
Model2A.objects.order_by("id"),
|
||||||
[Model2B, Model2D],
|
[Model2B, Model2D],
|
||||||
transform=lambda o: o.__class__,
|
transform=lambda o: o.__class__,
|
||||||
)
|
)
|
||||||
|
|
||||||
self.assertQuerysetEqual(
|
self.assertQuerysetEqual(
|
||||||
Model2A.objects.db_manager('secondary').order_by('id'),
|
Model2A.objects.db_manager("secondary").order_by("id"),
|
||||||
[Model2A, Model2C],
|
[Model2A, Model2C],
|
||||||
transform=lambda o: o.__class__,
|
transform=lambda o: o.__class__,
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_instance_of_filter_on_non_default_database(self):
|
def test_instance_of_filter_on_non_default_database(self):
|
||||||
Base.objects.db_manager('secondary').create(field_b='B1')
|
Base.objects.db_manager("secondary").create(field_b="B1")
|
||||||
ModelX.objects.db_manager('secondary').create(field_b='B', field_x='X')
|
ModelX.objects.db_manager("secondary").create(field_b="B", field_x="X")
|
||||||
ModelY.objects.db_manager('secondary').create(field_b='Y', field_y='Y')
|
ModelY.objects.db_manager("secondary").create(field_b="Y", field_y="Y")
|
||||||
|
|
||||||
objects = Base.objects.db_manager('secondary').filter(instance_of=Base)
|
objects = Base.objects.db_manager("secondary").filter(instance_of=Base)
|
||||||
self.assertQuerysetEqual(
|
self.assertQuerysetEqual(
|
||||||
objects,
|
objects,
|
||||||
[Base, ModelX, ModelY],
|
[Base, ModelX, ModelY],
|
||||||
|
|
@ -54,19 +54,19 @@ class MultipleDatabasesTests(TestCase):
|
||||||
)
|
)
|
||||||
|
|
||||||
self.assertQuerysetEqual(
|
self.assertQuerysetEqual(
|
||||||
Base.objects.db_manager('secondary').filter(instance_of=ModelX),
|
Base.objects.db_manager("secondary").filter(instance_of=ModelX),
|
||||||
[ModelX],
|
[ModelX],
|
||||||
transform=lambda o: o.__class__,
|
transform=lambda o: o.__class__,
|
||||||
)
|
)
|
||||||
|
|
||||||
self.assertQuerysetEqual(
|
self.assertQuerysetEqual(
|
||||||
Base.objects.db_manager('secondary').filter(instance_of=ModelY),
|
Base.objects.db_manager("secondary").filter(instance_of=ModelY),
|
||||||
[ModelY],
|
[ModelY],
|
||||||
transform=lambda o: o.__class__,
|
transform=lambda o: o.__class__,
|
||||||
)
|
)
|
||||||
|
|
||||||
self.assertQuerysetEqual(
|
self.assertQuerysetEqual(
|
||||||
Base.objects.db_manager('secondary').filter(
|
Base.objects.db_manager("secondary").filter(
|
||||||
Q(instance_of=ModelX) | Q(instance_of=ModelY)
|
Q(instance_of=ModelX) | Q(instance_of=ModelY)
|
||||||
),
|
),
|
||||||
[ModelX, ModelY],
|
[ModelX, ModelY],
|
||||||
|
|
@ -76,10 +76,14 @@ class MultipleDatabasesTests(TestCase):
|
||||||
|
|
||||||
def test_forward_many_to_one_descriptor_on_non_default_database(self):
|
def test_forward_many_to_one_descriptor_on_non_default_database(self):
|
||||||
def func():
|
def func():
|
||||||
blog = BlogA.objects.db_manager('secondary').create(name='Blog', info='Info')
|
blog = BlogA.objects.db_manager("secondary").create(
|
||||||
entry = BlogEntry.objects.db_manager('secondary').create(blog=blog, text='Text')
|
name="Blog", info="Info"
|
||||||
|
)
|
||||||
|
entry = BlogEntry.objects.db_manager("secondary").create(
|
||||||
|
blog=blog, text="Text"
|
||||||
|
)
|
||||||
ContentType.objects.clear_cache()
|
ContentType.objects.clear_cache()
|
||||||
entry = BlogEntry.objects.db_manager('secondary').get(pk=entry.id)
|
entry = BlogEntry.objects.db_manager("secondary").get(pk=entry.id)
|
||||||
self.assertEqual(blog, entry.blog)
|
self.assertEqual(blog, entry.blog)
|
||||||
|
|
||||||
# Ensure no queries are made using the default database.
|
# Ensure no queries are made using the default database.
|
||||||
|
|
@ -87,21 +91,27 @@ class MultipleDatabasesTests(TestCase):
|
||||||
|
|
||||||
def test_reverse_many_to_one_descriptor_on_non_default_database(self):
|
def test_reverse_many_to_one_descriptor_on_non_default_database(self):
|
||||||
def func():
|
def func():
|
||||||
blog = BlogA.objects.db_manager('secondary').create(name='Blog', info='Info')
|
blog = BlogA.objects.db_manager("secondary").create(
|
||||||
entry = BlogEntry.objects.db_manager('secondary').create(blog=blog, text='Text')
|
name="Blog", info="Info"
|
||||||
|
)
|
||||||
|
entry = BlogEntry.objects.db_manager("secondary").create(
|
||||||
|
blog=blog, text="Text"
|
||||||
|
)
|
||||||
ContentType.objects.clear_cache()
|
ContentType.objects.clear_cache()
|
||||||
blog = BlogA.objects.db_manager('secondary').get(pk=blog.id)
|
blog = BlogA.objects.db_manager("secondary").get(pk=blog.id)
|
||||||
self.assertEqual(entry, blog.blogentry_set.using('secondary').get())
|
self.assertEqual(entry, blog.blogentry_set.using("secondary").get())
|
||||||
|
|
||||||
# Ensure no queries are made using the default database.
|
# Ensure no queries are made using the default database.
|
||||||
self.assertNumQueries(0, func)
|
self.assertNumQueries(0, func)
|
||||||
|
|
||||||
def test_reverse_one_to_one_descriptor_on_non_default_database(self):
|
def test_reverse_one_to_one_descriptor_on_non_default_database(self):
|
||||||
def func():
|
def func():
|
||||||
m2a = Model2A.objects.db_manager('secondary').create(field1='A1')
|
m2a = Model2A.objects.db_manager("secondary").create(field1="A1")
|
||||||
one2one = One2OneRelatingModel.objects.db_manager('secondary').create(one2one=m2a, field1='121')
|
one2one = One2OneRelatingModel.objects.db_manager("secondary").create(
|
||||||
|
one2one=m2a, field1="121"
|
||||||
|
)
|
||||||
ContentType.objects.clear_cache()
|
ContentType.objects.clear_cache()
|
||||||
m2a = Model2A.objects.db_manager('secondary').get(pk=m2a.id)
|
m2a = Model2A.objects.db_manager("secondary").get(pk=m2a.id)
|
||||||
self.assertEqual(one2one, m2a.one2onerelatingmodel)
|
self.assertEqual(one2one, m2a.one2onerelatingmodel)
|
||||||
|
|
||||||
# Ensure no queries are made using the default database.
|
# Ensure no queries are made using the default database.
|
||||||
|
|
@ -109,12 +119,12 @@ class MultipleDatabasesTests(TestCase):
|
||||||
|
|
||||||
def test_many_to_many_descriptor_on_non_default_database(self):
|
def test_many_to_many_descriptor_on_non_default_database(self):
|
||||||
def func():
|
def func():
|
||||||
m2a = Model2A.objects.db_manager('secondary').create(field1='A1')
|
m2a = Model2A.objects.db_manager("secondary").create(field1="A1")
|
||||||
rm = RelatingModel.objects.db_manager('secondary').create()
|
rm = RelatingModel.objects.db_manager("secondary").create()
|
||||||
rm.many2many.add(m2a)
|
rm.many2many.add(m2a)
|
||||||
ContentType.objects.clear_cache()
|
ContentType.objects.clear_cache()
|
||||||
m2a = Model2A.objects.db_manager('secondary').get(pk=m2a.id)
|
m2a = Model2A.objects.db_manager("secondary").get(pk=m2a.id)
|
||||||
self.assertEqual(rm, m2a.relatingmodel_set.using('secondary').get())
|
self.assertEqual(rm, m2a.relatingmodel_set.using("secondary").get())
|
||||||
|
|
||||||
# Ensure no queries are made using the default database.
|
# Ensure no queries are made using the default database.
|
||||||
self.assertNumQueries(0, func)
|
self.assertNumQueries(0, func)
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -1,9 +1,9 @@
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
|
|
||||||
from polymorphic.tests.models import Bottom, Middle, Top
|
from polymorphic.tests.models import Bottom, Middle, Top
|
||||||
|
|
||||||
|
|
||||||
class RegressionTests(TestCase):
|
class RegressionTests(TestCase):
|
||||||
|
|
||||||
def test_for_query_result_incomplete_with_inheritance(self):
|
def test_for_query_result_incomplete_with_inheritance(self):
|
||||||
""" https://github.com/bconstantin/django_polymorphic/issues/15 """
|
""" https://github.com/bconstantin/django_polymorphic/issues/15 """
|
||||||
|
|
||||||
|
|
@ -15,10 +15,16 @@ class RegressionTests(TestCase):
|
||||||
bottom.save()
|
bottom.save()
|
||||||
|
|
||||||
expected_queryset = [top, middle, bottom]
|
expected_queryset = [top, middle, bottom]
|
||||||
self.assertQuerysetEqual(Top.objects.order_by('pk'), [repr(r) for r in expected_queryset])
|
self.assertQuerysetEqual(
|
||||||
|
Top.objects.order_by("pk"), [repr(r) for r in expected_queryset]
|
||||||
|
)
|
||||||
|
|
||||||
expected_queryset = [middle, bottom]
|
expected_queryset = [middle, bottom]
|
||||||
self.assertQuerysetEqual(Middle.objects.order_by('pk'), [repr(r) for r in expected_queryset])
|
self.assertQuerysetEqual(
|
||||||
|
Middle.objects.order_by("pk"), [repr(r) for r in expected_queryset]
|
||||||
|
)
|
||||||
|
|
||||||
expected_queryset = [bottom]
|
expected_queryset = [bottom]
|
||||||
self.assertQuerysetEqual(Bottom.objects.order_by('pk'), [repr(r) for r in expected_queryset])
|
self.assertQuerysetEqual(
|
||||||
|
Bottom.objects.order_by("pk"), [repr(r) for r in expected_queryset]
|
||||||
|
)
|
||||||
|
|
|
||||||
|
|
@ -1,26 +1,36 @@
|
||||||
from django.test import TransactionTestCase
|
from django.test import TransactionTestCase
|
||||||
|
|
||||||
from polymorphic.models import PolymorphicTypeUndefined, PolymorphicModel
|
from polymorphic.models import PolymorphicModel, PolymorphicTypeUndefined
|
||||||
from polymorphic.tests.models import Model2A, Model2B, Model2C, Model2D, Enhance_Inherit, Enhance_Base
|
from polymorphic.tests.models import (
|
||||||
from polymorphic.utils import reset_polymorphic_ctype, sort_by_subclass, get_base_polymorphic_model
|
Enhance_Base,
|
||||||
|
Enhance_Inherit,
|
||||||
|
Model2A,
|
||||||
|
Model2B,
|
||||||
|
Model2C,
|
||||||
|
Model2D,
|
||||||
|
)
|
||||||
|
from polymorphic.utils import (
|
||||||
|
get_base_polymorphic_model,
|
||||||
|
reset_polymorphic_ctype,
|
||||||
|
sort_by_subclass,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class UtilsTests(TransactionTestCase):
|
class UtilsTests(TransactionTestCase):
|
||||||
|
|
||||||
def test_sort_by_subclass(self):
|
def test_sort_by_subclass(self):
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
sort_by_subclass(Model2D, Model2B, Model2D, Model2A, Model2C),
|
sort_by_subclass(Model2D, Model2B, Model2D, Model2A, Model2C),
|
||||||
[Model2A, Model2B, Model2C, Model2D, Model2D]
|
[Model2A, Model2B, Model2C, Model2D, Model2D],
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_reset_polymorphic_ctype(self):
|
def test_reset_polymorphic_ctype(self):
|
||||||
"""
|
"""
|
||||||
Test the the polymorphic_ctype_id can be restored.
|
Test the the polymorphic_ctype_id can be restored.
|
||||||
"""
|
"""
|
||||||
Model2A.objects.create(field1='A1')
|
Model2A.objects.create(field1="A1")
|
||||||
Model2D.objects.create(field1='A1', field2='B2', field3='C3', field4='D4')
|
Model2D.objects.create(field1="A1", field2="B2", field3="C3", field4="D4")
|
||||||
Model2B.objects.create(field1='A1', field2='B2')
|
Model2B.objects.create(field1="A1", field2="B2")
|
||||||
Model2B.objects.create(field1='A1', field2='B2')
|
Model2B.objects.create(field1="A1", field2="B2")
|
||||||
Model2A.objects.all().update(polymorphic_ctype_id=None)
|
Model2A.objects.all().update(polymorphic_ctype_id=None)
|
||||||
|
|
||||||
with self.assertRaises(PolymorphicTypeUndefined):
|
with self.assertRaises(PolymorphicTypeUndefined):
|
||||||
|
|
@ -30,12 +40,7 @@ class UtilsTests(TransactionTestCase):
|
||||||
|
|
||||||
self.assertQuerysetEqual(
|
self.assertQuerysetEqual(
|
||||||
Model2A.objects.order_by("pk"),
|
Model2A.objects.order_by("pk"),
|
||||||
[
|
[Model2A, Model2D, Model2B, Model2B],
|
||||||
Model2A,
|
|
||||||
Model2D,
|
|
||||||
Model2B,
|
|
||||||
Model2B,
|
|
||||||
],
|
|
||||||
transform=lambda o: o.__class__,
|
transform=lambda o: o.__class__,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -59,6 +64,7 @@ class UtilsTests(TransactionTestCase):
|
||||||
"""
|
"""
|
||||||
Skipping abstract models that can't be used for querying.
|
Skipping abstract models that can't be used for querying.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
class A(PolymorphicModel):
|
class A(PolymorphicModel):
|
||||||
class Meta:
|
class Meta:
|
||||||
abstract = True
|
abstract = True
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,9 @@ 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
|
||||||
from polymorphic.models import PolymorphicModel
|
|
||||||
|
|
||||||
from polymorphic.base import PolymorphicModelBase
|
from polymorphic.base import PolymorphicModelBase
|
||||||
|
from polymorphic.models import PolymorphicModel
|
||||||
|
|
||||||
|
|
||||||
def reset_polymorphic_ctype(*models, **filters):
|
def reset_polymorphic_ctype(*models, **filters):
|
||||||
|
|
@ -16,8 +16,8 @@ def reset_polymorphic_ctype(*models, **filters):
|
||||||
Add ``preserve_existing=True`` to skip models which already
|
Add ``preserve_existing=True`` to skip models which already
|
||||||
have a polymorphic content type.
|
have a polymorphic content type.
|
||||||
"""
|
"""
|
||||||
using = filters.pop('using', DEFAULT_DB_ALIAS)
|
using = filters.pop("using", DEFAULT_DB_ALIAS)
|
||||||
ignore_existing = filters.pop('ignore_existing', False)
|
ignore_existing = filters.pop("ignore_existing", False)
|
||||||
|
|
||||||
models = sort_by_subclass(*models)
|
models = sort_by_subclass(*models)
|
||||||
if ignore_existing:
|
if ignore_existing:
|
||||||
|
|
@ -26,7 +26,9 @@ def reset_polymorphic_ctype(*models, **filters):
|
||||||
models = reversed(models)
|
models = reversed(models)
|
||||||
|
|
||||||
for new_model in models:
|
for new_model in models:
|
||||||
new_ct = ContentType.objects.db_manager(using).get_for_model(new_model, for_concrete_model=False)
|
new_ct = ContentType.objects.db_manager(using).get_for_model(
|
||||||
|
new_model, for_concrete_model=False
|
||||||
|
)
|
||||||
|
|
||||||
qs = new_model.objects.db_manager(using)
|
qs = new_model.objects.db_manager(using)
|
||||||
if ignore_existing:
|
if ignore_existing:
|
||||||
|
|
@ -61,6 +63,7 @@ def sort_by_subclass(*classes):
|
||||||
return sorted(classes, cmp=_compare_mro)
|
return sorted(classes, cmp=_compare_mro)
|
||||||
else:
|
else:
|
||||||
from functools import cmp_to_key
|
from functools import cmp_to_key
|
||||||
|
|
||||||
return sorted(classes, key=cmp_to_key(_compare_mro))
|
return sorted(classes, key=cmp_to_key(_compare_mro))
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -69,8 +72,10 @@ def get_base_polymorphic_model(ChildModel, allow_abstract=False):
|
||||||
First the first concrete model in the inheritance chain that inherited from the PolymorphicModel.
|
First the first concrete model in the inheritance chain that inherited from the PolymorphicModel.
|
||||||
"""
|
"""
|
||||||
for Model in reversed(ChildModel.mro()):
|
for Model in reversed(ChildModel.mro()):
|
||||||
if isinstance(Model, PolymorphicModelBase) and \
|
if (
|
||||||
Model is not PolymorphicModel and \
|
isinstance(Model, PolymorphicModelBase)
|
||||||
(allow_abstract or not Model._meta.abstract):
|
and Model is not PolymorphicModel
|
||||||
|
and (allow_abstract or not Model._meta.abstract)
|
||||||
|
):
|
||||||
return Model
|
return Model
|
||||||
return None
|
return None
|
||||||
|
|
|
||||||
72
runtests.py
72
runtests.py
|
|
@ -8,50 +8,51 @@ import django
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.core.management import execute_from_command_line
|
from django.core.management import execute_from_command_line
|
||||||
|
|
||||||
|
|
||||||
# python -Wd, or run via coverage:
|
# python -Wd, or run via coverage:
|
||||||
warnings.simplefilter('always', DeprecationWarning)
|
warnings.simplefilter("always", DeprecationWarning)
|
||||||
|
|
||||||
# Give feedback on used versions
|
# Give feedback on used versions
|
||||||
sys.stderr.write('Using Python version {0} from {1}\n'.format(sys.version[:5], sys.executable))
|
sys.stderr.write(
|
||||||
sys.stderr.write('Using Django version {0} from {1}\n'.format(
|
"Using Python version {0} from {1}\n".format(sys.version[:5], sys.executable)
|
||||||
django.get_version(),
|
)
|
||||||
dirname(abspath(django.__file__)))
|
sys.stderr.write(
|
||||||
|
"Using Django version {0} from {1}\n".format(
|
||||||
|
django.get_version(), dirname(abspath(django.__file__))
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
if not settings.configured:
|
if not settings.configured:
|
||||||
settings.configure(
|
settings.configure(
|
||||||
DEBUG=False,
|
DEBUG=False,
|
||||||
DATABASES={
|
DATABASES={
|
||||||
'default': dj_database_url.config(
|
"default": dj_database_url.config(
|
||||||
env='PRIMARY_DATABASE',
|
env="PRIMARY_DATABASE", default="sqlite://:memory:"
|
||||||
default='sqlite://:memory:',
|
|
||||||
),
|
),
|
||||||
'secondary': dj_database_url.config(
|
"secondary": dj_database_url.config(
|
||||||
env='SECONDARY_DATABASE',
|
env="SECONDARY_DATABASE", default="sqlite://:memory:"
|
||||||
default='sqlite://:memory:',
|
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
TEST_RUNNER="django.test.runner.DiscoverRunner",
|
TEST_RUNNER="django.test.runner.DiscoverRunner",
|
||||||
INSTALLED_APPS=(
|
INSTALLED_APPS=(
|
||||||
'django.contrib.auth',
|
"django.contrib.auth",
|
||||||
'django.contrib.contenttypes',
|
"django.contrib.contenttypes",
|
||||||
'django.contrib.messages',
|
"django.contrib.messages",
|
||||||
'django.contrib.sessions',
|
"django.contrib.sessions",
|
||||||
'django.contrib.sites',
|
"django.contrib.sites",
|
||||||
'django.contrib.admin',
|
"django.contrib.admin",
|
||||||
'polymorphic',
|
"polymorphic",
|
||||||
'polymorphic.tests',
|
"polymorphic.tests",
|
||||||
),
|
),
|
||||||
MIDDLEWARE=(
|
MIDDLEWARE=(
|
||||||
'django.middleware.common.CommonMiddleware',
|
"django.middleware.common.CommonMiddleware",
|
||||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
"django.contrib.sessions.middleware.SessionMiddleware",
|
||||||
'django.middleware.csrf.CsrfViewMiddleware',
|
"django.middleware.csrf.CsrfViewMiddleware",
|
||||||
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
"django.contrib.auth.middleware.AuthenticationMiddleware",
|
||||||
'django.contrib.messages.middleware.MessageMiddleware',
|
"django.contrib.messages.middleware.MessageMiddleware",
|
||||||
),
|
),
|
||||||
SITE_ID=3,
|
SITE_ID=3,
|
||||||
TEMPLATES=[{
|
TEMPLATES=[
|
||||||
|
{
|
||||||
"BACKEND": "django.template.backends.django.DjangoTemplates",
|
"BACKEND": "django.template.backends.django.DjangoTemplates",
|
||||||
"DIRS": (),
|
"DIRS": (),
|
||||||
"OPTIONS": {
|
"OPTIONS": {
|
||||||
|
|
@ -69,24 +70,25 @@ if not settings.configured:
|
||||||
"django.contrib.auth.context_processors.auth",
|
"django.contrib.auth.context_processors.auth",
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
},
|
}
|
||||||
],
|
],
|
||||||
POLYMORPHIC_TEST_SWAPPABLE='polymorphic.swappedmodel',
|
POLYMORPHIC_TEST_SWAPPABLE="polymorphic.swappedmodel",
|
||||||
ROOT_URLCONF=None,
|
ROOT_URLCONF=None,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
DEFAULT_TEST_APPS = [
|
DEFAULT_TEST_APPS = ["polymorphic"]
|
||||||
'polymorphic',
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
def runtests():
|
def runtests():
|
||||||
other_args = list(filter(lambda arg: arg.startswith('-'), sys.argv[1:]))
|
other_args = list(filter(lambda arg: arg.startswith("-"), sys.argv[1:]))
|
||||||
test_apps = list(filter(lambda arg: not arg.startswith('-'), sys.argv[1:])) or DEFAULT_TEST_APPS
|
test_apps = (
|
||||||
argv = sys.argv[:1] + ['test', '--traceback'] + other_args + test_apps
|
list(filter(lambda arg: not arg.startswith("-"), sys.argv[1:]))
|
||||||
|
or DEFAULT_TEST_APPS
|
||||||
|
)
|
||||||
|
argv = sys.argv[:1] + ["test", "--traceback"] + other_args + test_apps
|
||||||
execute_from_command_line(argv)
|
execute_from_command_line(argv)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == "__main__":
|
||||||
runtests()
|
runtests()
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue