Reformat all files with isort and black

fix_request_path_info
Diederik van der Boor 2019-07-15 09:50:03 +02:00
parent d314dce4a2
commit 59c020ee50
No known key found for this signature in database
GPG Key ID: 4FA014E0305E73C1
50 changed files with 4069 additions and 2419 deletions

View File

@ -5,11 +5,9 @@
# so the docs root won't be detected by find_packages()
# Display sane URLs in the docs:
STATIC_URL = '/static/'
STATIC_URL = "/static/"
# Avoid error for missing the secret key
SECRET_KEY = 'docs'
SECRET_KEY = "docs"
INSTALLED_APPS = [
'django.contrib.contenttypes',
]
INSTALLED_APPS = ["django.contrib.contenttypes"]

View File

@ -11,173 +11,174 @@
# All configuration values have a default; values that are commented out
# serve to show the default.
import sys
import os
import sys
import django
import sphinx_rtd_theme
# 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
# 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('..'))
os.environ['DJANGO_SETTINGS_MODULE'] = 'djangodummy.settings'
sys.path.insert(0, os.path.abspath("_ext"))
sys.path.insert(0, os.path.abspath(".."))
os.environ["DJANGO_SETTINGS_MODULE"] = "djangodummy.settings"
django.setup()
# -- General configuration -----------------------------------------------------
# If your documentation needs a minimal Sphinx version, state it here.
#needs_sphinx = '1.0'
# needs_sphinx = '1.0'
# Add any Sphinx extension module names here, as strings. They can be extensions
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
extensions = [
'sphinx.ext.autodoc',
'sphinx.ext.graphviz',
'sphinx.ext.intersphinx',
'sphinxcontrib_django',
"sphinx.ext.autodoc",
"sphinx.ext.graphviz",
"sphinx.ext.intersphinx",
"sphinxcontrib_django",
]
# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
templates_path = ["_templates"]
# The suffix of source filenames.
source_suffix = '.rst'
source_suffix = ".rst"
# The encoding of source files.
#source_encoding = 'utf-8-sig'
# source_encoding = 'utf-8-sig'
# The master toctree document.
master_doc = 'index'
master_doc = "index"
# General information about the project.
project = u'django-polymorphic'
copyright = u'2013, Bert Constantin, Chris Glass, Diederik van der Boor'
project = u"django-polymorphic"
copyright = u"2013, Bert Constantin, Chris Glass, Diederik van der Boor"
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
# built documents.
#
# The short X.Y version.
version = '2.0.3'
version = "2.0.3"
# 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
# for a list of supported languages.
#language = None
# language = None
# There are two options for replacing |today|: either, you set today to some
# non-false value, then it is used:
#today = ''
# today = ''
# Else, today_fmt is used as the format for a strftime call.
#today_fmt = '%B %d, %Y'
# today_fmt = '%B %d, %Y'
# List of patterns, relative to source directory, that match files and
# 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.
#default_role = None
# default_role = None
# If true, '()' will be appended to :func: etc. cross-reference text.
#add_function_parentheses = True
# add_function_parentheses = True
# If true, the current module name will be prepended to all description
# unit titles (such as .. function::).
#add_module_names = True
# add_module_names = True
# If true, sectionauthor and moduleauthor directives will be shown in the
# output. They are ignored by default.
#show_authors = False
# show_authors = False
# 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.
#modindex_common_prefix = []
# modindex_common_prefix = []
# -- Options for HTML output ---------------------------------------------------
# The theme to use for HTML and HTML Help pages. See the documentation for
# 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()]
# Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the
# documentation.
#html_theme_options = {}
# html_theme_options = {}
# Add any paths that contain custom themes here, relative to this directory.
#html_theme_path = []
# html_theme_path = []
# The name for this set of Sphinx documents. If None, it defaults to
# "<project> v<release> documentation".
#html_title = None
# html_title = None
# A shorter title for the navigation bar. Default is the same as html_title.
#html_short_title = None
# html_short_title = None
# The name of an image file (relative to this directory) to place at the top
# of the sidebar.
#html_logo = None
# html_logo = None
# The name of an image file (within the static path) to use as favicon of the
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
# pixels large.
#html_favicon = None
# html_favicon = None
# 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,
# 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,
# using the given strftime format.
#html_last_updated_fmt = '%b %d, %Y'
# html_last_updated_fmt = '%b %d, %Y'
# If true, SmartyPants will be used to convert quotes and dashes to
# typographically correct entities.
#html_use_smartypants = True
# html_use_smartypants = True
# Custom sidebar templates, maps document names to template names.
#html_sidebars = {}
# html_sidebars = {}
# Additional templates that should be rendered to pages, maps page names to
# template names.
#html_additional_pages = {}
# html_additional_pages = {}
# If false, no module index is generated.
#html_domain_indices = True
# html_domain_indices = True
# If false, no index is generated.
#html_use_index = True
# html_use_index = True
# If true, the index is split into individual pages for each letter.
#html_split_index = False
# html_split_index = False
# If true, links to the reST sources are added to the pages.
#html_show_sourcelink = True
# html_show_sourcelink = True
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
#html_show_sphinx = True
# html_show_sphinx = True
# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
#html_show_copyright = True
# html_show_copyright = True
# If true, an OpenSearch description file will be output, and all pages will
# contain a <link> tag referring to it. The value of this option must be the
# base URL from which the finished HTML is served.
#html_use_opensearch = ''
# html_use_opensearch = ''
# This is the file name suffix for HTML files (e.g. ".xhtml").
#html_file_suffix = None
# html_file_suffix = None
# Output file base name for HTML help builder.
htmlhelp_basename = 'django-polymorphicdoc'
htmlhelp_basename = "django-polymorphicdoc"
# -- Options for LaTeX output --------------------------------------------------
@ -185,10 +186,8 @@ htmlhelp_basename = 'django-polymorphicdoc'
latex_elements = {
# The paper size ('letterpaper' or 'a4paper').
#'papersize': 'letterpaper',
# The font size ('10pt', '11pt' or '12pt').
#'pointsize': '10pt',
# Additional stuff for the LaTeX preamble.
#'preamble': '',
}
@ -196,29 +195,34 @@ latex_elements = {
# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title, author, documentclass [howto/manual]).
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 title page.
#latex_logo = None
# latex_logo = None
# For "manual" documents, if this is true, then toplevel headings are parts,
# not chapters.
#latex_use_parts = False
# latex_use_parts = False
# If true, show page references after internal links.
#latex_show_pagerefs = False
# latex_show_pagerefs = False
# If true, show URL addresses after external links.
#latex_show_urls = False
# latex_show_urls = False
# Documents to append as an appendix to all manuals.
#latex_appendices = []
# latex_appendices = []
# If false, no module index is generated.
#latex_domain_indices = True
# latex_domain_indices = True
# -- Options for manual page output --------------------------------------------
@ -226,12 +230,17 @@ latex_documents = [
# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
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.
#man_show_urls = False
# man_show_urls = False
# -- Options for Texinfo output ------------------------------------------------
@ -240,26 +249,32 @@ man_pages = [
# (source start file, target name, title, author,
# dir menu entry, description, category)
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.',
'Miscellaneous'),
(
"index",
"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.
#texinfo_appendices = []
# texinfo_appendices = []
# If false, no module index is generated.
#texinfo_domain_indices = True
# texinfo_domain_indices = True
# How to display URL addresses: 'footnote', 'no', or 'inline'.
#texinfo_show_urls = 'footnote'
# texinfo_show_urls = 'footnote'
# Example configuration for intersphinx: refer to the Python standard library.
intersphinx_mapping = {
#'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_member_order = 'groupwise'
autodoc_member_order = "groupwise"

View File

@ -10,105 +10,102 @@ MANAGERS = ADMINS
PROJECT_ROOT = os.path.dirname(os.path.dirname(__file__))
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(PROJECT_ROOT, 'example.db'),
"default": {
"ENGINE": "django.db.backends.sqlite3",
"NAME": os.path.join(PROJECT_ROOT, "example.db"),
}
}
SITE_ID = 1
# Make this unique, and don't share it with anybody.
SECRET_KEY = '5$f%)&amp;a4tc*bg(79+ku!7o$kri-duw99@hq_)va^_kaw9*l)!7'
SECRET_KEY = "5$f%)&amp;a4tc*bg(79+ku!7o$kri-duw99@hq_)va^_kaw9*l)!7"
# Language
# TIME_ZONE = 'America/Chicago'
LANGUAGE_CODE = 'en-us'
LANGUAGE_CODE = "en-us"
USE_I18N = True
USE_L10N = True
USE_TZ = True
# Paths
MEDIA_ROOT = ''
MEDIA_URL = '/media/'
STATIC_ROOT = ''
STATIC_URL = '/static/'
MEDIA_ROOT = ""
MEDIA_URL = "/media/"
STATIC_ROOT = ""
STATIC_URL = "/static/"
# Apps
STATICFILES_FINDERS = (
'django.contrib.staticfiles.finders.FileSystemFinder',
'django.contrib.staticfiles.finders.AppDirectoriesFinder',
"django.contrib.staticfiles.finders.FileSystemFinder",
"django.contrib.staticfiles.finders.AppDirectoriesFinder",
)
MIDDLEWARE = (
'django.middleware.common.CommonMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
"django.middleware.common.CommonMiddleware",
"django.contrib.sessions.middleware.SessionMiddleware",
"django.middleware.csrf.CsrfViewMiddleware",
"django.contrib.auth.middleware.AuthenticationMiddleware",
"django.contrib.messages.middleware.MessageMiddleware",
)
TEMPLATES=[{
"BACKEND": "django.template.backends.django.DjangoTemplates",
"DIRS": (),
"OPTIONS": {
"loaders": (
"django.template.loaders.filesystem.Loader",
"django.template.loaders.app_directories.Loader",
),
"context_processors": (
"django.template.context_processors.debug",
"django.template.context_processors.i18n",
"django.template.context_processors.media",
"django.template.context_processors.request",
"django.template.context_processors.static",
"django.contrib.messages.context_processors.messages",
"django.contrib.auth.context_processors.auth",
),
},
}]
TEMPLATES = [
{
"BACKEND": "django.template.backends.django.DjangoTemplates",
"DIRS": (),
"OPTIONS": {
"loaders": (
"django.template.loaders.filesystem.Loader",
"django.template.loaders.app_directories.Loader",
),
"context_processors": (
"django.template.context_processors.debug",
"django.template.context_processors.i18n",
"django.template.context_processors.media",
"django.template.context_processors.request",
"django.template.context_processors.static",
"django.contrib.messages.context_processors.messages",
"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 = (
'django.contrib.auth',
'django.contrib.admin',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'polymorphic', # needed if you want to use the polymorphic admin
'pexp', # this Django app is for testing and experimentation; not needed otherwise
'orders',
"django.contrib.auth",
"django.contrib.admin",
"django.contrib.contenttypes",
"django.contrib.sessions",
"django.contrib.messages",
"django.contrib.staticfiles",
"polymorphic", # needed if you want to use the polymorphic admin
"pexp", # this Django app is for testing and experimentation; not needed otherwise
"orders",
)
TEST_RUNNER = 'django.test.runner.DiscoverRunner' # silence system checks
TEST_RUNNER = "django.test.runner.DiscoverRunner" # silence system checks
# Logging configuration
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'filters': {
'require_debug_false': {
'()': 'django.utils.log.RequireDebugFalse'
"version": 1,
"disable_existing_loggers": False,
"filters": {"require_debug_false": {"()": "django.utils.log.RequireDebugFalse"}},
"handlers": {
"mail_admins": {
"level": "ERROR",
"filters": ["require_debug_false"],
"class": "django.utils.log.AdminEmailHandler",
}
},
'handlers': {
'mail_admins': {
'level': 'ERROR',
'filters': ['require_debug_false'],
'class': 'django.utils.log.AdminEmailHandler'
"loggers": {
"django.request": {
"handlers": ["mail_admins"],
"level": "ERROR",
"propagate": True,
}
},
'loggers': {
'django.request': {
'handlers': ['mail_admins'],
'level': 'ERROR',
'propagate': True,
},
}
}

View File

@ -6,6 +6,6 @@ from django.views.generic import RedirectView
admin.autodiscover()
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^$', RedirectView.as_view(url=reverse_lazy('admin:index'), permanent=False)),
url(r"^admin/", admin.site.urls),
url(r"^$", RedirectView.as_view(url=reverse_lazy("admin:index"), permanent=False)),
]

View File

@ -15,12 +15,14 @@ framework.
"""
import os
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "example.settings")
# This application object is used by any WSGI server configured to use this
# file. This includes Django's development server, if the WSGI_APPLICATION
# setting points here.
from django.core.wsgi import get_wsgi_application
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "example.settings")
application = get_wsgi_application()
# Apply WSGI middleware here.

View File

@ -1,7 +1,8 @@
from django.contrib import admin
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):
@ -24,11 +25,7 @@ class PaymentInline(StackedPolymorphicInline):
"""
model = Payment
child_inlines = (
CreditCardPaymentInline,
BankPaymentInline,
SepaPaymentInline,
)
child_inlines = (CreditCardPaymentInline, BankPaymentInline, SepaPaymentInline)
@admin.register(Order)
@ -39,5 +36,5 @@ class OrderAdmin(PolymorphicInlineSupportMixin, admin.ModelAdmin):
To make sure the inlines are properly handled,
the ``PolymorphicInlineSupportMixin`` is needed to
"""
inlines = (PaymentInline,)
inlines = (PaymentInline,)

View File

@ -6,83 +6,148 @@ from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('contenttypes', '0002_remove_content_type_name'),
]
dependencies = [("contenttypes", "0002_remove_content_type_name")]
operations = [
migrations.CreateModel(
name='Order',
name="Order",
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={
'ordering': ('title',),
'verbose_name': 'Organisation',
'verbose_name_plural': 'Organisations',
"ordering": ("title",),
"verbose_name": "Organisation",
"verbose_name_plural": "Organisations",
},
),
migrations.CreateModel(
name='Payment',
name="Payment",
fields=[
('id', 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)),
(
"id",
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={
'verbose_name': 'Payment',
'verbose_name_plural': 'Payments',
},
options={"verbose_name": "Payment", "verbose_name_plural": "Payments"},
),
migrations.CreateModel(
name='BankPayment',
name="BankPayment",
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)),
('swift', models.CharField(max_length=20)),
(
"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)),
("swift", models.CharField(max_length=20)),
],
options={
'verbose_name': 'Bank Payment',
'verbose_name_plural': 'Bank Payments',
"verbose_name": "Bank Payment",
"verbose_name_plural": "Bank Payments",
},
bases=('orders.payment',),
bases=("orders.payment",),
),
migrations.CreateModel(
name='CreditCardPayment',
name="CreditCardPayment",
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)),
('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()),
(
"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)),
(
"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={
'verbose_name': 'Credit Card Payment',
'verbose_name_plural': 'Credit Card Payments',
"verbose_name": "Credit Card Payment",
"verbose_name_plural": "Credit Card Payments",
},
bases=('orders.payment',),
bases=("orders.payment",),
),
migrations.CreateModel(
name='SepaPayment',
name="SepaPayment",
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)),
('bic', models.CharField(max_length=11)),
(
"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)),
("bic", models.CharField(max_length=11)),
],
options={
'verbose_name': 'Bank Payment',
'verbose_name_plural': 'Bank Payments',
"verbose_name": "Bank Payment",
"verbose_name_plural": "Bank Payments",
},
bases=('orders.payment',),
bases=("orders.payment",),
),
migrations.AddField(
model_name='payment',
name='order',
field=models.ForeignKey(to='orders.Order', on_delete=models.CASCADE),
model_name="payment",
name="order",
field=models.ForeignKey(to="orders.Order", on_delete=models.CASCADE),
),
migrations.AddField(
model_name='payment',
name='polymorphic_ctype',
field=models.ForeignKey(related_name='polymorphic_orders.payment_set+', editable=False, on_delete=models.CASCADE, to='contenttypes.ContentType', null=True),
model_name="payment",
name="polymorphic_ctype",
field=models.ForeignKey(
related_name="polymorphic_orders.payment_set+",
editable=False,
on_delete=models.CASCADE,
to="contenttypes.ContentType",
null=True,
),
),
]

View File

@ -11,12 +11,13 @@ class Order(models.Model):
"""
An example order that has polymorphic relations
"""
title = models.CharField(_("Title"), max_length=200)
class Meta:
verbose_name = _("Organisation")
verbose_name_plural = _("Organisations")
ordering = ('title',)
ordering = ("title",)
def __str__(self):
return self.title
@ -27,8 +28,9 @@ class Payment(PolymorphicModel):
"""
A generic payment model.
"""
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)
class Meta:
@ -43,6 +45,7 @@ class CreditCardPayment(Payment):
"""
Credit card
"""
MONTH_CHOICES = [(i, n) for i, n in sorted(MONTHS_3.items())]
card_type = models.CharField(max_length=10)
@ -58,6 +61,7 @@ class BankPayment(Payment):
"""
Payment by bank
"""
bank_name = models.CharField(max_length=100)
swift = models.CharField(max_length=20)
@ -70,6 +74,7 @@ class SepaPayment(Payment):
"""
Payment by SEPA (EU)
"""
iban = models.CharField(max_length=34)
bic = models.CharField(max_length=11)

View File

@ -1,6 +1,11 @@
from django.contrib import admin
from polymorphic.admin import PolymorphicParentModelAdmin, PolymorphicChildModelAdmin, PolymorphicChildModelFilter
from pexp.models import *
from polymorphic.admin import (
PolymorphicChildModelAdmin,
PolymorphicChildModelFilter,
PolymorphicParentModelAdmin,
)
class ProjectAdmin(PolymorphicParentModelAdmin):
@ -14,11 +19,7 @@ class ProjectChildAdmin(PolymorphicChildModelAdmin):
# On purpose, only have the shared fields here.
# The fields of the derived model should still be displayed.
base_fieldsets = (
("Base fields", {
'fields': ('topic',)
}),
)
base_fieldsets = (("Base fields", {"fields": ("topic",)}),)
admin.site.register(Project, ProjectAdmin)

View File

@ -7,12 +7,12 @@ import sys
import time
from pprint import pprint
from random import Random
from django.core.management import BaseCommand
from django.db import connection
from pexp.models import *
rnd = Random()
@ -24,7 +24,7 @@ def show_queries():
connection.queries = []
def print_timing(func, message='', iterations=1):
def print_timing(func, message="", iterations=1):
def wrapper(*arg):
results = []
connection.queries_log.clear()
@ -36,13 +36,12 @@ def print_timing(func, message='', iterations=1):
res_sum = 0
for r in results:
res_sum += r
print("%s%-19s: %.4f ms, %i queries (%i times)" % (
message, func.func_name,
res_sum,
len(connection.queries),
iterations
))
print(
"%s%-19s: %.4f ms, %i queries (%i times)"
% (message, func.func_name, res_sum, len(connection.queries), iterations)
)
sys.stdout.flush()
return wrapper
@ -52,9 +51,9 @@ class Command(BaseCommand):
def handle_noargs(self, **options):
if False:
TestModelA.objects.all().delete()
a = TestModelA.objects.create(field1='A1')
b = TestModelB.objects.create(field1='B1', field2='B2')
c = TestModelC.objects.create(field1='C1', field2='C2', field3='C3')
a = TestModelA.objects.create(field1="A1")
b = TestModelB.objects.create(field1="B1", field2="B2")
c = TestModelC.objects.create(field1="C1", field2="C2", field3="C3")
connection.queries_log.clear()
print(TestModelC.base_objects.all())
show_queries()
@ -64,7 +63,9 @@ class Command(BaseCommand):
for i in range(1000):
a = TestModelA.objects.create(field1=str(i % 100))
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:
print(i)
@ -77,9 +78,9 @@ class Command(BaseCommand):
return
NormalModelA.objects.all().delete()
a = NormalModelA.objects.create(field1='A1')
b = NormalModelB.objects.create(field1='B1', field2='B2')
c = NormalModelC.objects.create(field1='C1', field2='C2', field3='C3')
a = NormalModelA.objects.create(field1="A1")
b = NormalModelB.objects.create(field1="B1", field2="B2")
c = NormalModelC.objects.create(field1="C1", field2="C2", field3="C3")
qs = TestModelA.objects.raw("SELECT * from pexp_testmodela")
for o in list(qs):
print(o)
@ -87,7 +88,8 @@ class Command(BaseCommand):
def poly_sql_query():
cursor = connection.cursor()
cursor.execute("""
cursor.execute(
"""
SELECT id, pexp_testmodela.field1, pexp_testmodelb.field2, pexp_testmodelc.field3
FROM pexp_testmodela
LEFT OUTER JOIN pexp_testmodelb
@ -96,18 +98,23 @@ def poly_sql_query():
ON pexp_testmodelb.testmodela_ptr_id = pexp_testmodelc.testmodelb_ptr_id
WHERE pexp_testmodela.field1=%i
ORDER BY pexp_testmodela.id
""" % rnd.randint(0, 100))
"""
% rnd.randint(0, 100)
)
# row=cursor.fetchone()
return
def poly_sql_query2():
cursor = connection.cursor()
cursor.execute("""
cursor.execute(
"""
SELECT id, pexp_testmodela.field1
FROM pexp_testmodela
WHERE pexp_testmodela.field1=%i
ORDER BY pexp_testmodela.id
""" % rnd.randint(0, 100))
"""
% rnd.randint(0, 100)
)
# row=cursor.fetchone()
return

View File

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
"""
This module is a scratchpad for general development, testing & debugging.
"""
@ -20,13 +19,15 @@ class Command(NoArgsCommand):
Project.objects.all().delete()
a = Project.objects.create(topic="John's gathering")
b = ArtProject.objects.create(topic="Sculpting with Tim", artist="T. Turner")
c = ResearchProject.objects.create(topic="Swallow Aerodynamics", supervisor="Dr. Winter")
print Project.objects.all()
print
c = ResearchProject.objects.create(
topic="Swallow Aerodynamics", supervisor="Dr. Winter"
)
print(Project.objects.all())
print("")
TestModelA.objects.all().delete()
a = TestModelA.objects.create(field1='A1')
b = TestModelB.objects.create(field1='B1', field2='B2')
c = TestModelC.objects.create(field1='C1', field2='C2', field3='C3')
print TestModelA.objects.all()
print
a = TestModelA.objects.create(field1="A1")
b = TestModelB.objects.create(field1="B1", field2="B2")
c = TestModelC.objects.create(field1="C1", field2="C2", field3="C3")
print(TestModelA.objects.all())
print("")

View File

@ -3,14 +3,14 @@
This module is a scratchpad for general development, testing & debugging
"""
import time
import sys
import time
from pprint import pprint
from django.core.management import BaseCommand
from django.db import connection
from pprint import pprint
from pexp.models import *
from pexp.models import *
num_objects = 1000
@ -27,7 +27,7 @@ def show_queries():
# benchmark wrappers
def print_timing(func, message='', iterations=1):
def print_timing(func, message="", iterations=1):
def wrapper(*arg):
results = []
connection.queries_log.clear()
@ -40,28 +40,31 @@ def print_timing(func, message='', iterations=1):
for r in results:
res_sum += r
median = res_sum / len(results)
print("%s%-19s: %.0f ms, %i queries" % (
message, func.func_name,
median,
len(connection.queries) / len(results)
))
print(
"%s%-19s: %.0f ms, %i queries"
% (message, func.func_name, median, len(connection.queries) / len(results))
)
sys.stdout.flush()
return wrapper
def run_vanilla_any_poly(func, iterations=1):
f = print_timing(func, ' ', iterations)
f = print_timing(func, " ", iterations)
f(NormalModelC)
f = print_timing(func, 'poly ', iterations)
f = print_timing(func, "poly ", iterations)
f(TestModelC)
###################################################################################
# benchmarks
def bench_create(model):
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()
@ -94,6 +97,7 @@ def bench_load2_short(model):
def bench_delete(model):
model.objects.all().delete()
###################################################################################
# Command
@ -105,10 +109,10 @@ class Command(BaseCommand):
func_list = [
(bench_delete, 1),
(bench_create, 1),
(bench_load1, 5),
(bench_load1, 5),
(bench_load1_short, 5),
(bench_load2, 5),
(bench_load2_short, 5)
(bench_load2_short, 5),
]
for f, iterations in func_list:
run_vanilla_any_poly(f, iterations=iterations)

View File

@ -15,5 +15,7 @@ class Command(BaseCommand):
Project.objects.all().delete()
o = Project.objects.create(topic="John's gathering")
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())

View File

@ -2,181 +2,295 @@
from __future__ import unicode_literals
from django.db import migrations, models
import polymorphic.showfields
class Migration(migrations.Migration):
dependencies = [
('contenttypes', '0002_remove_content_type_name'),
]
dependencies = [("contenttypes", "0002_remove_content_type_name")]
operations = [
migrations.CreateModel(
name='NormalModelA',
name="NormalModelA",
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(
name='Project',
name="Project",
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={
'abstract': False,
},
options={"abstract": False},
bases=(polymorphic.showfields.ShowFieldContent, models.Model),
),
migrations.CreateModel(
name='ProxyBase',
name="ProxyBase",
fields=[
('id', 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)),
(
"id",
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={
'ordering': ('title',),
},
options={"ordering": ("title",)},
),
migrations.CreateModel(
name='TestModelA',
name="TestModelA",
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={
'abstract': False,
},
options={"abstract": False},
bases=(polymorphic.showfields.ShowFieldTypeAndContent, models.Model),
),
migrations.CreateModel(
name='UUIDModelA',
name="UUIDModelA",
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={
'abstract': False,
},
options={"abstract": False},
bases=(polymorphic.showfields.ShowFieldTypeAndContent, models.Model),
),
migrations.CreateModel(
name='ArtProject',
name="ArtProject",
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={
'abstract': False,
},
bases=('pexp.project',),
options={"abstract": False},
bases=("pexp.project",),
),
migrations.CreateModel(
name='NormalModelB',
name="NormalModelB",
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(
name='ResearchProject',
name="ResearchProject",
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={
'abstract': False,
},
bases=('pexp.project',),
options={"abstract": False},
bases=("pexp.project",),
),
migrations.CreateModel(
name='TestModelB',
name="TestModelB",
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={
'abstract': False,
},
bases=('pexp.testmodela',),
options={"abstract": False},
bases=("pexp.testmodela",),
),
migrations.CreateModel(
name='UUIDModelB',
name="UUIDModelB",
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={
'abstract': False,
},
bases=('pexp.uuidmodela',),
options={"abstract": False},
bases=("pexp.uuidmodela",),
),
migrations.AddField(
model_name='uuidmodela',
name='polymorphic_ctype',
field=models.ForeignKey(related_name='polymorphic_pexp.uuidmodela_set+', editable=False, on_delete=models.CASCADE, to='contenttypes.ContentType', null=True),
model_name="uuidmodela",
name="polymorphic_ctype",
field=models.ForeignKey(
related_name="polymorphic_pexp.uuidmodela_set+",
editable=False,
on_delete=models.CASCADE,
to="contenttypes.ContentType",
null=True,
),
),
migrations.AddField(
model_name='testmodela',
name='polymorphic_ctype',
field=models.ForeignKey(related_name='polymorphic_pexp.testmodela_set+', editable=False, on_delete=models.CASCADE, to='contenttypes.ContentType', null=True),
model_name="testmodela",
name="polymorphic_ctype",
field=models.ForeignKey(
related_name="polymorphic_pexp.testmodela_set+",
editable=False,
on_delete=models.CASCADE,
to="contenttypes.ContentType",
null=True,
),
),
migrations.AddField(
model_name='project',
name='polymorphic_ctype',
field=models.ForeignKey(related_name='polymorphic_pexp.project_set+', editable=False, on_delete=models.CASCADE, to='contenttypes.ContentType', null=True),
model_name="project",
name="polymorphic_ctype",
field=models.ForeignKey(
related_name="polymorphic_pexp.project_set+",
editable=False,
on_delete=models.CASCADE,
to="contenttypes.ContentType",
null=True,
),
),
migrations.CreateModel(
name='ProxyA',
fields=[
],
options={
'proxy': True,
},
bases=('pexp.proxybase',),
name="ProxyA", fields=[], options={"proxy": True}, bases=("pexp.proxybase",)
),
migrations.CreateModel(
name='ProxyB',
fields=[
],
options={
'proxy': True,
},
bases=('pexp.proxybase',),
name="ProxyB", fields=[], options={"proxy": True}, bases=("pexp.proxybase",)
),
migrations.CreateModel(
name='NormalModelC',
name="NormalModelC",
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(
name='TestModelC',
name="TestModelC",
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)),
('field4', models.ManyToManyField(related_name='related_c', to='pexp.TestModelB')),
(
"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)),
(
"field4",
models.ManyToManyField(
related_name="related_c", to="pexp.TestModelB"
),
),
],
options={
'abstract': False,
},
bases=('pexp.testmodelb',),
options={"abstract": False},
bases=("pexp.testmodelb",),
),
migrations.CreateModel(
name='UUIDModelC',
name="UUIDModelC",
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={
'abstract': False,
},
bases=('pexp.uuidmodelb',),
options={"abstract": False},
bases=("pexp.uuidmodelb",),
),
]

View File

@ -8,6 +8,7 @@ from polymorphic.showfields import ShowFieldContent, ShowFieldTypeAndContent
class Project(ShowFieldContent, PolymorphicModel):
"""Polymorphic model"""
topic = models.CharField(max_length=30)
@ -21,6 +22,7 @@ class ResearchProject(Project):
class UUIDModelA(ShowFieldTypeAndContent, PolymorphicModel):
"""UUID as primary key example"""
uuid_primary_key = models.UUIDField(primary_key=True)
field1 = models.CharField(max_length=10)
@ -35,17 +37,17 @@ class UUIDModelC(UUIDModelB):
class ProxyBase(PolymorphicModel):
"""Proxy model example - a single table with multiple types."""
title = models.CharField(max_length=200)
def __unicode__(self):
return u"<ProxyBase[type={0}]: {1}>".format(self.polymorphic_ctype, self.title)
class Meta:
ordering = ('title',)
ordering = ("title",)
class ProxyA(ProxyBase):
class Meta:
proxy = True
@ -54,7 +56,6 @@ class ProxyA(ProxyBase):
class ProxyB(ProxyBase):
class Meta:
proxy = True
@ -64,6 +65,7 @@ class ProxyB(ProxyBase):
# Internals for management command tests
class TestModelA(ShowFieldTypeAndContent, PolymorphicModel):
field1 = models.CharField(max_length=10)
@ -74,11 +76,12 @@ class TestModelB(TestModelA):
class TestModelC(TestModelB):
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):
"""Normal Django inheritance, no polymorphic behavior"""
field1 = models.CharField(max_length=10)

View File

@ -13,4 +13,3 @@ try:
__version__ = pkg_resources.require("django-polymorphic")[0].version
except pkg_resources.DistributionNotFound:
__version__ = None # for RTD among others

View File

@ -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),
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 .filters import PolymorphicChildModelFilter
# Utils
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
# as the admin already relies on contenttypes.
from .generic import (
GenericPolymorphicInlineModelAdmin, # base class
GenericStackedPolymorphicInline, # stacked inline
)
from .generic import GenericPolymorphicInlineModelAdmin # base class
from .generic import 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__ = (
'PolymorphicParentModelAdmin',
'PolymorphicChildModelAdmin',
'PolymorphicModelChoiceForm',
'PolymorphicChildModelFilter',
'PolymorphicInlineAdminForm',
'PolymorphicInlineAdminFormSet',
'PolymorphicInlineSupportMixin',
'PolymorphicInlineModelAdmin',
'StackedPolymorphicInline',
'GenericPolymorphicInlineModelAdmin',
'GenericStackedPolymorphicInline',
"PolymorphicParentModelAdmin",
"PolymorphicChildModelAdmin",
"PolymorphicModelChoiceForm",
"PolymorphicChildModelFilter",
"PolymorphicInlineAdminForm",
"PolymorphicInlineAdminFormSet",
"PolymorphicInlineSupportMixin",
"PolymorphicInlineModelAdmin",
"StackedPolymorphicInline",
"GenericPolymorphicInlineModelAdmin",
"GenericStackedPolymorphicInline",
)

View File

@ -8,6 +8,7 @@ from django.urls import resolve
from django.utils.translation import ugettext_lazy as _
from polymorphic.utils import get_base_polymorphic_model
from ..admin import PolymorphicParentModelAdmin
@ -46,7 +47,9 @@ class PolymorphicChildModelAdmin(admin.ModelAdmin):
show_in_index = False
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:
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.
# 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
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)
def get_model_perms(self, request):
match = resolve(request.path)
if 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}
if (
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)
@property
@ -87,10 +94,11 @@ class PolymorphicChildModelAdmin(admin.ModelAdmin):
"admin/%s/%s/change_form.html" % (app_label, opts.object_name.lower()),
"admin/%s/change_form.html" % app_label,
# 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/polymorphic/change_form.html",
"admin/change_form.html"
"admin/change_form.html",
]
@property
@ -103,13 +111,15 @@ class PolymorphicChildModelAdmin(admin.ModelAdmin):
base_app_label = base_opts.app_label
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,
# 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/polymorphic/delete_confirmation.html",
"admin/delete_confirmation.html"
"admin/delete_confirmation.html",
]
@property
@ -125,15 +135,16 @@ class PolymorphicChildModelAdmin(admin.ModelAdmin):
"admin/%s/%s/object_history.html" % (app_label, opts.object_name.lower()),
"admin/%s/object_history.html" % app_label,
# 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/polymorphic/object_history.html",
"admin/object_history.html"
"admin/object_history.html",
]
def _get_parent_admin(self):
# 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:
# when parent_model is in among child_models, just return super instance
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.
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!
# 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):
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):
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):
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)
def render_change_form(
self, request, context, add=False, change=False, form_url="", obj=None
):
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
)
def delete_view(self, request, object_id, context=None):
extra_context = {
'base_opts': self.base_model._meta,
}
return super(PolymorphicChildModelAdmin, self).delete_view(request, object_id, extra_context)
extra_context = {"base_opts": self.base_model._meta}
return super(PolymorphicChildModelAdmin, self).delete_view(
request, object_id, extra_context
)
def history_view(self, request, object_id, extra_context=None):
# Make sure the history view can also display polymorphic breadcrumbs
context = {
'base_opts': self.base_model._meta,
}
context = {"base_opts": self.base_model._meta}
if 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 ----
@ -201,7 +218,7 @@ class PolymorphicChildModelAdmin(admin.ModelAdmin):
if other_fields:
return (
base_fieldsets[0],
(self.extra_fieldset_title, {'fields': other_fields}),
(self.extra_fieldset_title, {"fields": other_fields}),
) + base_fieldsets[1:]
else:
return base_fieldsets
@ -215,13 +232,15 @@ class PolymorphicChildModelAdmin(admin.ModelAdmin):
# By not declaring the fields/form in the base class,
# get_form() will populate the form with all available fields.
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.
for fieldset in self.get_base_fieldsets(request, obj):
for field in fieldset[1]['fields']:
for field in fieldset[1]["fields"]:
try:
subclass_fields.remove(field)
except ValueError:
pass # field not found in form, Django will raise exception later.
pass # field not found in form, Django will raise exception later.
return subclass_fields

View File

@ -14,11 +14,12 @@ class PolymorphicChildModelFilter(admin.SimpleListFilter):
list_filter = (PolymorphicChildModelFilter,)
"""
title = _('Type')
parameter_name = 'polymorphic_ctype'
title = _("Type")
parameter_name = "polymorphic_ctype"
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):
try:
@ -31,5 +32,8 @@ class PolymorphicChildModelFilter(admin.SimpleListFilter):
if choice_value == value:
return queryset.filter(polymorphic_ctype_id=choice_value)
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

View File

@ -7,12 +7,15 @@ class PolymorphicModelChoiceForm(forms.Form):
"""
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):
# Allow to easily redefine the label (a commonly expected usecase)
super(PolymorphicModelChoiceForm, self).__init__(*args, **kwargs)
self.fields['ct_id'].label = self.type_label
self.fields["ct_id"].label = self.type_label

View File

@ -2,14 +2,22 @@ from django.contrib.contenttypes.admin import GenericInlineModelAdmin
from django.contrib.contenttypes.models import ContentType
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
class GenericPolymorphicInlineModelAdmin(PolymorphicInlineModelAdmin, GenericInlineModelAdmin):
class GenericPolymorphicInlineModelAdmin(
PolymorphicInlineModelAdmin, GenericInlineModelAdmin
):
"""
Base class for variation of inlines based on generic foreign keys.
"""
#: The formset class
formset = BaseGenericPolymorphicInlineFormSet
@ -31,6 +39,7 @@ class GenericPolymorphicInlineModelAdmin(PolymorphicInlineModelAdmin, GenericInl
"""
Variation for generic inlines.
"""
# Make sure that the GFK fields are excluded from the child forms
formset_child = GenericPolymorphicFormSetChild
ct_field = "content_type"
@ -42,22 +51,24 @@ class GenericPolymorphicInlineModelAdmin(PolymorphicInlineModelAdmin, GenericInl
Expose the ContentType that the child relates to.
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):
# Similar to GenericInlineModelAdmin.get_formset(),
# make sure the GFK is automatically excluded from the form
defaults = {
"ct_field": self.ct_field,
"fk_field": self.ct_fk_field,
}
defaults = {"ct_field": self.ct_field, "fk_field": self.ct_fk_field}
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):
"""
The stacked layout for generic inlines.
"""
#: The default template to use.
template = 'admin/polymorphic/edit_inline/stacked.html'
template = "admin/polymorphic/edit_inline/stacked.html"

View File

@ -5,7 +5,7 @@ This makes sure that admin fieldsets/layout settings are exported to the templat
"""
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.text import capfirst
from django.utils.translation import ugettext
@ -19,11 +19,11 @@ class PolymorphicInlineAdminForm(InlineAdminForm):
"""
def polymorphic_ctype_field(self):
return AdminField(self.form, 'polymorphic_ctype', False)
return AdminField(self.form, "polymorphic_ctype", False)
@property
def is_empty(self):
return '__prefix__' in self.form.prefix
return "__prefix__" in self.form.prefix
class PolymorphicInlineAdminFormSet(InlineAdminFormSet):
@ -32,15 +32,19 @@ class PolymorphicInlineAdminFormSet(InlineAdminFormSet):
"""
def __init__(self, *args, **kwargs):
self.request = kwargs.pop('request', None) # Assigned later via PolymorphicInlineSupportMixin later.
self.obj = kwargs.pop('obj', None)
self.request = kwargs.pop(
"request", None
) # Assigned later via PolymorphicInlineSupportMixin later.
self.obj = kwargs.pop("obj", None)
super(PolymorphicInlineAdminFormSet, self).__init__(*args, **kwargs)
def __iter__(self):
"""
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
model = original.get_real_instance_class()
child_inline = self.opts.get_child_inline_instance(model)
@ -54,7 +58,7 @@ class PolymorphicInlineAdminFormSet(InlineAdminFormSet):
original=original,
readonly_fields=self.get_child_readonly_fields(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.
@ -88,22 +92,24 @@ class PolymorphicInlineAdminFormSet(InlineAdminFormSet):
This overrides the default Django version to add the ``childTypes`` data.
"""
verbose_name = self.opts.verbose_name
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),
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": force_text(model._meta.verbose_name),
}
for model in self.formset.child_forms.keys()
],
"deleteText": ugettext("Remove"),
},
'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):
@ -119,14 +125,17 @@ class PolymorphicInlineSupportMixin(object):
: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
polymorphic inline formset. This fixes the media and form appearance
of the inline polymorphic models.
"""
inline_admin_formsets = super(PolymorphicInlineSupportMixin, self).get_inline_formsets(
request, formsets, inline_instances, obj=obj)
inline_admin_formsets = super(
PolymorphicInlineSupportMixin, self
).get_inline_formsets(request, formsets, inline_instances, obj=obj)
for admin_formset in inline_admin_formsets:
if isinstance(admin_formset.formset, BasePolymorphicModelFormSet):

View File

@ -10,9 +10,14 @@ from django.contrib.admin.utils import flatten_fieldsets
from django.core.exceptions import ImproperlyConfigured
from django.forms import Media
from polymorphic.formsets import polymorphic_child_forms_factory, BasePolymorphicInlineFormSet, \
PolymorphicFormSetChild, UnsupportedChildType
from polymorphic.formsets import (
BasePolymorphicInlineFormSet,
PolymorphicFormSetChild,
UnsupportedChildType,
polymorphic_child_forms_factory,
)
from polymorphic.formsets.utils import add_media
from .helpers import PolymorphicInlineSupportMixin
@ -31,14 +36,8 @@ class PolymorphicInlineModelAdmin(InlineModelAdmin):
#: The extra media to add for the polymorphic inlines effect.
#: This can be redefined for subclasses.
polymorphic_media = Media(
js=(
'polymorphic/js/polymorphic_inlines.js',
),
css={
'all': (
'polymorphic/css/polymorphic_inlines.css',
)
}
js=("polymorphic/js/polymorphic_inlines.js",),
css={"all": ("polymorphic/css/polymorphic_inlines.css",)},
)
#: The extra forms to show
@ -90,7 +89,9 @@ class PolymorphicInlineModelAdmin(InlineModelAdmin):
try:
return self._child_inlines_lookup[model]
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):
"""
@ -101,7 +102,9 @@ class PolymorphicInlineModelAdmin(InlineModelAdmin):
:rtype: type
"""
# 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
# the regular inlineformset_factory(), and amend that with our extra bits.
@ -151,7 +154,10 @@ class PolymorphicInlineModelAdmin(InlineModelAdmin):
child_media = child_instance.media
# 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, self.polymorphic_media)
@ -170,12 +176,15 @@ class PolymorphicInlineModelAdmin(InlineModelAdmin):
The model form options however, will all be read.
"""
formset_child = PolymorphicFormSetChild
extra = 0 # TODO: currently unused for the children.
def __init__(self, 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):
# 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,
# this allows overriding settings.
if 'fields' in kwargs:
fields = kwargs.pop('fields')
if "fields" in kwargs:
fields = kwargs.pop("fields")
else:
fields = flatten_fieldsets(self.get_fieldsets(request, obj))
@ -220,9 +229,15 @@ class PolymorphicInlineModelAdmin(InlineModelAdmin):
exclude = list(self.exclude)
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
# InlineModelAdmin doesn't define its own.
exclude.extend(self.form._meta.exclude)
@ -232,7 +247,9 @@ class PolymorphicInlineModelAdmin(InlineModelAdmin):
"form": self.form,
"fields": fields,
"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)
@ -247,5 +264,6 @@ class StackedPolymorphicInline(PolymorphicInlineModelAdmin):
Stacked inline for django-polymorphic models.
Since tabular doesn't make much sense with changed fields, just offer this one.
"""
#: The default template to use.
template = 'admin/polymorphic/edit_inline/stacked.html'
template = "admin/polymorphic/edit_inline/stacked.html"

View File

@ -7,7 +7,7 @@ from django.contrib import admin
from django.contrib.admin.helpers import AdminErrorList, AdminForm
from django.contrib.admin.templatetags.admin_urls import add_preserved_filters
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.http import Http404, HttpResponseRedirect
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.safestring import mark_safe
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:
# Django 2.0+
@ -73,7 +74,9 @@ class PolymorphicParentModelAdmin(admin.ModelAdmin):
pk_regex = r"(\d+|__fk__)"
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
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,
# which also means that a "Save and continue editing" button won't work.
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):
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):
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)
@ -134,7 +147,7 @@ class PolymorphicParentModelAdmin(admin.ModelAdmin):
self._lazy_setup()
choices = []
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)
perm_function = getattr(model_admin, perm_function_name)
if not perm_function(request):
@ -145,21 +158,28 @@ class PolymorphicParentModelAdmin(admin.ModelAdmin):
def _get_real_admin(self, object_id, super_if_self=True):
try:
obj = self.model.objects.non_polymorphic() \
.values('polymorphic_ctype').get(pk=object_id)
obj = (
self.model.objects.non_polymorphic()
.values("polymorphic_ctype")
.get(pk=object_id)
)
except self.model.DoesNotExist:
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):
try:
ct = ContentType.objects.get_for_id(ct_id)
except ContentType.DoesNotExist as e:
raise Http404(e) # Handle invalid GET parameters
raise Http404(e) # Handle invalid GET parameters
model_class = ct.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)
@ -167,14 +187,22 @@ class PolymorphicParentModelAdmin(admin.ModelAdmin):
# 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.
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:
# HACK: the only way to get the instance of an model admin,
# is to read the registry of the AdminSite.
real_admin = self._child_admin_site._registry[model_class]
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:
return super(PolymorphicParentModelAdmin, self)
@ -188,19 +216,21 @@ class PolymorphicParentModelAdmin(admin.ModelAdmin):
qs = qs.non_polymorphic()
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."""
ct_id = int(request.GET.get('ct_id', 0))
ct_id = int(request.GET.get("ct_id", 0))
if not ct_id:
# Display choices
return self.add_type_view(request)
else:
real_admin = self._get_real_admin_by_ct(ct_id)
# rebuild form_url, otherwise libraries below will override it.
form_url = add_preserved_filters({
'preserved_filters': urlencode({'ct_id': ct_id}),
'opts': self.model._meta},
form_url
form_url = add_preserved_filters(
{
"preserved_filters": urlencode({"ct_id": ct_id}),
"opts": self.model._meta,
},
form_url,
)
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)
else:
# 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):
"""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)
def get_preserved_filters(self, request):
if '_changelist_filters' in request.GET:
if "_changelist_filters" in request.GET:
request.GET = request.GET.copy()
filters = request.GET.get('_changelist_filters')
filters = request.GET.get("_changelist_filters")
f = filters.split("&")
for x in f:
c = x.split('=')
c = x.split("=")
request.GET[c[0]] = c[1]
del request.GET['_changelist_filters']
del request.GET["_changelist_filters"]
return super(PolymorphicParentModelAdmin, self).get_preserved_filters(request)
def get_urls(self):
@ -256,91 +288,100 @@ class PolymorphicParentModelAdmin(admin.ModelAdmin):
"""
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:
# See if the path started with an ID.
try:
pos = path.find('/')
pos = path.find("/")
if pos == -1:
object_id = long(path)
else:
object_id = long(path[0:pos])
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)
resolver = URLResolver('^', real_admin.urls)
resolver = URLResolver("^", real_admin.urls)
resolvermatch = resolver.resolve(path) # May raise Resolver404
if not resolvermatch:
raise Http404("No match for path '{0}' in admin subclass.".format(path))
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.
"""
if not self.has_add_permission(request):
raise PermissionDenied
extra_qs = ''
if request.META['QUERY_STRING']:
extra_qs = ""
if request.META["QUERY_STRING"]:
# QUERY_STRING is bytes in Python 3, using force_text() to decode it as string.
# 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:
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
form = self.add_type_form(
data=request.POST if request.method == 'POST' else None,
initial={'ct_id': choices[0][0]}
data=request.POST if request.method == "POST" else None,
initial={"ct_id": choices[0][0]},
)
form.fields['ct_id'].choices = choices
form.fields["ct_id"].choices = choices
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
fieldsets = ((None, {'fields': ('ct_id',)}),)
fieldsets = ((None, {"fields": ("ct_id",)}),)
adminForm = AdminForm(form, fieldsets, {}, model_admin=self)
media = self.media + adminForm.media
opts = self.model._meta
context = {
'title': _('Add %s') % force_text(opts.verbose_name),
'adminform': adminForm,
'is_popup': ("_popup" in request.POST or
"_popup" in request.GET),
'media': mark_safe(media),
'errors': AdminErrorList(form, ()),
'app_label': opts.app_label,
"title": _("Add %s") % force_text(opts.verbose_name),
"adminform": adminForm,
"is_popup": ("_popup" in request.POST or "_popup" in request.GET),
"media": mark_safe(media),
"errors": AdminErrorList(form, ()),
"app_label": opts.app_label,
}
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.
"""
opts = self.model._meta
app_label = opts.app_label
context.update({
'has_change_permission': self.has_change_permission(request),
'form_url': mark_safe(form_url),
'opts': opts,
'add': True,
'save_on_top': self.save_on_top,
})
context.update(
{
"has_change_permission": self.has_change_permission(request),
"form_url": mark_safe(form_url),
"opts": opts,
"add": True,
"save_on_top": self.save_on_top,
}
)
templates = self.add_type_template or [
"admin/%s/%s/add_type_form.html" % (app_label, opts.object_name.lower()),
"admin/%s/add_type_form.html" % app_label,
"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
@ -359,7 +400,8 @@ class PolymorphicParentModelAdmin(admin.ModelAdmin):
"admin/%s/%s/change_list.html" % (app_label, opts.object_name.lower()),
"admin/%s/change_list.html" % app_label,
# 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/change_list.html"
"admin/change_list.html",
]

View File

@ -20,9 +20,11 @@ from .query import PolymorphicQuerySet
# PolymorphicQuerySet Q objects (and filter()) support these additional key words.
# 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):
@ -32,6 +34,7 @@ class ManagerInheritanceWarning(RuntimeWarning):
###################################################################################
# PolymorphicModel meta class
class PolymorphicModelBase(ModelBase):
"""
Manager inheritance is a pretty complex topic which may need
@ -60,17 +63,21 @@ class PolymorphicModelBase(ModelBase):
# print; print '###', model_name, '- bases:', bases
# Workaround compatibility issue with six.with_metaclass() and custom Django model metaclasses:
if not attrs and model_name == 'NewBase':
return super(PolymorphicModelBase, self).__new__(self, model_name, bases, attrs)
if not attrs and model_name == "NewBase":
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
# simulated that behavior on the polymorphic manager to all subclasses behave like polymorphics
if django.VERSION < (2, 0):
if 'Meta' in attrs:
if not hasattr(attrs['Meta'], 'manager_inheritance_from_future'):
attrs['Meta'].manager_inheritance_from_future = True
if "Meta" in attrs:
if not hasattr(attrs["Meta"], "manager_inheritance_from_future"):
attrs["Meta"].manager_inheritance_from_future = True
else:
attrs['Meta'] = type('Meta', (object,), {'manager_inheritance_from_future': True})
attrs["Meta"] = type(
"Meta", (object,), {"manager_inheritance_from_future": True}
)
# create new model
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
# which is directly in the python path. To work around this we temporarily set
# app_label here for PolymorphicModel.
meta = attrs.get('Meta', None)
do_app_label_workaround = (meta
and attrs['__module__'] == 'polymorphic'
and model_name == 'PolymorphicModel'
and getattr(meta, 'app_label', None) is None)
meta = attrs.get("Meta", None)
do_app_label_workaround = (
meta
and attrs["__module__"] == "polymorphic"
and model_name == "PolymorphicModel"
and getattr(meta, "app_label", None) is None
)
if do_app_label_workaround:
meta.app_label = 'poly_dummy_app_label'
new_class = super(PolymorphicModelBase, self).__new__(self, model_name, bases, attrs)
meta.app_label = "poly_dummy_app_label"
new_class = super(PolymorphicModelBase, self).__new__(
self, model_name, bases, attrs
)
if do_app_label_workaround:
del(meta.app_label)
del meta.app_label
return new_class
@classmethod
@ -133,17 +144,25 @@ class PolymorphicModelBase(ModelBase):
if django.VERSION < (2, 0):
extra = "\nConsider using Meta.manager_inheritance_from_future = True for Django 1.x projects"
else:
extra = ''
e = ('PolymorphicModel: "{0}.{1}" manager is of type "{2}", but must be a subclass of'
' PolymorphicManager.{extra} to support retrieving subclasses'.format(
model_name, manager_name, type(manager).__name__, extra=extra))
extra = ""
e = (
'PolymorphicModel: "{0}.{1}" manager is of type "{2}", but must be a subclass of'
" PolymorphicManager.{extra} to support retrieving subclasses".format(
model_name, manager_name, type(manager).__name__, extra=extra
)
)
warnings.warn(e, ManagerInheritanceWarning, stacklevel=3)
return manager
if not getattr(manager, 'queryset_class', None) or not issubclass(manager.queryset_class, PolymorphicQuerySet):
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))
if not getattr(manager, "queryset_class", None) or not issubclass(
manager.queryset_class, PolymorphicQuerySet
):
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)
return manager
@ -151,8 +170,12 @@ class PolymorphicModelBase(ModelBase):
def base_objects(self):
warnings.warn(
"Using PolymorphicModel.base_objects is deprecated.\n"
"Use {0}.objects.non_polymorphic() instead.".format(self.__class__.__name__),
DeprecationWarning, stacklevel=2)
"Use {0}.objects.non_polymorphic() instead.".format(
self.__class__.__name__
),
DeprecationWarning,
stacklevel=2,
)
return self._base_objects
@property
@ -162,13 +185,13 @@ class PolymorphicModelBase(ModelBase):
# manager as default manager for the third level of inheritance when
# that third level doesn't define a manager at all.
manager = models.Manager()
manager.name = 'base_objects'
manager.name = "base_objects"
manager.model = self
return manager
@property
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
# hack: a small patch to Django would be a better solution.
# 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).
# This way we don't need to patch django.core.management.commands.dumpdata
# 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]:
return self._base_objects
manager = super(PolymorphicModelBase, self)._default_manager
if not isinstance(manager, PolymorphicManager):
warnings.warn("{0}._default_manager is not a PolymorphicManager".format(
self.__class__.__name__
), ManagerInheritanceWarning)
warnings.warn(
"{0}._default_manager is not a PolymorphicManager".format(
self.__class__.__name__
),
ManagerInheritanceWarning,
)
return manager

View File

@ -1,21 +1,20 @@
"""Compatibility with Python 2 (taken from 'django.utils.six')"""
import sys
PY2 = sys.version_info[0] == 2
PY3 = sys.version_info[0] == 3
if PY3:
string_types = str,
integer_types = int,
class_types = type,
string_types = (str,)
integer_types = (int,)
class_types = (type,)
text_type = str
binary_type = bytes
MAXSIZE = sys.maxsize
else:
string_types = basestring,
string_types = (basestring,)
integer_types = (int, long)
@ -23,7 +22,8 @@ def with_metaclass(meta, *bases):
class metaclass(type):
def __new__(cls, name, this_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):
@ -35,10 +35,11 @@ def python_2_unicode_compatible(klass):
returning text and apply this decorator to the class.
"""
if PY2:
if '__str__' not in klass.__dict__:
raise ValueError("@python_2_unicode_compatible cannot be applied "
"to %s because it doesn't define __str__()." %
klass.__name__)
if "__str__" not in klass.__dict__:
raise ValueError(
"@python_2_unicode_compatible cannot be applied "
"to %s because it doesn't define __str__()." % klass.__name__
)
klass.__unicode__ = klass.__str__
klass.__str__ = lambda self: self.__unicode__().encode('utf-8')
klass.__str__ = lambda self: self.__unicode__().encode("utf-8")
return klass

View File

@ -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.
"""
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__ = (
'PolymorphicFormSetView',
'PolymorphicInlineFormSetView',
'PolymorphicInlineFormSet',
"PolymorphicFormSetView",
"PolymorphicInlineFormSetView",
"PolymorphicInlineFormSet",
)
@ -25,7 +30,7 @@ class PolymorphicFormSetMixin(object):
formset_class = BasePolymorphicModelFormSet
#: Default 0 extra forms
factory_kwargs = {'extra': 0}
factory_kwargs = {"extra": 0}
#: Define the children
# :type: list[PolymorphicFormSetChild]
@ -36,7 +41,9 @@ class PolymorphicFormSetMixin(object):
:rtype: list[PolymorphicFormSetChild]
"""
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
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.
# This makes sure the base class construction is completely honored.
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
@ -72,10 +81,13 @@ class PolymorphicFormSetView(PolymorphicFormSetMixin, extra_views.ModelFormSetVi
]
"""
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.
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),
]
"""
formset_class = BasePolymorphicInlineFormSet
class PolymorphicInlineFormSet(PolymorphicFormSetMixin, extra_views.InlineFormSetFactory):
class PolymorphicInlineFormSet(
PolymorphicFormSetMixin, extra_views.InlineFormSetFactory
):
"""
An inline to add to the ``inlines`` of
the :class:`~extra_views.advanced.CreateWithInlinesView`
@ -123,4 +138,5 @@ class PolymorphicInlineFormSet(PolymorphicFormSetMixin, extra_views.InlineFormSe
return self.object.get_absolute_url()
"""
formset_class = BasePolymorphicInlineFormSet

View File

@ -10,7 +10,7 @@ def get_polymorphic_base_content_type(obj):
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:
superclasses = list(obj.__class__.mro())
except TypeError:
@ -19,11 +19,11 @@ def get_polymorphic_base_content_type(obj):
polymorphic_superclasses = list()
for sclass in superclasses:
if hasattr(sclass, 'polymorphic_model_marker'):
if hasattr(sclass, "polymorphic_model_marker"):
polymorphic_superclasses.append(sclass)
# 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]
else:
root_polymorphic_class = polymorphic_superclasses[-2]

View File

@ -8,31 +8,30 @@ For every child type, there is an :class:`PolymorphicFormSetChild` instance
that describes how to display and construct the child.
It's parameters are very similar to the parent's factory method.
"""
from .models import (
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.
from .generic import ( # Can import generic here, as polymorphic already depends on the 'contenttypes' app.
BaseGenericPolymorphicInlineFormSet,
GenericPolymorphicFormSetChild,
generic_polymorphic_inlineformset_factory,
)
from .models import (
BasePolymorphicInlineFormSet,
BasePolymorphicModelFormSet,
PolymorphicFormSetChild,
UnsupportedChildType,
polymorphic_child_forms_factory,
polymorphic_inlineformset_factory,
polymorphic_modelformset_factory,
)
__all__ = (
'BasePolymorphicModelFormSet',
'BasePolymorphicInlineFormSet',
'PolymorphicFormSetChild',
'UnsupportedChildType',
'polymorphic_modelformset_factory',
'polymorphic_inlineformset_factory',
'polymorphic_child_forms_factory',
'BaseGenericPolymorphicInlineFormSet',
'GenericPolymorphicFormSetChild',
'generic_polymorphic_inlineformset_factory',
"BasePolymorphicModelFormSet",
"BasePolymorphicInlineFormSet",
"PolymorphicFormSetChild",
"UnsupportedChildType",
"polymorphic_modelformset_factory",
"polymorphic_inlineformset_factory",
"polymorphic_child_forms_factory",
"BaseGenericPolymorphicInlineFormSet",
"GenericPolymorphicFormSetChild",
"generic_polymorphic_inlineformset_factory",
)

View File

@ -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.db import models
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):
@ -12,8 +19,8 @@ class GenericPolymorphicFormSetChild(PolymorphicFormSetChild):
"""
def __init__(self, *args, **kwargs):
self.ct_field = kwargs.pop('ct_field', 'content_type')
self.fk_field = kwargs.pop('fk_field', 'object_id')
self.ct_field = kwargs.pop("ct_field", "content_type")
self.fk_field = kwargs.pop("fk_field", "object_id")
super(GenericPolymorphicFormSetChild, self).__init__(*args, **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.
"""
exclude = list(self.exclude)
extra_exclude = kwargs.pop('extra_exclude', None)
extra_exclude = kwargs.pop("extra_exclude", None)
if extra_exclude:
exclude += list(extra_exclude)
@ -31,33 +38,52 @@ class GenericPolymorphicFormSetChild(PolymorphicFormSetChild):
opts = self.model._meta
ct_field = opts.get_field(self.ct_field)
if 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)
if (
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
exclude.extend([ct_field.name, fk_field.name])
kwargs['exclude'] = exclude
kwargs["exclude"] = exclude
return super(GenericPolymorphicFormSetChild, self).get_form(**kwargs)
class BaseGenericPolymorphicInlineFormSet(BaseGenericInlineFormSet, BasePolymorphicModelFormSet):
class BaseGenericPolymorphicInlineFormSet(
BaseGenericInlineFormSet, BasePolymorphicModelFormSet
):
"""
Polymorphic formset variation for inline generic formsets
"""
def generic_polymorphic_inlineformset_factory(model, formset_children, form=ModelForm,
formset=BaseGenericPolymorphicInlineFormSet,
ct_field="content_type", fk_field="object_id",
# Base form
# TODO: should these fields be removed in favor of creating
# the base form as a formset child too?
fields=None, exclude=None,
extra=1, can_order=False, 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):
def generic_polymorphic_inlineformset_factory(
model,
formset_children,
form=ModelForm,
formset=BaseGenericPolymorphicInlineFormSet,
ct_field="content_type",
fk_field="object_id",
# Base form
# TODO: should these fields be removed in favor of creating
# the base form as a formset child too?
fields=None,
exclude=None,
extra=1,
can_order=False,
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.
@ -70,22 +96,22 @@ def generic_polymorphic_inlineformset_factory(model, formset_children, form=Mode
:rtype: type
"""
kwargs = {
'model': model,
'form': form,
'formfield_callback': formfield_callback,
'formset': formset,
'ct_field': ct_field,
'fk_field': fk_field,
'extra': extra,
'can_delete': can_delete,
'can_order': can_order,
'fields': fields,
'exclude': exclude,
'min_num': min_num,
'max_num': max_num,
'validate_min': validate_min,
'validate_max': validate_max,
'for_concrete_model': for_concrete_model,
"model": model,
"form": form,
"formfield_callback": formfield_callback,
"formset": formset,
"ct_field": ct_field,
"fk_field": fk_field,
"extra": extra,
"can_delete": can_delete,
"can_order": can_order,
"fields": fields,
"exclude": exclude,
"min_num": min_num,
"max_num": max_num,
"validate_min": validate_min,
"validate_max": validate_max,
"for_concrete_model": for_concrete_model,
# 'localized_fields': localized_fields,
# 'labels': labels,
# 'help_texts': help_texts,
@ -97,12 +123,14 @@ def generic_polymorphic_inlineformset_factory(model, formset_children, form=Mode
child_kwargs = {
# 'exclude': exclude,
'ct_field': ct_field,
'fk_field': fk_field,
"ct_field": ct_field,
"fk_field": fk_field,
}
if child_form_kwargs:
child_kwargs.update(child_form_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

View File

@ -3,10 +3,18 @@ from collections import OrderedDict
from django import forms
from django.contrib.contenttypes.models import ContentType
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 polymorphic.models import PolymorphicModel
from .utils import add_media
@ -20,9 +28,19 @@ class PolymorphicFormSetChild(object):
Provide this information in the :func:'polymorphic_inlineformset_factory' construction.
"""
def __init__(self, model, form=ModelForm, fields=None, exclude=None,
formfield_callback=None, widgets=None, localized_fields=None,
labels=None, help_texts=None, error_messages=None):
def __init__(
self,
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
@ -59,20 +77,20 @@ class PolymorphicFormSetChild(object):
# we allow to define things like 'extra_...' fields that are amended to the current child settings.
exclude = list(self.exclude)
extra_exclude = kwargs.pop('extra_exclude', None)
extra_exclude = kwargs.pop("extra_exclude", None)
if extra_exclude:
exclude += list(extra_exclude)
defaults = {
'form': self._form_base,
'formfield_callback': self.formfield_callback,
'fields': self.fields,
'exclude': exclude,
"form": self._form_base,
"formfield_callback": self.formfield_callback,
"fields": self.fields,
"exclude": exclude,
# 'for_concrete_model': for_concrete_model,
'localized_fields': self.localized_fields,
'labels': self.labels,
'help_texts': self.help_texts,
'error_messages': self.error_messages,
"localized_fields": self.localized_fields,
"labels": self.labels,
"help_texts": self.help_texts,
"error_messages": self.error_messages,
# 'field_classes': field_classes,
}
defaults.update(kwargs)
@ -125,61 +143,71 @@ class BasePolymorphicModelFormSet(BaseModelFormSet):
pk_field = self.model._meta.pk
to_python = self._get_to_python(pk_field)
pk = to_python(pk)
kwargs['instance'] = self._existing_object(pk)
if i < self.initial_form_count() and 'instance' not in kwargs:
kwargs['instance'] = self.get_queryset()[i]
kwargs["instance"] = self._existing_object(pk)
if i < self.initial_form_count() and "instance" not in kwargs:
kwargs["instance"] = self.get_queryset()[i]
if i >= self.initial_form_count() and self.initial_extra:
# Set initial values for extra forms
try:
kwargs['initial'] = self.initial_extra[i - self.initial_form_count()]
kwargs["initial"] = self.initial_extra[i - self.initial_form_count()]
except IndexError:
pass
# BaseFormSet logic, with custom formset_class
defaults = {
'auto_id': self.auto_id,
'prefix': self.add_prefix(i),
'error_class': self.error_class,
"auto_id": self.auto_id,
"prefix": self.add_prefix(i),
"error_class": self.error_class,
}
if self.is_bound:
defaults['data'] = self.data
defaults['files'] = self.files
if self.initial and 'initial' not in kwargs:
defaults["data"] = self.data
defaults["files"] = self.files
if self.initial and "initial" not in kwargs:
try:
defaults['initial'] = self.initial[i]
defaults["initial"] = self.initial[i]
except IndexError:
pass
# Allow extra forms to be empty, unless they're part of
# the minimum forms.
if i >= self.initial_form_count() and i >= self.min_num:
defaults['empty_permitted'] = True
defaults['use_required_attribute'] = False
defaults["empty_permitted"] = True
defaults["use_required_attribute"] = False
defaults.update(kwargs)
# Need to find the model that will be displayed in this form.
# Hence, peeking in the self.queryset_data beforehand.
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
model = defaults['instance'].get_real_instance_class() # allow proxy models
model = defaults[
"instance"
].get_real_instance_class() # allow proxy models
else:
# Extra or empty form, use the provided type.
# Note this completely tru
prefix = defaults['prefix']
prefix = defaults["prefix"]
try:
ct_id = int(self.data["{0}-polymorphic_ctype".format(prefix)])
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()
if model not in self.child_forms:
# 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:
if 'instance' in defaults:
model = defaults['instance'].get_real_instance_class() # allow proxy models
elif 'polymorphic_ctype' in defaults.get('initial', {}):
model = defaults['initial']['polymorphic_ctype'].model_class()
if "instance" in defaults:
model = defaults[
"instance"
].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):
model = self.queryset_data[i].__class__
else:
@ -196,9 +224,13 @@ class BasePolymorphicModelFormSet(BaseModelFormSet):
def add_fields(self, form, index):
"""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.
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)
def get_form_class(self, model):
@ -206,7 +238,9 @@ class BasePolymorphicModelFormSet(BaseModelFormSet):
Return the proper form class for the given model.
"""
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):
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, "
"but no form class is registered to display it.".format(
self.__class__.__name__, model.__name__
))
)
)
def is_multipart(self):
"""
@ -247,7 +282,7 @@ class BasePolymorphicModelFormSet(BaseModelFormSet):
form = form_class(
auto_id=self.auto_id,
prefix=self.add_prefix('__prefix__'),
prefix=self.add_prefix("__prefix__"),
empty_permitted=True,
use_required_attribute=False,
**kwargs
@ -259,20 +294,37 @@ class BasePolymorphicModelFormSet(BaseModelFormSet):
@property
def empty_form(self):
# 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,
formset=BasePolymorphicModelFormSet,
# Base field
# TODO: should these fields be removed in favor of creating
# the base form as a formset child too?
form=ModelForm,
fields=None, exclude=None, extra=1, can_order=False,
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):
def polymorphic_modelformset_factory(
model,
formset_children,
formset=BasePolymorphicModelFormSet,
# Base field
# TODO: should these fields be removed in favor of creating
# the base form as a formset child too?
form=ModelForm,
fields=None,
exclude=None,
extra=1,
can_order=False,
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.
@ -285,25 +337,25 @@ def polymorphic_modelformset_factory(model, formset_children,
:rtype: type
"""
kwargs = {
'model': model,
'form': form,
'formfield_callback': formfield_callback,
'formset': formset,
'extra': extra,
'can_delete': can_delete,
'can_order': can_order,
'fields': fields,
'exclude': exclude,
'min_num': min_num,
'max_num': max_num,
'widgets': widgets,
'validate_min': validate_min,
'validate_max': validate_max,
'localized_fields': localized_fields,
'labels': labels,
'help_texts': help_texts,
'error_messages': error_messages,
'field_classes': field_classes,
"model": model,
"form": form,
"formfield_callback": formfield_callback,
"formset": formset,
"extra": extra,
"can_delete": can_delete,
"can_order": can_order,
"fields": fields,
"exclude": exclude,
"min_num": min_num,
"max_num": max_num,
"widgets": widgets,
"validate_min": validate_min,
"validate_max": validate_max,
"localized_fields": localized_fields,
"labels": labels,
"help_texts": help_texts,
"error_messages": error_messages,
"field_classes": field_classes,
}
FormSet = modelformset_factory(**kwargs)
@ -313,7 +365,9 @@ def polymorphic_modelformset_factory(model, formset_children,
if 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
@ -326,17 +380,34 @@ class BasePolymorphicInlineFormSet(BaseInlineFormSet, BasePolymorphicModelFormSe
return super(BasePolymorphicInlineFormSet, self)._construct_form(i, **kwargs)
def polymorphic_inlineformset_factory(parent_model, model, formset_children,
formset=BasePolymorphicInlineFormSet, fk_name=None,
# Base field
# TODO: should these fields be removed in favor of creating
# the base form as a formset child too?
form=ModelForm,
fields=None, exclude=None, extra=1, can_order=False,
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):
def polymorphic_inlineformset_factory(
parent_model,
model,
formset_children,
formset=BasePolymorphicInlineFormSet,
fk_name=None,
# Base field
# TODO: should these fields be removed in favor of creating
# the base form as a formset child too?
form=ModelForm,
fields=None,
exclude=None,
extra=1,
can_order=False,
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.
@ -349,27 +420,27 @@ def polymorphic_inlineformset_factory(parent_model, model, formset_children,
:rtype: type
"""
kwargs = {
'parent_model': parent_model,
'model': model,
'form': form,
'formfield_callback': formfield_callback,
'formset': formset,
'fk_name': fk_name,
'extra': extra,
'can_delete': can_delete,
'can_order': can_order,
'fields': fields,
'exclude': exclude,
'min_num': min_num,
'max_num': max_num,
'widgets': widgets,
'validate_min': validate_min,
'validate_max': validate_max,
'localized_fields': localized_fields,
'labels': labels,
'help_texts': help_texts,
'error_messages': error_messages,
'field_classes': field_classes,
"parent_model": parent_model,
"model": model,
"form": form,
"formfield_callback": formfield_callback,
"formset": formset,
"fk_name": fk_name,
"extra": extra,
"can_delete": can_delete,
"can_order": can_order,
"fields": fields,
"exclude": exclude,
"min_num": min_num,
"max_num": max_num,
"widgets": widgets,
"validate_min": validate_min,
"validate_max": validate_max,
"localized_fields": localized_fields,
"labels": labels,
"help_texts": help_texts,
"error_messages": error_messages,
"field_classes": field_classes,
}
FormSet = inlineformset_factory(**kwargs)
@ -379,5 +450,7 @@ def polymorphic_inlineformset_factory(parent_model, model, formset_children,
if 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

View File

@ -5,14 +5,11 @@ The manager class for use in the models.
from __future__ import unicode_literals
from django.db import models
from polymorphic.compat import python_2_unicode_compatible
from polymorphic.query import PolymorphicQuerySet
__all__ = (
'PolymorphicManager',
'PolymorphicQuerySet',
)
__all__ = ("PolymorphicManager", "PolymorphicQuerySet")
@python_2_unicode_compatible
@ -23,12 +20,17 @@ class PolymorphicManager(models.Manager):
Usually not explicitly needed, except if a custom manager or
a custom queryset class is to be used.
"""
queryset_class = PolymorphicQuerySet
@classmethod
def from_queryset(cls, queryset_class, class_name=None):
manager = super(PolymorphicManager, cls).from_queryset(queryset_class, class_name=class_name)
manager.queryset_class = queryset_class # also set our version, Django uses _queryset_class
manager = super(PolymorphicManager, cls).from_queryset(
queryset_class, class_name=class_name
)
manager.queryset_class = (
queryset_class
) # also set our version, Django uses _queryset_class
return manager
def get_queryset(self):
@ -38,7 +40,10 @@ class PolymorphicManager(models.Manager):
return qs
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
def non_polymorphic(self):

View File

@ -6,10 +6,14 @@ from __future__ import absolute_import
from django.contrib.contenttypes.models import ContentType
from django.db import models
from django.db.models.fields.related import ReverseOneToOneDescriptor, ForwardManyToOneDescriptor
from django.db.models.fields.related import (
ForwardManyToOneDescriptor,
ReverseOneToOneDescriptor,
)
from django.db.utils import DEFAULT_DB_ALIAS
from polymorphic.compat import with_metaclass
from .base import PolymorphicModelBase
from .managers import PolymorphicManager
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)
#: The model field that stores the :class:`~django.contrib.contenttypes.models.ContentType` reference to the actual class.
polymorphic_ctype = models.ForeignKey(
ContentType, null=True, editable=False, on_delete=models.CASCADE,
related_name='polymorphic_%(app_label)s.%(class)s_set+'
ContentType,
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
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.
# 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
# (used by PolymorphicQuerySet._get_real_instances)
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
def save(self, *args, **kwargs):
"""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)
return super(PolymorphicModel, self).save(*args, **kwargs)
save.alters_data = True
def get_real_instance_class(self):
@ -92,28 +103,40 @@ class PolymorphicModel(with_metaclass(PolymorphicModelBase, models.Model)):
determined using this method.
"""
if self.polymorphic_ctype_id is None:
raise PolymorphicTypeUndefined((
"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, "
"make sure the `polymorphic_ctype_id` field points to the ContentType ID of the model subclass."
).format(self.__class__.__name__, self.pk))
raise PolymorphicTypeUndefined(
(
"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, "
"make sure the `polymorphic_ctype_id` field points to the ContentType ID of the model subclass."
).format(self.__class__.__name__, self.pk)
)
# the following line would be the easiest way to do this, but it produces sql queries
# return self.polymorphic_ctype.model_class()
# so we use the following version, which uses the ContentType manager cache.
# 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.
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
# issues missing with the ContentType models.
if model is not None \
and not issubclass(model, self.__class__) \
and (self.__class__._meta.proxy_for_model is None \
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,
))
if (
model is not None
and not issubclass(model, self.__class__)
and (
self.__class__._meta.proxy_for_model is None
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
@ -121,13 +144,21 @@ class PolymorphicModel(with_metaclass(PolymorphicModelBase, models.Model)):
model_class = self.get_real_instance_class()
if model_class is 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):
model_class = self.get_real_instance_class()
if model_class is 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):
"""
@ -147,7 +178,7 @@ class PolymorphicModel(with_metaclass(PolymorphicModelBase, models.Model)):
return self
return real_model.objects.db_manager(self._state.db).get(pk=self.pk)
def __init__(self, * args, ** kwargs):
def __init__(self, *args, **kwargs):
"""Replace Django's inheritance accessor member functions for our model
(self.__class__) with our own versions.
We monkey patch them until a patch can be added to Django
@ -166,7 +197,7 @@ class PolymorphicModel(with_metaclass(PolymorphicModelBase, models.Model)):
But they should not. So we replace them with our own accessors that use
our appropriate base_objects manager.
"""
super(PolymorphicModel, self).__init__(*args, ** kwargs)
super(PolymorphicModel, self).__init__(*args, **kwargs)
if self.__class__.polymorphic_super_sub_accessors_replaced:
return
@ -176,15 +207,25 @@ class PolymorphicModel(with_metaclass(PolymorphicModelBase, models.Model)):
def accessor_function(self):
attr = model._base_objects.get(pk=self.pk)
return attr
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():
# Here be dragons.
orig_accessor = getattr(self.__class__, name, None)
if issubclass(type(orig_accessor), (ReverseOneToOneDescriptor, ForwardManyToOneDescriptor)):
setattr(self.__class__, name, property(create_accessor_function_for_model(model, name)))
if issubclass(
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):
"""helper function for __init__:
@ -194,31 +235,45 @@ class PolymorphicModel(with_metaclass(PolymorphicModelBase, models.Model)):
result[field_name] = model
def add_model_if_regular(model, field_name, result):
if (issubclass(model, models.Model)
and model != models.Model
and model != self.__class__
and model != PolymorphicModel):
if (
issubclass(model, models.Model)
and model != models.Model
and model != self.__class__
and model != PolymorphicModel
):
add_model(model, field_name, result)
def add_all_super_models(model, result):
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
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_all_super_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
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
for (
sub_cls
) in super_cls.__subclasses__(): # go through all subclasses of 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
if super_to_sub_related_field.related_name is None:
# if related name is None the related field is the name of the subclass
to_subclass_fieldname = sub_cls.__name__.lower()
else:
# 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)

View File

@ -12,8 +12,12 @@ from django.db.models import FieldDoesNotExist
from django.db.models.query import ModelIterable, Q, QuerySet
from . import compat
from .query_translate import translate_polymorphic_filter_definitions_in_kwargs, translate_polymorphic_filter_definitions_in_args
from .query_translate import translate_polymorphic_field_path, translate_polymorphic_Q_object
from .query_translate import (
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
# by the polymorphic queryset.iterator() implementation
@ -72,7 +76,7 @@ def transmogrify(cls, obj):
"""
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.
new = obj
new.__class__ = cls
@ -87,6 +91,7 @@ def transmogrify(cls, obj):
###################################################################################
# PolymorphicQuerySet
class PolymorphicQuerySet(QuerySet):
"""
QuerySet for PolymorphicModel
@ -115,14 +120,17 @@ class PolymorphicQuerySet(QuerySet):
new.polymorphic_disabled = self.polymorphic_disabled
new.polymorphic_deferred_loading = (
copy.copy(self.polymorphic_deferred_loading[0]),
self.polymorphic_deferred_loading[1])
self.polymorphic_deferred_loading[1],
)
return new
def as_manager(cls):
from .managers import PolymorphicManager
manager = PolymorphicManager.from_queryset(cls)()
manager._built_with_as_manager = True
return manager
as_manager.queryset_only = True
as_manager = classmethod(as_manager)
@ -154,15 +162,22 @@ class PolymorphicQuerySet(QuerySet):
def _filter_or_exclude(self, negate, *args, **kwargs):
# 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
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)
q_objects = translate_polymorphic_filter_definitions_in_args(
self.model, args, using=self.db
) # 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):
"""translate the field paths in the args, then call vanilla order_by."""
field_names = [
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
]
return super(PolymorphicQuerySet, self).order_by(*field_names)
@ -213,8 +228,8 @@ class PolymorphicQuerySet(QuerySet):
"""
existing, defer = self.polymorphic_deferred_loading
field_names = set(field_names)
if 'pk' in field_names:
field_names.remove('pk')
if "pk" in field_names:
field_names.remove("pk")
field_names.add(self.model._meta.pk.name)
if defer:
@ -228,14 +243,14 @@ class PolymorphicQuerySet(QuerySet):
def _process_aggregate_args(self, args, kwargs):
"""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)"""
___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):
# The field on which the aggregate operates is
# stored inside a complex query expression.
if isinstance(a, Q):
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():
if source_expression is not None:
patch_lookup(source_expression)
@ -246,6 +261,7 @@ class PolymorphicQuerySet(QuerySet):
""" *args might be complex expressions too in django 1.8 so
the testing for a '___' is rather complex on this one """
if isinstance(a, Q):
def tree_node_test___lookup(my_model, node):
" process all children of this Q node "
for i in range(len(node.children)):
@ -253,17 +269,17 @@ class PolymorphicQuerySet(QuerySet):
if type(child) == tuple:
# 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:
# this Q object child is another Q object, recursively process this as well
tree_node_test___lookup(my_model, child)
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():
test___lookup(source_expression)
else:
assert '___' not in a.name, ___lookup_assert_msg
assert "___" not in a.name, ___lookup_assert_msg
for a in args:
test___lookup(a)
@ -325,7 +341,7 @@ class PolymorphicQuerySet(QuerySet):
Finally we re-sort the resulting objects into the correct order and
return them as a list.
"""
resultlist = [] # polymorphic list of result-objects
resultlist = [] # polymorphic list of result-objects
# dict contains one entry per unique model type occurring in result,
# in the format idlist_per_model[modelclass]=[list-of-object-ids]
@ -344,8 +360,12 @@ class PolymorphicQuerySet(QuerySet):
# - 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"
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_concrete_model_class_id = content_type_manager.get_for_model(self.model, for_concrete_model=True).pk
self_model_class_id = content_type_manager.get_for_model(
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):
@ -354,7 +374,9 @@ class PolymorphicQuerySet(QuerySet):
resultlist.append(base_object)
else:
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:
# Dealing with a stale content type
@ -365,9 +387,15 @@ class PolymorphicQuerySet(QuerySet):
resultlist.append(transmogrify(real_concrete_class, base_object))
else:
# 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()
idlist_per_model[real_concrete_class].append(getattr(base_object, pk_name))
indexlist_per_model[real_concrete_class].append((i, len(resultlist)))
real_concrete_class = content_type_manager.get_for_id(
real_concrete_class_id
).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)
# 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
for real_concrete_class, idlist in idlist_per_model.items():
indices = indexlist_per_model[real_concrete_class]
real_objects = real_concrete_class._base_objects.db_manager(self.db).filter(**{
('%s__in' % pk_name): idlist,
})
real_objects.query.select_related = self.query.select_related # copy select related configuration to new qs
real_objects = real_concrete_class._base_objects.db_manager(self.db).filter(
**{("%s__in" % pk_name): idlist}
)
real_objects.query.select_related = (
self.query.select_related
) # copy select related configuration to new qs
# Copy deferred fields configuration to the new queryset
deferred_loading_fields = []
@ -388,14 +418,15 @@ class PolymorphicQuerySet(QuerySet):
for field in existing_fields:
try:
translated_field_name = translate_polymorphic_field_path(
real_concrete_class, field)
real_concrete_class, field
)
except AssertionError:
if '___' in field:
if "___" in field:
# The originally passed argument to .defer() or .only()
# was in the form Model2B___field2, where Model2B is
# now a superclass of real_concrete_class. Thus it's
# 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.
# Ignore deferred fields that don't exist in this subclass type.
@ -407,7 +438,10 @@ class PolymorphicQuerySet(QuerySet):
raise
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 = {
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)
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:
real_object.polymorphic_annotate_names = annotate_names
# set polymorphic_extra_select_names in all objects (currently just used for debugging/printing)
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:
real_object.polymorphic_extra_select_names = extra_select_names
@ -460,15 +498,14 @@ class PolymorphicQuerySet(QuerySet):
def __repr__(self, *args, **kwargs):
if self.model.polymorphic_query_multiline_output:
result = [repr(o) for o in self.all()]
return '[ ' + ',\n '.join(result) + ' ]'
return "[ " + ",\n ".join(result) + " ]"
else:
return super(PolymorphicQuerySet, self).__repr__(*args, **kwargs)
class _p_list_class(list):
def __repr__(self, *args, **kwargs):
result = [repr(o) for o in self]
return '[ ' + ',\n '.join(result) + ' ]'
return "[ " + ",\n ".join(result) + " ]"
def get_real_instances(self, base_result_objects=None):
"""

View File

@ -15,18 +15,19 @@ from django.db.models import Q
from django.db.models.fields.related import ForeignObjectRel, RelatedField
from django.db.utils import DEFAULT_DB_ALIAS
###################################################################################
# PolymorphicQuerySet support functions
# These functions implement the additional filter- and Q-object functionality.
# They form a kind of small framework for easily adding more
# functionality to filters and Q objects.
# Probably a more general queryset enhancement class could be made out of them.
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()
@ -43,21 +44,25 @@ def translate_polymorphic_filter_definitions_in_kwargs(queryset_model, kwargs, u
additional_args = []
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:
# replace kwargs element
del(kwargs[field_path])
del kwargs[field_path]
kwargs[new_expr[0]] = new_expr[1]
elif isinstance(new_expr, models.Q):
del(kwargs[field_path])
del kwargs[field_path]
additional_args.append(new_expr)
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):
" process all children of this Q node "
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:
# this Q object child is a tuple => a kwarg like Q( instance_of=ModelB )
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:
node.children[i] = new_expr
else:
@ -79,7 +86,9 @@ def translate_polymorphic_Q_object(queryset_model, potential_q_object, using=DEF
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()
@ -90,10 +99,15 @@ def translate_polymorphic_filter_definitions_in_args(queryset_model, args, using
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
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,
# 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)
elif field_path == 'not_instance_of':
elif field_path == "not_instance_of":
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
# 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):
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:
return field_path
assert classname, 'PolymorphicModel: %s: bad field specification' % field_path
assert classname, "PolymorphicModel: %s: bad field specification" % field_path
negated = False
if classname[0] == '-':
if classname[0] == "-":
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
appname, sep, classname = classname.partition('__')
appname, sep, classname = classname.partition("__")
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):
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)
else:
@ -171,18 +194,21 @@ def translate_polymorphic_field_path(queryset_model, field_path):
submodels = _get_all_sub_models(queryset_model)
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)
if negated:
newpath = '-'
newpath = "-"
else:
newpath = ''
newpath = ""
newpath += basepath
if basepath:
newpath += '__'
newpath += "__"
newpath += pure_field_path
return newpath
@ -199,11 +225,13 @@ def _get_all_sub_models(base_model):
# model name is occurring twice in submodel inheritance tree => Error
if model.__name__ in result and model != result[model.__name__]:
raise FieldError(
'PolymorphicModel: model name alone is ambiguous: %s.%s and %s.%s match!\n'
'In this case, please use the syntax: applabel__ModelName___field' % (
model._meta.app_label, model.__name__,
"PolymorphicModel: model name alone is ambiguous: %s.%s and %s.%s match!\n"
"In this case, please use the syntax: applabel__ModelName___field"
% (
model._meta.app_label,
model.__name__,
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:
return _get_query_related_name(myclass)
else:
return path + '__' + _get_query_related_name(myclass)
return ''
return path + "__" + _get_query_related_name(myclass)
return ""
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)):
from .models import PolymorphicModel
if issubclass(modellist, PolymorphicModel):
modellist = [modellist]
else:
raise TypeError(
'PolymorphicModel: instance_of expects a list of (polymorphic) '
'models or a single (polymorphic) model'
"PolymorphicModel: instance_of expects a list of (polymorphic) "
"models or a single (polymorphic) model"
)
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):
contenttype_ids = set()
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)
subclasses = model.__subclasses__()
if subclasses:

View File

@ -6,15 +6,16 @@ from django.db import models
from . import compat
from .compat import python_2_unicode_compatible
RE_DEFERRED = re.compile('_Deferred_.*')
RE_DEFERRED = re.compile("_Deferred_.*")
@python_2_unicode_compatible
class ShowFieldBase(object):
""" 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_content = False
@ -32,24 +33,24 @@ class ShowFieldBase(object):
"helper for __unicode__"
content = getattr(self, field_name)
if self.polymorphic_showfield_old_format:
out = ': '
out = ": "
else:
out = ' '
out = " "
if issubclass(field_type, models.ForeignKey):
if content is None:
out += 'None'
out += "None"
else:
out += content.__class__.__name__
elif issubclass(field_type, models.ManyToManyField):
out += '%d' % content.count()
out += "%d" % content.count()
elif isinstance(content, compat.integer_types):
out += str(content)
elif content is None:
out += 'None'
out += "None"
else:
txt = str(content)
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 + '"'
return out
@ -57,7 +58,10 @@ class ShowFieldBase(object):
"helper for __unicode__"
done_fields = set()
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
if field.name in done_fields:
continue # work around django diamond inheritance problem
@ -66,78 +70,90 @@ class ShowFieldBase(object):
out = field.name
# 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:
out += ' ' + str(getattr(self, field.name))
if (
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.)
else:
if self.polymorphic_showfield_type:
out += ' (' + type(field).__name__
out += " (" + type(field).__name__
if field.primary_key:
out += '/pk'
out += ')'
out += "/pk"
out += ")"
if self.polymorphic_showfield_content:
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):
"helper for __unicode__"
parts.append((True, '- ' + title, ':'))
parts.append((True, "- " + title, ":"))
for field_name in field_list:
out = field_name
content = getattr(self, field_name)
if self.polymorphic_showfield_type:
out += ' (' + type(content).__name__ + ')'
out += " (" + type(content).__name__ + ")"
if self.polymorphic_showfield_content:
out += self._showfields_get_content(field_name)
parts.append((False, out, ','))
parts.append((False, out, ","))
def __str__(self):
# create list ("parts") containing one tuple for each title/field:
# ( bool: new section , item-text , separator to use after item )
# start with model name
parts = [(True, RE_DEFERRED.sub('', self.__class__.__name__), ':')]
parts = [(True, RE_DEFERRED.sub("", self.__class__.__name__), ":")]
# add all regular fields
self._showfields_add_regular_fields(parts)
# add annotate fields
if hasattr(self, 'polymorphic_annotate_names'):
self._showfields_add_dynamic_fields(self.polymorphic_annotate_names, 'Ann', parts)
if hasattr(self, "polymorphic_annotate_names"):
self._showfields_add_dynamic_fields(
self.polymorphic_annotate_names, "Ann", parts
)
# add extra() select fields
if hasattr(self, 'polymorphic_extra_select_names'):
self._showfields_add_dynamic_fields(self.polymorphic_extra_select_names, 'Extra', parts)
if hasattr(self, "polymorphic_extra_select_names"):
self._showfields_add_dynamic_fields(
self.polymorphic_extra_select_names, "Extra", parts
)
if self.polymorphic_showfield_deferred:
fields = self.get_deferred_fields()
if fields:
parts.append((False, "deferred[{0}]".format(",".join(sorted(fields))), ''))
parts.append(
(False, "deferred[{0}]".format(",".join(sorted(fields))), "")
)
# format result
indent = len(self.__class__.__name__) + 5
indentstr = ''.rjust(indent)
out = ''
indentstr = "".rjust(indent)
out = ""
xpos = 0
possible_line_break_pos = None
for i in range(len(parts)):
new_section, p, separator = parts[i]
final = (i == len(parts) - 1)
final = i == len(parts) - 1
if not final:
next_new_section, _, _ = parts[i + 1]
if (self.polymorphic_showfield_max_line_width
and xpos + len(p) > self.polymorphic_showfield_max_line_width
and possible_line_break_pos is not None):
if (
self.polymorphic_showfield_max_line_width
and xpos + len(p) > self.polymorphic_showfield_max_line_width
and possible_line_break_pos is not None
):
rest = out[possible_line_break_pos:]
out = out[:possible_line_break_pos]
out += '\n' + indentstr + rest
out += "\n" + indentstr + rest
xpos = indent + len(rest)
out += p
@ -147,26 +163,29 @@ class ShowFieldBase(object):
if not next_new_section:
out += separator
xpos += len(separator)
out += ' '
out += " "
xpos += 1
if not new_section:
possible_line_break_pos = len(out)
return '<' + out + '>'
return "<" + out + ">"
class ShowFieldType(ShowFieldBase):
""" model mixin that shows the object's class and it's field types """
polymorphic_showfield_type = True
class ShowFieldContent(ShowFieldBase):
""" model mixin that shows the object's class, it's fields and field contents """
polymorphic_showfield_content = True
class ShowFieldTypeAndContent(ShowFieldBase):
""" model mixin, like ShowFieldContent, but also show field types """
polymorphic_showfield_type = True
polymorphic_showfield_content = True

View File

@ -1,14 +1,14 @@
from django.template import Library, Node, TemplateSyntaxError
from polymorphic import compat
register = Library()
class BreadcrumbScope(Node):
def __init__(self, base_opts, nodelist):
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
@classmethod
def parse(cls, parser, token):
@ -16,15 +16,14 @@ class BreadcrumbScope(Node):
if len(bits) == 2:
(tagname, base_opts) = bits
base_opts = parser.compile_filter(base_opts)
nodelist = parser.parse(('endbreadcrumb_scope',))
nodelist = parser.parse(("endbreadcrumb_scope",))
parser.delete_first_token()
return cls(
base_opts=base_opts,
nodelist=nodelist
)
return cls(base_opts=base_opts, nodelist=nodelist)
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):
# app_label is really hard to overwrite in the standard Django ModelAdmin.
@ -34,8 +33,8 @@ class BreadcrumbScope(Node):
new_vars = {}
if base_opts and not isinstance(base_opts, compat.string_types):
new_vars = {
'app_label': base_opts.app_label, # What this is all about
'opts': base_opts,
"app_label": base_opts.app_label, # What this is all about
"opts": base_opts,
}
new_scope = context.push()

View File

@ -7,7 +7,6 @@ from django.utils.translation import ugettext
from polymorphic.formsets import BasePolymorphicModelFormSet
register = Library()
@ -19,7 +18,7 @@ def include_empty_form(formset):
for form in formset:
yield form
if hasattr(formset, 'empty_forms'):
if hasattr(formset, "empty_forms"):
# BasePolymorphicModelFormSet
for form in formset.empty_forms:
yield form
@ -40,24 +39,25 @@ def as_script_options(formset):
- ``add_text``
- ``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 = {
'prefix': formset.prefix,
'pkFieldName': formset.model._meta.pk.name,
'addText': getattr(formset, 'add_text', None) or ugettext('Add another %(verbose_name)s') % {
'verbose_name': capfirst(verbose_name),
},
'showAddButton': getattr(formset, 'show_add_button', True),
'deleteText': ugettext('Delete'),
"prefix": formset.prefix,
"pkFieldName": formset.model._meta.pk.name,
"addText": getattr(formset, "add_text", None)
or ugettext("Add another %(verbose_name)s")
% {"verbose_name": capfirst(verbose_name)},
"showAddButton": getattr(formset, "show_add_button", True),
"deleteText": ugettext("Delete"),
}
if isinstance(formset, BasePolymorphicModelFormSet):
# Allow to add different types
options['childTypes'] = [
options["childTypes"] = [
{
'name': force_text(model._meta.verbose_name),
'type': model._meta.model_name,
} for model in formset.child_forms.keys()
"name": force_text(model._meta.verbose_name),
"type": model._meta.model_name,
}
for model in formset.child_forms.keys()
]
return json.dumps(options)

View File

@ -12,6 +12,7 @@ class AdminTestCase(TestCase):
"""
Testing the admin site
"""
#: The model to test
model = None
#: The admin class to test
@ -20,7 +21,9 @@ class AdminTestCase(TestCase):
@classmethod
def setUpClass(cls):
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):
super(AdminTestCase, self).setUp()
@ -37,9 +40,11 @@ class AdminTestCase(TestCase):
def register(self, model):
"""Decorator, like admin.register()"""
def _dec(admin_class):
self.admin_register(model, admin_class)
return admin_class
return _dec
def admin_register(self, model, admin_site):
@ -48,9 +53,7 @@ class AdminTestCase(TestCase):
# Make sure the URLs are reachable by reverse()
clear_url_caches()
set_urlconf(tuple([
url('^tmp-admin/', self.admin_site.urls)
]))
set_urlconf(tuple([url("^tmp-admin/", self.admin_site.urls)]))
def get_admin_instance(self, model):
try:
@ -66,40 +69,42 @@ class AdminTestCase(TestCase):
def get_add_url(self, 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):
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):
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):
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):
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.
"""
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)
self.assertEqual(response.status_code, 200)
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.
"""
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)
self.assertFormSuccess(request.path, response)
return response
@ -109,7 +114,7 @@ class AdminTestCase(TestCase):
Make a direct "add" call to the admin page, circumvening login checks.
"""
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)
self.assertEqual(response.status_code, 200)
return response
@ -119,7 +124,9 @@ class AdminTestCase(TestCase):
Perform a GET request on the admin page
"""
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))
self.assertEqual(response.status_code, 200)
return response
@ -129,7 +136,9 @@ class AdminTestCase(TestCase):
Make a direct "add" call to the admin page, circumvening login checks.
"""
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))
self.assertFormSuccess(request.path, response)
return response
@ -139,7 +148,9 @@ class AdminTestCase(TestCase):
Perform a GET request on the admin page
"""
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))
self.assertEqual(response.status_code, 200)
return response
@ -149,7 +160,9 @@ class AdminTestCase(TestCase):
Perform a GET request on the admin delete page
"""
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))
self.assertEqual(response.status_code, 200)
return response
@ -159,12 +172,16 @@ class AdminTestCase(TestCase):
Make a direct "add" call to the admin page, circumvening login checks.
"""
if not extra:
extra = {'data': {'post': 'yes'}}
extra = {"data": {"post": "yes"}}
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))
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
def create_admin_request(self, method, url, data=None, **extra):
@ -174,8 +191,8 @@ class AdminTestCase(TestCase):
factory_method = getattr(RequestFactory(), method)
if data is not None:
if method != 'get':
data['csrfmiddlewaretoken'] = 'foo'
if method != "get":
data["csrfmiddlewaretoken"] = "foo"
dummy_request = factory_method(url, data=data)
dummy_request.user = self.admin_user
@ -185,7 +202,7 @@ class AdminTestCase(TestCase):
# data = base_data
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
# Add properties which middleware would typically do
@ -201,14 +218,21 @@ class AdminTestCase(TestCase):
self.assertIn(response.status_code, [200, 302])
if response.status_code != 302:
context_data = response.context_data
if 'errors' in context_data:
errors = response.context_data['errors']
elif 'form' in context_data:
errors = context_data['form'].errors
if "errors" in context_data:
errors = response.context_data["errors"]
elif "form" in context_data:
errors = context_data["form"].errors
else:
raise KeyError("Unknown field for errors in the TemplateResponse!")
self.assertEqual(response.status_code, 302,
"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))
self.assertEqual(
response.status_code,
302,
"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

View File

@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
import django
import uuid
import django
from django.contrib.contenttypes.models import ContentType
from django.db import models
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.models import PolymorphicModel
from polymorphic.query import PolymorphicQuerySet
from polymorphic.showfields import ShowFieldContent, ShowFieldType, ShowFieldTypeAndContent
from polymorphic.showfields import (
ShowFieldContent,
ShowFieldType,
ShowFieldTypeAndContent,
)
class PlainA(models.Model):
@ -59,17 +63,17 @@ class ModelExtraExternal(models.Model):
class ModelShow1(ShowFieldType, PolymorphicModel):
field1 = models.CharField(max_length=10)
m2m = models.ManyToManyField('self')
m2m = models.ManyToManyField("self")
class ModelShow2(ShowFieldContent, PolymorphicModel):
field1 = models.CharField(max_length=10)
m2m = models.ManyToManyField('self')
m2m = models.ManyToManyField("self")
class ModelShow3(ShowFieldTypeAndContent, PolymorphicModel):
field1 = models.CharField(max_length=10)
m2m = models.ManyToManyField('self')
m2m = models.ManyToManyField("self")
class ModelShow1_plain(PolymorphicModel):
@ -108,8 +112,10 @@ class Enhance_Inherit(Enhance_Base, Enhance_Plain):
class RelationBase(ShowFieldTypeAndContent, PolymorphicModel):
field_base = models.CharField(max_length=10)
fk = models.ForeignKey('self', on_delete=models.CASCADE, null=True, related_name='relationbase_set')
m2m = models.ManyToManyField('self')
fk = models.ForeignKey(
"self", on_delete=models.CASCADE, null=True, related_name="relationbase_set"
)
m2m = models.ManyToManyField("self")
class RelationA(RelationBase):
@ -143,21 +149,24 @@ class ModelUnderRelParent(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)
class MyManagerQuerySet(PolymorphicQuerySet):
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):
queryset_class = MyManagerQuerySet
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):
return self.all().my_queryset_foo()
@ -196,12 +205,15 @@ class MROBase2(MROBase1):
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()
class MRODerived(MROBase2, MROBase3):
if django.VERSION < (3, 0):
class Meta:
manager_inheritance_from_future = True
@ -212,18 +224,20 @@ class ParentModelWithManager(PolymorphicModel):
class ChildModelWithManager(PolymorphicModel):
# 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()
class PlainMyManagerQuerySet(QuerySet):
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):
def my_queryset_foo(self):
return self.get_queryset().my_queryset_foo()
@ -236,7 +250,11 @@ class PlainParentModelWithManager(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()
@ -270,14 +288,14 @@ class InitTestModel(ShowFieldType, PolymorphicModel):
bar = models.CharField(max_length=100)
def __init__(self, *args, **kwargs):
kwargs['bar'] = self.x()
kwargs["bar"] = self.x()
super(InitTestModel, self).__init__(*args, **kwargs)
class InitTestModelSubclass(InitTestModel):
def x(self):
return 'XYZ'
return "XYZ"
# models from github issue
@ -319,6 +337,7 @@ class UUIDPlainB(UUIDPlainA):
class UUIDPlainC(UUIDPlainB):
field3 = models.CharField(max_length=10)
# base -> proxy
@ -327,7 +346,6 @@ class ProxyBase(PolymorphicModel):
class ProxyChild(ProxyBase):
class Meta:
proxy = True
@ -335,6 +353,7 @@ class ProxyChild(ProxyBase):
class NonProxyChild(ProxyBase):
name = models.CharField(max_length=10)
# base -> proxy -> real models
@ -343,7 +362,6 @@ class ProxiedBase(ShowFieldTypeAndContent, PolymorphicModel):
class ProxyModelBase(ProxiedBase):
class Meta:
proxy = True
@ -364,14 +382,20 @@ class ProxyModelB(ProxyModelBase):
# with related field 'ContentType.relatednameclash_set'." (reported by Andrew Ingram)
# fixed with related_name
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 TestParentLinkAndRelatedName(ModelShow1_plain):
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 Meta:
swappable = 'POLYMORPHIC_TEST_SWAPPABLE'
swappable = "POLYMORPHIC_TEST_SWAPPABLE"
class SwappedModel(AbstractModel):
@ -410,7 +434,9 @@ class InlineParent(models.Model):
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)
@ -434,13 +460,11 @@ class Duck(PolymorphicModel):
class RedheadDuck(Duck):
class Meta:
proxy = True
class RubberDuck(Duck):
class Meta:
proxy = True
@ -454,22 +478,22 @@ class MultiTableDerived(MultiTableBase):
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):
abstract_field = models.CharField(max_length=10, default='test_af')
abstract_field = models.CharField(max_length=10, default="test_af")
class Meta:
abstract = True
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):
base_field = models.CharField(max_length=10, default='test_bf')
base_field = models.CharField(max_length=10, default="test_bf")
class SubclassSelectorProxyModel(SubclassSelectorProxyBaseModel):
@ -478,4 +502,4 @@ class SubclassSelectorProxyModel(SubclassSelectorProxyBaseModel):
class SubclassSelectorProxyConcreteModel(SubclassSelectorProxyModel):
concrete_field = models.CharField(max_length=10, default='test_cf')
concrete_field = models.CharField(max_length=10, default="test_cf")

View File

@ -2,18 +2,31 @@ from django.contrib import admin
from django.contrib.contenttypes.models import ContentType
from django.utils.html import escape
from polymorphic.admin import PolymorphicChildModelAdmin, PolymorphicChildModelFilter, PolymorphicInlineSupportMixin, \
PolymorphicParentModelAdmin, StackedPolymorphicInline
from polymorphic.admin import (
PolymorphicChildModelAdmin,
PolymorphicChildModelFilter,
PolymorphicInlineSupportMixin,
PolymorphicParentModelAdmin,
StackedPolymorphicInline,
)
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):
def test_admin_registration(self):
"""
Test how the registration works
"""
@self.register(Model2A)
class Model2Admin(PolymorphicParentModelAdmin):
base_model = Model2A
@ -25,48 +38,42 @@ class PolymorphicAdminTests(AdminTestCase):
@self.register(Model2D)
class Model2ChildAdmin(PolymorphicChildModelAdmin):
base_model = Model2A
base_fieldsets = (
("Base fields", {
'fields': ('field1',)
}),
)
base_fieldsets = (("Base fields", {"fields": ("field1",)}),)
# -- add page
ct_id = ContentType.objects.get_for_model(Model2D).pk
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_post_add(Model2A, {
'field1': 'A',
'field2': 'B',
'field3': 'C',
'field4': 'D'
}, qs='?ct_id={}'.format(ct_id))
self.admin_post_add(
Model2A,
{"field1": "A", "field2": "B", "field3": "C", "field4": "D"},
qs="?ct_id={}".format(ct_id),
)
d_obj = Model2A.objects.all()[0]
self.assertEqual(d_obj.__class__, Model2D)
self.assertEqual(d_obj.field1, 'A')
self.assertEqual(d_obj.field2, 'B')
self.assertEqual(d_obj.field1, "A")
self.assertEqual(d_obj.field2, "B")
# -- list page
self.admin_get_changelist(Model2A) # asserts 200
# -- edit
response = self.admin_get_change(Model2A, d_obj.pk)
self.assertContains(response, 'field4')
self.admin_post_change(Model2A, d_obj.pk, {
'field1': 'A2',
'field2': 'B2',
'field3': 'C2',
'field4': 'D2'
})
self.assertContains(response, "field4")
self.admin_post_change(
Model2A,
d_obj.pk,
{"field1": "A2", "field2": "B2", "field3": "C2", "field4": "D2"},
)
d_obj.refresh_from_db()
self.assertEqual(d_obj.field1, 'A2')
self.assertEqual(d_obj.field2, 'B2')
self.assertEqual(d_obj.field3, 'C2')
self.assertEqual(d_obj.field4, 'D2')
self.assertEqual(d_obj.field1, "A2")
self.assertEqual(d_obj.field2, "B2")
self.assertEqual(d_obj.field3, "C2")
self.assertEqual(d_obj.field4, "D2")
# -- history
self.admin_get_history(Model2A, d_obj.pk)
@ -80,6 +87,7 @@ class PolymorphicAdminTests(AdminTestCase):
"""
Test the registration of inline models.
"""
class InlineModelAChild(StackedPolymorphicInline.Child):
model = InlineModelA
@ -88,43 +96,46 @@ class PolymorphicAdminTests(AdminTestCase):
class Inline(StackedPolymorphicInline):
model = InlineModelA
child_inlines = (
InlineModelAChild,
InlineModelBChild,
)
child_inlines = (InlineModelAChild, InlineModelBChild)
@self.register(InlineParent)
class InlineParentAdmin(PolymorphicInlineSupportMixin, admin.ModelAdmin):
inlines = (Inline,)
parent = InlineParent.objects.create(title='FOO')
parent = InlineParent.objects.create(title="FOO")
self.assertEqual(parent.inline_children.count(), 0)
# -- get edit page
response = self.admin_get_change(InlineParent, parent.pk)
# 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": "inlinemodelb"'))
# -- post edit page
self.admin_post_change(InlineParent, parent.pk, {
'title': 'FOO2',
'inline_children-INITIAL_FORMS': 0,
'inline_children-TOTAL_FORMS': 1,
'inline_children-MIN_NUM_FORMS': 0,
'inline_children-MAX_NUM_FORMS': 1000,
'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',
})
self.admin_post_change(
InlineParent,
parent.pk,
{
"title": "FOO2",
"inline_children-INITIAL_FORMS": 0,
"inline_children-TOTAL_FORMS": 1,
"inline_children-MIN_NUM_FORMS": 0,
"inline_children-MAX_NUM_FORMS": 1000,
"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()
self.assertEqual(parent.title, 'FOO2')
self.assertEqual(parent.title, "FOO2")
self.assertEqual(parent.inline_children.count(), 1)
child = parent.inline_children.all()[0]
self.assertEqual(child.__class__, InlineModelB)
self.assertEqual(child.field1, 'A2')
self.assertEqual(child.field2, 'B2')
self.assertEqual(child.field1, "A2")
self.assertEqual(child.field2, "B2")

View File

@ -1,10 +1,7 @@
from unittest import TestCase
from polymorphic.contrib.guardian import get_polymorphic_base_content_type
from polymorphic.tests.models import (
Model2D,
PlainC,
)
from polymorphic.tests.models import Model2D, PlainC
class ContribTests(TestCase):
@ -12,20 +9,19 @@ class ContribTests(TestCase):
The test suite
"""
def test_contrib_guardian(self):
# Regular Django inheritance should return the child model content type.
obj = PlainC()
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)
self.assertEqual(ctype.name, 'plain c')
self.assertEqual(ctype.name, "plain c")
# Polymorphic inheritance should return the parent model content type.
obj = Model2D()
ctype = get_polymorphic_base_content_type(obj)
self.assertEqual(ctype.name, 'model2a')
self.assertEqual(ctype.name, "model2a")
ctype = get_polymorphic_base_content_type(Model2D)
self.assertEqual(ctype.name, 'model2a')
self.assertEqual(ctype.name, "model2a")

View File

@ -23,29 +23,29 @@ class MultipleDatabasesTests(TestCase):
multi_db = True
def test_save_to_non_default_database(self):
Model2A.objects.db_manager('secondary').create(field1='A1')
Model2C(field1='C1', field2='C2', field3='C3').save(using='secondary')
Model2B.objects.create(field1='B1', field2='B2')
Model2D(field1='D1', field2='D2', field3='D3', field4='D4').save()
Model2A.objects.db_manager("secondary").create(field1="A1")
Model2C(field1="C1", field2="C2", field3="C3").save(using="secondary")
Model2B.objects.create(field1="B1", field2="B2")
Model2D(field1="D1", field2="D2", field3="D3", field4="D4").save()
self.assertQuerysetEqual(
Model2A.objects.order_by('id'),
Model2A.objects.order_by("id"),
[Model2B, Model2D],
transform=lambda o: o.__class__,
)
self.assertQuerysetEqual(
Model2A.objects.db_manager('secondary').order_by('id'),
Model2A.objects.db_manager("secondary").order_by("id"),
[Model2A, Model2C],
transform=lambda o: o.__class__,
)
def test_instance_of_filter_on_non_default_database(self):
Base.objects.db_manager('secondary').create(field_b='B1')
ModelX.objects.db_manager('secondary').create(field_b='B', field_x='X')
ModelY.objects.db_manager('secondary').create(field_b='Y', field_y='Y')
Base.objects.db_manager("secondary").create(field_b="B1")
ModelX.objects.db_manager("secondary").create(field_b="B", field_x="X")
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(
objects,
[Base, ModelX, ModelY],
@ -54,19 +54,19 @@ class MultipleDatabasesTests(TestCase):
)
self.assertQuerysetEqual(
Base.objects.db_manager('secondary').filter(instance_of=ModelX),
Base.objects.db_manager("secondary").filter(instance_of=ModelX),
[ModelX],
transform=lambda o: o.__class__,
)
self.assertQuerysetEqual(
Base.objects.db_manager('secondary').filter(instance_of=ModelY),
Base.objects.db_manager("secondary").filter(instance_of=ModelY),
[ModelY],
transform=lambda o: o.__class__,
)
self.assertQuerysetEqual(
Base.objects.db_manager('secondary').filter(
Base.objects.db_manager("secondary").filter(
Q(instance_of=ModelX) | Q(instance_of=ModelY)
),
[ModelX, ModelY],
@ -76,10 +76,14 @@ class MultipleDatabasesTests(TestCase):
def test_forward_many_to_one_descriptor_on_non_default_database(self):
def func():
blog = BlogA.objects.db_manager('secondary').create(name='Blog', info='Info')
entry = BlogEntry.objects.db_manager('secondary').create(blog=blog, text='Text')
blog = BlogA.objects.db_manager("secondary").create(
name="Blog", info="Info"
)
entry = BlogEntry.objects.db_manager("secondary").create(
blog=blog, text="Text"
)
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)
# 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 func():
blog = BlogA.objects.db_manager('secondary').create(name='Blog', info='Info')
entry = BlogEntry.objects.db_manager('secondary').create(blog=blog, text='Text')
blog = BlogA.objects.db_manager("secondary").create(
name="Blog", info="Info"
)
entry = BlogEntry.objects.db_manager("secondary").create(
blog=blog, text="Text"
)
ContentType.objects.clear_cache()
blog = BlogA.objects.db_manager('secondary').get(pk=blog.id)
self.assertEqual(entry, blog.blogentry_set.using('secondary').get())
blog = BlogA.objects.db_manager("secondary").get(pk=blog.id)
self.assertEqual(entry, blog.blogentry_set.using("secondary").get())
# Ensure no queries are made using the default database.
self.assertNumQueries(0, func)
def test_reverse_one_to_one_descriptor_on_non_default_database(self):
def func():
m2a = Model2A.objects.db_manager('secondary').create(field1='A1')
one2one = One2OneRelatingModel.objects.db_manager('secondary').create(one2one=m2a, field1='121')
m2a = Model2A.objects.db_manager("secondary").create(field1="A1")
one2one = One2OneRelatingModel.objects.db_manager("secondary").create(
one2one=m2a, field1="121"
)
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)
# 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 func():
m2a = Model2A.objects.db_manager('secondary').create(field1='A1')
rm = RelatingModel.objects.db_manager('secondary').create()
m2a = Model2A.objects.db_manager("secondary").create(field1="A1")
rm = RelatingModel.objects.db_manager("secondary").create()
rm.many2many.add(m2a)
ContentType.objects.clear_cache()
m2a = Model2A.objects.db_manager('secondary').get(pk=m2a.id)
self.assertEqual(rm, m2a.relatingmodel_set.using('secondary').get())
m2a = Model2A.objects.db_manager("secondary").get(pk=m2a.id)
self.assertEqual(rm, m2a.relatingmodel_set.using("secondary").get())
# Ensure no queries are made using the default database.
self.assertNumQueries(0, func)

File diff suppressed because it is too large Load Diff

View File

@ -1,9 +1,9 @@
from django.test import TestCase
from polymorphic.tests.models import Bottom, Middle, Top
class RegressionTests(TestCase):
def test_for_query_result_incomplete_with_inheritance(self):
""" https://github.com/bconstantin/django_polymorphic/issues/15 """
@ -15,10 +15,16 @@ class RegressionTests(TestCase):
bottom.save()
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]
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]
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]
)

View File

@ -1,26 +1,36 @@
from django.test import TransactionTestCase
from polymorphic.models import PolymorphicTypeUndefined, PolymorphicModel
from polymorphic.tests.models import Model2A, Model2B, Model2C, Model2D, Enhance_Inherit, Enhance_Base
from polymorphic.utils import reset_polymorphic_ctype, sort_by_subclass, get_base_polymorphic_model
from polymorphic.models import PolymorphicModel, PolymorphicTypeUndefined
from polymorphic.tests.models import (
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):
def test_sort_by_subclass(self):
self.assertEqual(
sort_by_subclass(Model2D, Model2B, Model2D, Model2A, Model2C),
[Model2A, Model2B, Model2C, Model2D, Model2D]
[Model2A, Model2B, Model2C, Model2D, Model2D],
)
def test_reset_polymorphic_ctype(self):
"""
Test the the polymorphic_ctype_id can be restored.
"""
Model2A.objects.create(field1='A1')
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')
Model2A.objects.create(field1="A1")
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")
Model2A.objects.all().update(polymorphic_ctype_id=None)
with self.assertRaises(PolymorphicTypeUndefined):
@ -30,12 +40,7 @@ class UtilsTests(TransactionTestCase):
self.assertQuerysetEqual(
Model2A.objects.order_by("pk"),
[
Model2A,
Model2D,
Model2B,
Model2B,
],
[Model2A, Model2D, Model2B, Model2B],
transform=lambda o: o.__class__,
)
@ -59,6 +64,7 @@ class UtilsTests(TransactionTestCase):
"""
Skipping abstract models that can't be used for querying.
"""
class A(PolymorphicModel):
class Meta:
abstract = True

View File

@ -2,9 +2,9 @@ import sys
from django.contrib.contenttypes.models import ContentType
from django.db import DEFAULT_DB_ALIAS
from polymorphic.models import PolymorphicModel
from polymorphic.base import PolymorphicModelBase
from polymorphic.models import PolymorphicModel
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
have a polymorphic content type.
"""
using = filters.pop('using', DEFAULT_DB_ALIAS)
ignore_existing = filters.pop('ignore_existing', False)
using = filters.pop("using", DEFAULT_DB_ALIAS)
ignore_existing = filters.pop("ignore_existing", False)
models = sort_by_subclass(*models)
if ignore_existing:
@ -26,7 +26,9 @@ def reset_polymorphic_ctype(*models, **filters):
models = reversed(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)
if ignore_existing:
@ -61,6 +63,7 @@ def sort_by_subclass(*classes):
return sorted(classes, cmp=_compare_mro)
else:
from functools import cmp_to_key
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.
"""
for Model in reversed(ChildModel.mro()):
if isinstance(Model, PolymorphicModelBase) and \
Model is not PolymorphicModel and \
(allow_abstract or not Model._meta.abstract):
if (
isinstance(Model, PolymorphicModelBase)
and Model is not PolymorphicModel
and (allow_abstract or not Model._meta.abstract)
):
return Model
return None

View File

@ -8,85 +8,87 @@ import django
from django.conf import settings
from django.core.management import execute_from_command_line
# python -Wd, or run via coverage:
warnings.simplefilter('always', DeprecationWarning)
warnings.simplefilter("always", DeprecationWarning)
# Give feedback on used versions
sys.stderr.write('Using Python version {0} from {1}\n'.format(sys.version[:5], sys.executable))
sys.stderr.write('Using Django version {0} from {1}\n'.format(
django.get_version(),
dirname(abspath(django.__file__)))
sys.stderr.write(
"Using Python version {0} from {1}\n".format(sys.version[:5], sys.executable)
)
sys.stderr.write(
"Using Django version {0} from {1}\n".format(
django.get_version(), dirname(abspath(django.__file__))
)
)
if not settings.configured:
settings.configure(
DEBUG=False,
DATABASES={
'default': dj_database_url.config(
env='PRIMARY_DATABASE',
default='sqlite://:memory:',
"default": dj_database_url.config(
env="PRIMARY_DATABASE", default="sqlite://:memory:"
),
'secondary': dj_database_url.config(
env='SECONDARY_DATABASE',
default='sqlite://:memory:',
"secondary": dj_database_url.config(
env="SECONDARY_DATABASE", default="sqlite://:memory:"
),
},
TEST_RUNNER="django.test.runner.DiscoverRunner",
INSTALLED_APPS=(
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.messages',
'django.contrib.sessions',
'django.contrib.sites',
'django.contrib.admin',
'polymorphic',
'polymorphic.tests',
"django.contrib.auth",
"django.contrib.contenttypes",
"django.contrib.messages",
"django.contrib.sessions",
"django.contrib.sites",
"django.contrib.admin",
"polymorphic",
"polymorphic.tests",
),
MIDDLEWARE = (
'django.middleware.common.CommonMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
MIDDLEWARE=(
"django.middleware.common.CommonMiddleware",
"django.contrib.sessions.middleware.SessionMiddleware",
"django.middleware.csrf.CsrfViewMiddleware",
"django.contrib.auth.middleware.AuthenticationMiddleware",
"django.contrib.messages.middleware.MessageMiddleware",
),
SITE_ID=3,
TEMPLATES=[{
"BACKEND": "django.template.backends.django.DjangoTemplates",
"DIRS": (),
"OPTIONS": {
"loaders": (
"django.template.loaders.filesystem.Loader",
"django.template.loaders.app_directories.Loader",
),
"context_processors": (
"django.template.context_processors.debug",
"django.template.context_processors.i18n",
"django.template.context_processors.media",
"django.template.context_processors.request",
"django.template.context_processors.static",
"django.contrib.messages.context_processors.messages",
"django.contrib.auth.context_processors.auth",
),
},
},
TEMPLATES=[
{
"BACKEND": "django.template.backends.django.DjangoTemplates",
"DIRS": (),
"OPTIONS": {
"loaders": (
"django.template.loaders.filesystem.Loader",
"django.template.loaders.app_directories.Loader",
),
"context_processors": (
"django.template.context_processors.debug",
"django.template.context_processors.i18n",
"django.template.context_processors.media",
"django.template.context_processors.request",
"django.template.context_processors.static",
"django.contrib.messages.context_processors.messages",
"django.contrib.auth.context_processors.auth",
),
},
}
],
POLYMORPHIC_TEST_SWAPPABLE='polymorphic.swappedmodel',
POLYMORPHIC_TEST_SWAPPABLE="polymorphic.swappedmodel",
ROOT_URLCONF=None,
)
DEFAULT_TEST_APPS = [
'polymorphic',
]
DEFAULT_TEST_APPS = ["polymorphic"]
def runtests():
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
argv = sys.argv[:1] + ['test', '--traceback'] + other_args + test_apps
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
)
argv = sys.argv[:1] + ["test", "--traceback"] + other_args + test_apps
execute_from_command_line(argv)
if __name__ == '__main__':
if __name__ == "__main__":
runtests()

View File

@ -2,5 +2,4 @@
from setuptools import setup
setup()