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() # so the docs root won't be detected by find_packages()
# Display sane URLs in the docs: # Display sane URLs in the docs:
STATIC_URL = '/static/' STATIC_URL = "/static/"
# Avoid error for missing the secret key # Avoid error for missing the secret key
SECRET_KEY = 'docs' SECRET_KEY = "docs"
INSTALLED_APPS = [ INSTALLED_APPS = ["django.contrib.contenttypes"]
'django.contrib.contenttypes',
]

View File

@ -11,173 +11,174 @@
# All configuration values have a default; values that are commented out # All configuration values have a default; values that are commented out
# serve to show the default. # serve to show the default.
import sys
import os import os
import sys
import django import django
import sphinx_rtd_theme import sphinx_rtd_theme
# If extensions (or modules to document with autodoc) are in another directory, # If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the # add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here. # documentation root, use os.path.abspath to make it absolute, like shown here.
sys.path.insert(0, os.path.abspath('_ext')) sys.path.insert(0, os.path.abspath("_ext"))
sys.path.insert(0, os.path.abspath('..')) sys.path.insert(0, os.path.abspath(".."))
os.environ['DJANGO_SETTINGS_MODULE'] = 'djangodummy.settings' os.environ["DJANGO_SETTINGS_MODULE"] = "djangodummy.settings"
django.setup() django.setup()
# -- General configuration ----------------------------------------------------- # -- General configuration -----------------------------------------------------
# If your documentation needs a minimal Sphinx version, state it here. # 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 # Add any Sphinx extension module names here, as strings. They can be extensions
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones. # coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
extensions = [ extensions = [
'sphinx.ext.autodoc', "sphinx.ext.autodoc",
'sphinx.ext.graphviz', "sphinx.ext.graphviz",
'sphinx.ext.intersphinx', "sphinx.ext.intersphinx",
'sphinxcontrib_django', "sphinxcontrib_django",
] ]
# Add any paths that contain templates here, relative to this directory. # Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates'] templates_path = ["_templates"]
# The suffix of source filenames. # The suffix of source filenames.
source_suffix = '.rst' source_suffix = ".rst"
# The encoding of source files. # The encoding of source files.
#source_encoding = 'utf-8-sig' # source_encoding = 'utf-8-sig'
# The master toctree document. # The master toctree document.
master_doc = 'index' master_doc = "index"
# General information about the project. # General information about the project.
project = u'django-polymorphic' project = u"django-polymorphic"
copyright = u'2013, Bert Constantin, Chris Glass, Diederik van der Boor' copyright = u"2013, Bert Constantin, Chris Glass, Diederik van der Boor"
# The version info for the project you're documenting, acts as replacement for # The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the # |version| and |release|, also used in various other places throughout the
# built documents. # built documents.
# #
# The short X.Y version. # The short X.Y version.
version = '2.0.3' version = "2.0.3"
# The full version, including alpha/beta/rc tags. # The full version, including alpha/beta/rc tags.
release = '2.0.3' release = "2.0.3"
# The language for content autogenerated by Sphinx. Refer to documentation # The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages. # for a list of supported languages.
#language = None # language = None
# There are two options for replacing |today|: either, you set today to some # There are two options for replacing |today|: either, you set today to some
# non-false value, then it is used: # non-false value, then it is used:
#today = '' # today = ''
# Else, today_fmt is used as the format for a strftime call. # 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 # List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files. # directories to ignore when looking for source files.
exclude_patterns = ['_build'] exclude_patterns = ["_build"]
# The reST default role (used for this markup: `text`) to use for all documents. # The reST default role (used for this markup: `text`) to use for all documents.
#default_role = None # default_role = None
# If true, '()' will be appended to :func: etc. cross-reference text. # 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 # If true, the current module name will be prepended to all description
# unit titles (such as .. function::). # unit titles (such as .. function::).
#add_module_names = True # add_module_names = True
# If true, sectionauthor and moduleauthor directives will be shown in the # If true, sectionauthor and moduleauthor directives will be shown in the
# output. They are ignored by default. # output. They are ignored by default.
#show_authors = False # show_authors = False
# The name of the Pygments (syntax highlighting) style to use. # The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'sphinx' pygments_style = "sphinx"
# A list of ignored prefixes for module index sorting. # A list of ignored prefixes for module index sorting.
#modindex_common_prefix = [] # modindex_common_prefix = []
# -- Options for HTML output --------------------------------------------------- # -- Options for HTML output ---------------------------------------------------
# The theme to use for HTML and HTML Help pages. See the documentation for # The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes. # a list of builtin themes.
html_theme = 'sphinx_rtd_theme' html_theme = "sphinx_rtd_theme"
html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]
# Theme options are theme-specific and customize the look and feel of a theme # Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the # further. For a list of options available for each theme, see the
# documentation. # documentation.
#html_theme_options = {} # html_theme_options = {}
# Add any paths that contain custom themes here, relative to this directory. # 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 # The name for this set of Sphinx documents. If None, it defaults to
# "<project> v<release> documentation". # "<project> v<release> documentation".
#html_title = None # html_title = None
# A shorter title for the navigation bar. Default is the same as html_title. # 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 # The name of an image file (relative to this directory) to place at the top
# of the sidebar. # 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 # 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 # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
# pixels large. # pixels large.
#html_favicon = None # html_favicon = None
# Add any paths that contain custom static files (such as style sheets) here, # Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files, # relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css". # so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ['_static'] html_static_path = ["_static"]
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
# using the given strftime format. # using the given strftime format.
#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 # If true, SmartyPants will be used to convert quotes and dashes to
# typographically correct entities. # typographically correct entities.
#html_use_smartypants = True # html_use_smartypants = True
# Custom sidebar templates, maps document names to template names. # 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 # Additional templates that should be rendered to pages, maps page names to
# template names. # template names.
#html_additional_pages = {} # html_additional_pages = {}
# If false, no module index is generated. # If false, no module index is generated.
#html_domain_indices = True # html_domain_indices = True
# If false, no index is generated. # 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. # 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. # 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. # 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. # 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 # 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 # contain a <link> tag referring to it. The value of this option must be the
# base URL from which the finished HTML is served. # 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"). # 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. # Output file base name for HTML help builder.
htmlhelp_basename = 'django-polymorphicdoc' htmlhelp_basename = "django-polymorphicdoc"
# -- Options for LaTeX output -------------------------------------------------- # -- Options for LaTeX output --------------------------------------------------
@ -185,10 +186,8 @@ htmlhelp_basename = 'django-polymorphicdoc'
latex_elements = { latex_elements = {
# The paper size ('letterpaper' or 'a4paper'). # The paper size ('letterpaper' or 'a4paper').
#'papersize': 'letterpaper', #'papersize': 'letterpaper',
# The font size ('10pt', '11pt' or '12pt'). # The font size ('10pt', '11pt' or '12pt').
#'pointsize': '10pt', #'pointsize': '10pt',
# Additional stuff for the LaTeX preamble. # Additional stuff for the LaTeX preamble.
#'preamble': '', #'preamble': '',
} }
@ -196,29 +195,34 @@ latex_elements = {
# Grouping the document tree into LaTeX files. List of tuples # Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title, author, documentclass [howto/manual]). # (source start file, target name, title, author, documentclass [howto/manual]).
latex_documents = [ latex_documents = [
('index', 'django-polymorphic.tex', u'django-polymorphic Documentation', (
u'Bert Constantin, Chris Glass, Diederik van der Boor', 'manual'), "index",
"django-polymorphic.tex",
u"django-polymorphic Documentation",
u"Bert Constantin, Chris Glass, Diederik van der Boor",
"manual",
)
] ]
# The name of an image file (relative to this directory) to place at the top of # The name of an image file (relative to this directory) to place at the top of
# the title page. # the title page.
#latex_logo = None # latex_logo = None
# For "manual" documents, if this is true, then toplevel headings are parts, # For "manual" documents, if this is true, then toplevel headings are parts,
# not chapters. # not chapters.
#latex_use_parts = False # latex_use_parts = False
# If true, show page references after internal links. # If true, show page references after internal links.
#latex_show_pagerefs = False # latex_show_pagerefs = False
# If true, show URL addresses after external links. # 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. # Documents to append as an appendix to all manuals.
#latex_appendices = [] # latex_appendices = []
# If false, no module index is generated. # If false, no module index is generated.
#latex_domain_indices = True # latex_domain_indices = True
# -- Options for manual page output -------------------------------------------- # -- Options for manual page output --------------------------------------------
@ -226,12 +230,17 @@ latex_documents = [
# One entry per manual page. List of tuples # One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section). # (source start file, name, description, authors, manual section).
man_pages = [ man_pages = [
('index', 'django-polymorphic', u'django-polymorphic Documentation', (
[u'Bert Constantin, Chris Glass, Diederik van der Boor'], 1) "index",
"django-polymorphic",
u"django-polymorphic Documentation",
[u"Bert Constantin, Chris Glass, Diederik van der Boor"],
1,
)
] ]
# If true, show URL addresses after external links. # If true, show URL addresses after external links.
#man_show_urls = False # man_show_urls = False
# -- Options for Texinfo output ------------------------------------------------ # -- Options for Texinfo output ------------------------------------------------
@ -240,26 +249,32 @@ man_pages = [
# (source start file, target name, title, author, # (source start file, target name, title, author,
# dir menu entry, description, category) # dir menu entry, description, category)
texinfo_documents = [ texinfo_documents = [
('index', 'django-polymorphic', u'django-polymorphic Documentation', (
u'Bert Constantin, Chris Glass, Diederik van der Boor', 'django-polymorphic', 'One line description of project.', "index",
'Miscellaneous'), "django-polymorphic",
u"django-polymorphic Documentation",
u"Bert Constantin, Chris Glass, Diederik van der Boor",
"django-polymorphic",
"One line description of project.",
"Miscellaneous",
)
] ]
# Documents to append as an appendix to all manuals. # Documents to append as an appendix to all manuals.
#texinfo_appendices = [] # texinfo_appendices = []
# If false, no module index is generated. # If false, no module index is generated.
#texinfo_domain_indices = True # texinfo_domain_indices = True
# How to display URL addresses: 'footnote', 'no', or 'inline'. # 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. # Example configuration for intersphinx: refer to the Python standard library.
intersphinx_mapping = { intersphinx_mapping = {
#'http://docs.python.org/': None, #'http://docs.python.org/': None,
'https://docs.djangoproject.com/en/stable': 'https://docs.djangoproject.com/en/stable/_objects', "https://docs.djangoproject.com/en/stable": "https://docs.djangoproject.com/en/stable/_objects"
} }
# autodoc settings # autodoc settings
autodoc_member_order = 'groupwise' autodoc_member_order = "groupwise"

View File

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

View File

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

View File

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

View File

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

View File

@ -6,83 +6,148 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [("contenttypes", "0002_remove_content_type_name")]
('contenttypes', '0002_remove_content_type_name'),
]
operations = [ operations = [
migrations.CreateModel( migrations.CreateModel(
name='Order', name="Order",
fields=[ fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), (
('title', models.CharField(max_length=200, verbose_name='Title')), "id",
models.AutoField(
verbose_name="ID",
serialize=False,
auto_created=True,
primary_key=True,
),
),
("title", models.CharField(max_length=200, verbose_name="Title")),
], ],
options={ options={
'ordering': ('title',), "ordering": ("title",),
'verbose_name': 'Organisation', "verbose_name": "Organisation",
'verbose_name_plural': 'Organisations', "verbose_name_plural": "Organisations",
}, },
), ),
migrations.CreateModel( migrations.CreateModel(
name='Payment', name="Payment",
fields=[ fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), (
('currency', models.CharField(default=b'USD', max_length=3)), "id",
('amount', models.DecimalField(max_digits=10, decimal_places=2)), models.AutoField(
verbose_name="ID",
serialize=False,
auto_created=True,
primary_key=True,
),
),
("currency", models.CharField(default=b"USD", max_length=3)),
("amount", models.DecimalField(max_digits=10, decimal_places=2)),
], ],
options={ options={"verbose_name": "Payment", "verbose_name_plural": "Payments"},
'verbose_name': 'Payment',
'verbose_name_plural': 'Payments',
},
), ),
migrations.CreateModel( migrations.CreateModel(
name='BankPayment', name="BankPayment",
fields=[ fields=[
('payment_ptr', models.OneToOneField(parent_link=True, auto_created=True, primary_key=True, serialize=False, on_delete=models.CASCADE, to='orders.Payment')), (
('bank_name', models.CharField(max_length=100)), "payment_ptr",
('swift', models.CharField(max_length=20)), models.OneToOneField(
parent_link=True,
auto_created=True,
primary_key=True,
serialize=False,
on_delete=models.CASCADE,
to="orders.Payment",
),
),
("bank_name", models.CharField(max_length=100)),
("swift", models.CharField(max_length=20)),
], ],
options={ options={
'verbose_name': 'Bank Payment', "verbose_name": "Bank Payment",
'verbose_name_plural': 'Bank Payments', "verbose_name_plural": "Bank Payments",
}, },
bases=('orders.payment',), bases=("orders.payment",),
), ),
migrations.CreateModel( migrations.CreateModel(
name='CreditCardPayment', name="CreditCardPayment",
fields=[ fields=[
('payment_ptr', models.OneToOneField(parent_link=True, auto_created=True, primary_key=True, serialize=False, on_delete=models.CASCADE, to='orders.Payment')), (
('card_type', models.CharField(max_length=10)), "payment_ptr",
('expiry_month', models.PositiveSmallIntegerField(choices=[(1, 'jan'), (2, 'feb'), (3, 'mar'), (4, 'apr'), (5, 'may'), (6, 'jun'), (7, 'jul'), (8, 'aug'), (9, 'sep'), (10, 'oct'), (11, 'nov'), (12, 'dec')])), models.OneToOneField(
('expiry_year', models.PositiveIntegerField()), parent_link=True,
auto_created=True,
primary_key=True,
serialize=False,
on_delete=models.CASCADE,
to="orders.Payment",
),
),
("card_type", models.CharField(max_length=10)),
(
"expiry_month",
models.PositiveSmallIntegerField(
choices=[
(1, "jan"),
(2, "feb"),
(3, "mar"),
(4, "apr"),
(5, "may"),
(6, "jun"),
(7, "jul"),
(8, "aug"),
(9, "sep"),
(10, "oct"),
(11, "nov"),
(12, "dec"),
]
),
),
("expiry_year", models.PositiveIntegerField()),
], ],
options={ options={
'verbose_name': 'Credit Card Payment', "verbose_name": "Credit Card Payment",
'verbose_name_plural': 'Credit Card Payments', "verbose_name_plural": "Credit Card Payments",
}, },
bases=('orders.payment',), bases=("orders.payment",),
), ),
migrations.CreateModel( migrations.CreateModel(
name='SepaPayment', name="SepaPayment",
fields=[ fields=[
('payment_ptr', models.OneToOneField(parent_link=True, auto_created=True, primary_key=True, serialize=False, on_delete=models.CASCADE, to='orders.Payment')), (
('iban', models.CharField(max_length=34)), "payment_ptr",
('bic', models.CharField(max_length=11)), models.OneToOneField(
parent_link=True,
auto_created=True,
primary_key=True,
serialize=False,
on_delete=models.CASCADE,
to="orders.Payment",
),
),
("iban", models.CharField(max_length=34)),
("bic", models.CharField(max_length=11)),
], ],
options={ options={
'verbose_name': 'Bank Payment', "verbose_name": "Bank Payment",
'verbose_name_plural': 'Bank Payments', "verbose_name_plural": "Bank Payments",
}, },
bases=('orders.payment',), bases=("orders.payment",),
), ),
migrations.AddField( migrations.AddField(
model_name='payment', model_name="payment",
name='order', name="order",
field=models.ForeignKey(to='orders.Order', on_delete=models.CASCADE), field=models.ForeignKey(to="orders.Order", on_delete=models.CASCADE),
), ),
migrations.AddField( migrations.AddField(
model_name='payment', model_name="payment",
name='polymorphic_ctype', name="polymorphic_ctype",
field=models.ForeignKey(related_name='polymorphic_orders.payment_set+', editable=False, on_delete=models.CASCADE, to='contenttypes.ContentType', null=True), field=models.ForeignKey(
related_name="polymorphic_orders.payment_set+",
editable=False,
on_delete=models.CASCADE,
to="contenttypes.ContentType",
null=True,
),
), ),
] ]

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -15,5 +15,7 @@ class Command(BaseCommand):
Project.objects.all().delete() Project.objects.all().delete()
o = Project.objects.create(topic="John's gathering") o = Project.objects.create(topic="John's gathering")
o = ArtProject.objects.create(topic="Sculpting with Tim", artist="T. Turner") o = ArtProject.objects.create(topic="Sculpting with Tim", artist="T. Turner")
o = ResearchProject.objects.create(topic="Swallow Aerodynamics", supervisor="Dr. Winter") o = ResearchProject.objects.create(
topic="Swallow Aerodynamics", supervisor="Dr. Winter"
)
print(Project.objects.all()) print(Project.objects.all())

View File

@ -2,181 +2,295 @@
from __future__ import unicode_literals from __future__ import unicode_literals
from django.db import migrations, models from django.db import migrations, models
import polymorphic.showfields import polymorphic.showfields
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [("contenttypes", "0002_remove_content_type_name")]
('contenttypes', '0002_remove_content_type_name'),
]
operations = [ operations = [
migrations.CreateModel( migrations.CreateModel(
name='NormalModelA', name="NormalModelA",
fields=[ fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), (
('field1', models.CharField(max_length=10)), "id",
models.AutoField(
verbose_name="ID",
serialize=False,
auto_created=True,
primary_key=True,
),
),
("field1", models.CharField(max_length=10)),
], ],
), ),
migrations.CreateModel( migrations.CreateModel(
name='Project', name="Project",
fields=[ fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), (
('topic', models.CharField(max_length=30)), "id",
models.AutoField(
verbose_name="ID",
serialize=False,
auto_created=True,
primary_key=True,
),
),
("topic", models.CharField(max_length=30)),
], ],
options={ options={"abstract": False},
'abstract': False,
},
bases=(polymorphic.showfields.ShowFieldContent, models.Model), bases=(polymorphic.showfields.ShowFieldContent, models.Model),
), ),
migrations.CreateModel( migrations.CreateModel(
name='ProxyBase', name="ProxyBase",
fields=[ fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), (
('title', models.CharField(max_length=200)), "id",
('polymorphic_ctype', models.ForeignKey(related_name='polymorphic_pexp.proxybase_set+', editable=False, on_delete=models.CASCADE, to='contenttypes.ContentType', null=True)), models.AutoField(
verbose_name="ID",
serialize=False,
auto_created=True,
primary_key=True,
),
),
("title", models.CharField(max_length=200)),
(
"polymorphic_ctype",
models.ForeignKey(
related_name="polymorphic_pexp.proxybase_set+",
editable=False,
on_delete=models.CASCADE,
to="contenttypes.ContentType",
null=True,
),
),
], ],
options={ options={"ordering": ("title",)},
'ordering': ('title',),
},
), ),
migrations.CreateModel( migrations.CreateModel(
name='TestModelA', name="TestModelA",
fields=[ fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), (
('field1', models.CharField(max_length=10)), "id",
models.AutoField(
verbose_name="ID",
serialize=False,
auto_created=True,
primary_key=True,
),
),
("field1", models.CharField(max_length=10)),
], ],
options={ options={"abstract": False},
'abstract': False,
},
bases=(polymorphic.showfields.ShowFieldTypeAndContent, models.Model), bases=(polymorphic.showfields.ShowFieldTypeAndContent, models.Model),
), ),
migrations.CreateModel( migrations.CreateModel(
name='UUIDModelA', name="UUIDModelA",
fields=[ fields=[
('uuid_primary_key', models.UUIDField(serialize=False, primary_key=True)), (
('field1', models.CharField(max_length=10)), "uuid_primary_key",
models.UUIDField(serialize=False, primary_key=True),
),
("field1", models.CharField(max_length=10)),
], ],
options={ options={"abstract": False},
'abstract': False,
},
bases=(polymorphic.showfields.ShowFieldTypeAndContent, models.Model), bases=(polymorphic.showfields.ShowFieldTypeAndContent, models.Model),
), ),
migrations.CreateModel( migrations.CreateModel(
name='ArtProject', name="ArtProject",
fields=[ fields=[
('project_ptr', models.OneToOneField(parent_link=True, auto_created=True, primary_key=True, serialize=False, on_delete=models.CASCADE, to='pexp.Project')), (
('artist', models.CharField(max_length=30)), "project_ptr",
models.OneToOneField(
parent_link=True,
auto_created=True,
primary_key=True,
serialize=False,
on_delete=models.CASCADE,
to="pexp.Project",
),
),
("artist", models.CharField(max_length=30)),
], ],
options={ options={"abstract": False},
'abstract': False, bases=("pexp.project",),
},
bases=('pexp.project',),
), ),
migrations.CreateModel( migrations.CreateModel(
name='NormalModelB', name="NormalModelB",
fields=[ fields=[
('normalmodela_ptr', models.OneToOneField(parent_link=True, auto_created=True, primary_key=True, serialize=False, on_delete=models.CASCADE, to='pexp.NormalModelA')), (
('field2', models.CharField(max_length=10)), "normalmodela_ptr",
models.OneToOneField(
parent_link=True,
auto_created=True,
primary_key=True,
serialize=False,
on_delete=models.CASCADE,
to="pexp.NormalModelA",
),
),
("field2", models.CharField(max_length=10)),
], ],
bases=('pexp.normalmodela',), bases=("pexp.normalmodela",),
), ),
migrations.CreateModel( migrations.CreateModel(
name='ResearchProject', name="ResearchProject",
fields=[ fields=[
('project_ptr', models.OneToOneField(parent_link=True, auto_created=True, primary_key=True, serialize=False, on_delete=models.CASCADE, to='pexp.Project')), (
('supervisor', models.CharField(max_length=30)), "project_ptr",
models.OneToOneField(
parent_link=True,
auto_created=True,
primary_key=True,
serialize=False,
on_delete=models.CASCADE,
to="pexp.Project",
),
),
("supervisor", models.CharField(max_length=30)),
], ],
options={ options={"abstract": False},
'abstract': False, bases=("pexp.project",),
},
bases=('pexp.project',),
), ),
migrations.CreateModel( migrations.CreateModel(
name='TestModelB', name="TestModelB",
fields=[ fields=[
('testmodela_ptr', models.OneToOneField(parent_link=True, auto_created=True, primary_key=True, serialize=False, on_delete=models.CASCADE, to='pexp.TestModelA')), (
('field2', models.CharField(max_length=10)), "testmodela_ptr",
models.OneToOneField(
parent_link=True,
auto_created=True,
primary_key=True,
serialize=False,
on_delete=models.CASCADE,
to="pexp.TestModelA",
),
),
("field2", models.CharField(max_length=10)),
], ],
options={ options={"abstract": False},
'abstract': False, bases=("pexp.testmodela",),
},
bases=('pexp.testmodela',),
), ),
migrations.CreateModel( migrations.CreateModel(
name='UUIDModelB', name="UUIDModelB",
fields=[ fields=[
('uuidmodela_ptr', models.OneToOneField(parent_link=True, auto_created=True, primary_key=True, serialize=False, on_delete=models.CASCADE, to='pexp.UUIDModelA')), (
('field2', models.CharField(max_length=10)), "uuidmodela_ptr",
models.OneToOneField(
parent_link=True,
auto_created=True,
primary_key=True,
serialize=False,
on_delete=models.CASCADE,
to="pexp.UUIDModelA",
),
),
("field2", models.CharField(max_length=10)),
], ],
options={ options={"abstract": False},
'abstract': False, bases=("pexp.uuidmodela",),
},
bases=('pexp.uuidmodela',),
), ),
migrations.AddField( migrations.AddField(
model_name='uuidmodela', model_name="uuidmodela",
name='polymorphic_ctype', name="polymorphic_ctype",
field=models.ForeignKey(related_name='polymorphic_pexp.uuidmodela_set+', editable=False, on_delete=models.CASCADE, to='contenttypes.ContentType', null=True), field=models.ForeignKey(
related_name="polymorphic_pexp.uuidmodela_set+",
editable=False,
on_delete=models.CASCADE,
to="contenttypes.ContentType",
null=True,
),
), ),
migrations.AddField( migrations.AddField(
model_name='testmodela', model_name="testmodela",
name='polymorphic_ctype', name="polymorphic_ctype",
field=models.ForeignKey(related_name='polymorphic_pexp.testmodela_set+', editable=False, on_delete=models.CASCADE, to='contenttypes.ContentType', null=True), field=models.ForeignKey(
related_name="polymorphic_pexp.testmodela_set+",
editable=False,
on_delete=models.CASCADE,
to="contenttypes.ContentType",
null=True,
),
), ),
migrations.AddField( migrations.AddField(
model_name='project', model_name="project",
name='polymorphic_ctype', name="polymorphic_ctype",
field=models.ForeignKey(related_name='polymorphic_pexp.project_set+', editable=False, on_delete=models.CASCADE, to='contenttypes.ContentType', null=True), field=models.ForeignKey(
related_name="polymorphic_pexp.project_set+",
editable=False,
on_delete=models.CASCADE,
to="contenttypes.ContentType",
null=True,
),
), ),
migrations.CreateModel( migrations.CreateModel(
name='ProxyA', name="ProxyA", fields=[], options={"proxy": True}, bases=("pexp.proxybase",)
fields=[
],
options={
'proxy': True,
},
bases=('pexp.proxybase',),
), ),
migrations.CreateModel( migrations.CreateModel(
name='ProxyB', name="ProxyB", fields=[], options={"proxy": True}, bases=("pexp.proxybase",)
fields=[
],
options={
'proxy': True,
},
bases=('pexp.proxybase',),
), ),
migrations.CreateModel( migrations.CreateModel(
name='NormalModelC', name="NormalModelC",
fields=[ fields=[
('normalmodelb_ptr', models.OneToOneField(parent_link=True, auto_created=True, primary_key=True, serialize=False, on_delete=models.CASCADE, to='pexp.NormalModelB')), (
('field3', models.CharField(max_length=10)), "normalmodelb_ptr",
models.OneToOneField(
parent_link=True,
auto_created=True,
primary_key=True,
serialize=False,
on_delete=models.CASCADE,
to="pexp.NormalModelB",
),
),
("field3", models.CharField(max_length=10)),
], ],
bases=('pexp.normalmodelb',), bases=("pexp.normalmodelb",),
), ),
migrations.CreateModel( migrations.CreateModel(
name='TestModelC', name="TestModelC",
fields=[ fields=[
('testmodelb_ptr', models.OneToOneField(parent_link=True, auto_created=True, primary_key=True, serialize=False, on_delete=models.CASCADE, to='pexp.TestModelB')), (
('field3', models.CharField(max_length=10)), "testmodelb_ptr",
('field4', models.ManyToManyField(related_name='related_c', to='pexp.TestModelB')), models.OneToOneField(
parent_link=True,
auto_created=True,
primary_key=True,
serialize=False,
on_delete=models.CASCADE,
to="pexp.TestModelB",
),
),
("field3", models.CharField(max_length=10)),
(
"field4",
models.ManyToManyField(
related_name="related_c", to="pexp.TestModelB"
),
),
], ],
options={ options={"abstract": False},
'abstract': False, bases=("pexp.testmodelb",),
},
bases=('pexp.testmodelb',),
), ),
migrations.CreateModel( migrations.CreateModel(
name='UUIDModelC', name="UUIDModelC",
fields=[ fields=[
('uuidmodelb_ptr', models.OneToOneField(parent_link=True, auto_created=True, primary_key=True, serialize=False, on_delete=models.CASCADE, to='pexp.UUIDModelB')), (
('field3', models.CharField(max_length=10)), "uuidmodelb_ptr",
models.OneToOneField(
parent_link=True,
auto_created=True,
primary_key=True,
serialize=False,
on_delete=models.CASCADE,
to="pexp.UUIDModelB",
),
),
("field3", models.CharField(max_length=10)),
], ],
options={ options={"abstract": False},
'abstract': False, bases=("pexp.uuidmodelb",),
},
bases=('pexp.uuidmodelb',),
), ),
] ]

View File

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

View File

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

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), The admin consists of a parent admin (which shows in the admin with a list),
and a child admin (which is used internally to show the edit/delete dialog). and a child admin (which is used internally to show the edit/delete dialog).
""" """
# Admins for the regular models
from .parentadmin import PolymorphicParentModelAdmin
from .childadmin import PolymorphicChildModelAdmin from .childadmin import PolymorphicChildModelAdmin
from .filters import PolymorphicChildModelFilter
# Utils # Utils
from .forms import PolymorphicModelChoiceForm from .forms import PolymorphicModelChoiceForm
from .filters import PolymorphicChildModelFilter
# Inlines
from .inlines import (
PolymorphicInlineModelAdmin, # base class
StackedPolymorphicInline, # stacked inline
)
# Helpers for the inlines
from .helpers import (
PolymorphicInlineAdminForm,
PolymorphicInlineAdminFormSet,
PolymorphicInlineSupportMixin, # mixin for the regular model admin!
)
# Expose generic admin features too. There is no need to split those # Expose generic admin features too. There is no need to split those
# as the admin already relies on contenttypes. # as the admin already relies on contenttypes.
from .generic import ( from .generic import GenericPolymorphicInlineModelAdmin # base class
GenericPolymorphicInlineModelAdmin, # base class from .generic import GenericStackedPolymorphicInline # stacked inline
GenericStackedPolymorphicInline, # stacked inline
) # Helpers for the inlines
from .helpers import PolymorphicInlineSupportMixin # mixin for the regular model admin!
from .helpers import PolymorphicInlineAdminForm, PolymorphicInlineAdminFormSet
# Inlines
from .inlines import PolymorphicInlineModelAdmin # base class
from .inlines import StackedPolymorphicInline # stacked inline
# Admins for the regular models
from .parentadmin import PolymorphicParentModelAdmin
__all__ = ( __all__ = (
'PolymorphicParentModelAdmin', "PolymorphicParentModelAdmin",
'PolymorphicChildModelAdmin', "PolymorphicChildModelAdmin",
'PolymorphicModelChoiceForm', "PolymorphicModelChoiceForm",
'PolymorphicChildModelFilter', "PolymorphicChildModelFilter",
'PolymorphicInlineAdminForm', "PolymorphicInlineAdminForm",
'PolymorphicInlineAdminFormSet', "PolymorphicInlineAdminFormSet",
'PolymorphicInlineSupportMixin', "PolymorphicInlineSupportMixin",
'PolymorphicInlineModelAdmin', "PolymorphicInlineModelAdmin",
'StackedPolymorphicInline', "StackedPolymorphicInline",
'GenericPolymorphicInlineModelAdmin', "GenericPolymorphicInlineModelAdmin",
'GenericStackedPolymorphicInline', "GenericStackedPolymorphicInline",
) )

View File

@ -8,6 +8,7 @@ from django.urls import resolve
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from polymorphic.utils import get_base_polymorphic_model from polymorphic.utils import get_base_polymorphic_model
from ..admin import PolymorphicParentModelAdmin from ..admin import PolymorphicParentModelAdmin
@ -46,7 +47,9 @@ class PolymorphicChildModelAdmin(admin.ModelAdmin):
show_in_index = False show_in_index = False
def __init__(self, model, admin_site, *args, **kwargs): def __init__(self, model, admin_site, *args, **kwargs):
super(PolymorphicChildModelAdmin, self).__init__(model, admin_site, *args, **kwargs) super(PolymorphicChildModelAdmin, self).__init__(
model, admin_site, *args, **kwargs
)
if self.base_model is None: if self.base_model is None:
self.base_model = get_base_polymorphic_model(model) self.base_model = get_base_polymorphic_model(model)
@ -59,19 +62,23 @@ class PolymorphicChildModelAdmin(admin.ModelAdmin):
# #
# Instead, pass the form unchecked here, because the standard ModelForm will just work. # Instead, pass the form unchecked here, because the standard ModelForm will just work.
# If the derived class sets the model explicitly, respect that setting. # If the derived class sets the model explicitly, respect that setting.
kwargs.setdefault('form', self.base_form or self.form) kwargs.setdefault("form", self.base_form or self.form)
# prevent infinite recursion when this is called from get_subclass_fields # prevent infinite recursion when this is called from get_subclass_fields
if not self.fieldsets and not self.fields: if not self.fieldsets and not self.fields:
kwargs.setdefault('fields', '__all__') kwargs.setdefault("fields", "__all__")
return super(PolymorphicChildModelAdmin, self).get_form(request, obj, **kwargs) return super(PolymorphicChildModelAdmin, self).get_form(request, obj, **kwargs)
def get_model_perms(self, request): def get_model_perms(self, request):
match = resolve(request.path) match = resolve(request.path)
if not self.show_in_index and match.app_name == 'admin' and match.url_name in ('index', 'app_list'): if (
return {'add': False, 'change': False, 'delete': False} not self.show_in_index
and match.app_name == "admin"
and match.url_name in ("index", "app_list")
):
return {"add": False, "change": False, "delete": False}
return super(PolymorphicChildModelAdmin, self).get_model_perms(request) return super(PolymorphicChildModelAdmin, self).get_model_perms(request)
@property @property
@ -87,10 +94,11 @@ class PolymorphicChildModelAdmin(admin.ModelAdmin):
"admin/%s/%s/change_form.html" % (app_label, opts.object_name.lower()), "admin/%s/%s/change_form.html" % (app_label, opts.object_name.lower()),
"admin/%s/change_form.html" % app_label, "admin/%s/change_form.html" % app_label,
# Added: # Added:
"admin/%s/%s/change_form.html" % (base_app_label, base_opts.object_name.lower()), "admin/%s/%s/change_form.html"
% (base_app_label, base_opts.object_name.lower()),
"admin/%s/change_form.html" % base_app_label, "admin/%s/change_form.html" % base_app_label,
"admin/polymorphic/change_form.html", "admin/polymorphic/change_form.html",
"admin/change_form.html" "admin/change_form.html",
] ]
@property @property
@ -103,13 +111,15 @@ class PolymorphicChildModelAdmin(admin.ModelAdmin):
base_app_label = base_opts.app_label base_app_label = base_opts.app_label
return [ return [
"admin/%s/%s/delete_confirmation.html" % (app_label, opts.object_name.lower()), "admin/%s/%s/delete_confirmation.html"
% (app_label, opts.object_name.lower()),
"admin/%s/delete_confirmation.html" % app_label, "admin/%s/delete_confirmation.html" % app_label,
# Added: # Added:
"admin/%s/%s/delete_confirmation.html" % (base_app_label, base_opts.object_name.lower()), "admin/%s/%s/delete_confirmation.html"
% (base_app_label, base_opts.object_name.lower()),
"admin/%s/delete_confirmation.html" % base_app_label, "admin/%s/delete_confirmation.html" % base_app_label,
"admin/polymorphic/delete_confirmation.html", "admin/polymorphic/delete_confirmation.html",
"admin/delete_confirmation.html" "admin/delete_confirmation.html",
] ]
@property @property
@ -125,15 +135,16 @@ class PolymorphicChildModelAdmin(admin.ModelAdmin):
"admin/%s/%s/object_history.html" % (app_label, opts.object_name.lower()), "admin/%s/%s/object_history.html" % (app_label, opts.object_name.lower()),
"admin/%s/object_history.html" % app_label, "admin/%s/object_history.html" % app_label,
# Added: # Added:
"admin/%s/%s/object_history.html" % (base_app_label, base_opts.object_name.lower()), "admin/%s/%s/object_history.html"
% (base_app_label, base_opts.object_name.lower()),
"admin/%s/object_history.html" % base_app_label, "admin/%s/object_history.html" % base_app_label,
"admin/polymorphic/object_history.html", "admin/polymorphic/object_history.html",
"admin/object_history.html" "admin/object_history.html",
] ]
def _get_parent_admin(self): def _get_parent_admin(self):
# this returns parent admin instance on which to call response_post_save methods # this returns parent admin instance on which to call response_post_save methods
parent_model = self.model._meta.get_field('polymorphic_ctype').model parent_model = self.model._meta.get_field("polymorphic_ctype").model
if parent_model == self.model: if parent_model == self.model:
# when parent_model is in among child_models, just return super instance # when parent_model is in among child_models, just return super instance
return super(PolymorphicChildModelAdmin, self) return super(PolymorphicChildModelAdmin, self)
@ -149,11 +160,15 @@ class PolymorphicChildModelAdmin(admin.ModelAdmin):
# Fetch admin instance for model class, see if it's a possible candidate. # Fetch admin instance for model class, see if it's a possible candidate.
model_admin = self.admin_site._registry.get(klass) model_admin = self.admin_site._registry.get(klass)
if model_admin is not None and isinstance(model_admin, PolymorphicParentModelAdmin): if model_admin is not None and isinstance(
model_admin, PolymorphicParentModelAdmin
):
return model_admin # Success! return model_admin # Success!
# If we get this far without returning there is no admin available # If we get this far without returning there is no admin available
raise ParentAdminNotRegistered("No parent admin was registered for a '{0}' model.".format(parent_model)) raise ParentAdminNotRegistered(
"No parent admin was registered for a '{0}' model.".format(parent_model)
)
def response_post_save_add(self, request, obj): def response_post_save_add(self, request, obj):
return self._get_parent_admin().response_post_save_add(request, obj) return self._get_parent_admin().response_post_save_add(request, obj)
@ -161,26 +176,28 @@ class PolymorphicChildModelAdmin(admin.ModelAdmin):
def response_post_save_change(self, request, obj): def response_post_save_change(self, request, obj):
return self._get_parent_admin().response_post_save_change(request, obj) return self._get_parent_admin().response_post_save_change(request, obj)
def render_change_form(self, request, context, add=False, change=False, form_url='', obj=None): def render_change_form(
context.update({ self, request, context, add=False, change=False, form_url="", obj=None
'base_opts': self.base_model._meta, ):
}) context.update({"base_opts": self.base_model._meta})
return super(PolymorphicChildModelAdmin, self).render_change_form(request, context, add=add, change=change, form_url=form_url, obj=obj) return super(PolymorphicChildModelAdmin, self).render_change_form(
request, context, add=add, change=change, form_url=form_url, obj=obj
)
def delete_view(self, request, object_id, context=None): def delete_view(self, request, object_id, context=None):
extra_context = { extra_context = {"base_opts": self.base_model._meta}
'base_opts': self.base_model._meta, return super(PolymorphicChildModelAdmin, self).delete_view(
} request, object_id, extra_context
return super(PolymorphicChildModelAdmin, self).delete_view(request, object_id, extra_context) )
def history_view(self, request, object_id, extra_context=None): def history_view(self, request, object_id, extra_context=None):
# Make sure the history view can also display polymorphic breadcrumbs # Make sure the history view can also display polymorphic breadcrumbs
context = { context = {"base_opts": self.base_model._meta}
'base_opts': self.base_model._meta,
}
if extra_context: if extra_context:
context.update(extra_context) context.update(extra_context)
return super(PolymorphicChildModelAdmin, self).history_view(request, object_id, extra_context=context) return super(PolymorphicChildModelAdmin, self).history_view(
request, object_id, extra_context=context
)
# ---- Extra: improving the form/fieldset default display ---- # ---- Extra: improving the form/fieldset default display ----
@ -201,7 +218,7 @@ class PolymorphicChildModelAdmin(admin.ModelAdmin):
if other_fields: if other_fields:
return ( return (
base_fieldsets[0], base_fieldsets[0],
(self.extra_fieldset_title, {'fields': other_fields}), (self.extra_fieldset_title, {"fields": other_fields}),
) + base_fieldsets[1:] ) + base_fieldsets[1:]
else: else:
return base_fieldsets return base_fieldsets
@ -215,13 +232,15 @@ class PolymorphicChildModelAdmin(admin.ModelAdmin):
# By not declaring the fields/form in the base class, # By not declaring the fields/form in the base class,
# get_form() will populate the form with all available fields. # get_form() will populate the form with all available fields.
form = self.get_form(request, obj, exclude=exclude) form = self.get_form(request, obj, exclude=exclude)
subclass_fields = list(form.base_fields.keys()) + list(self.get_readonly_fields(request, obj)) subclass_fields = list(form.base_fields.keys()) + list(
self.get_readonly_fields(request, obj)
)
# Find which fields are not part of the common fields. # Find which fields are not part of the common fields.
for fieldset in self.get_base_fieldsets(request, obj): for fieldset in self.get_base_fieldsets(request, obj):
for field in fieldset[1]['fields']: for field in fieldset[1]["fields"]:
try: try:
subclass_fields.remove(field) subclass_fields.remove(field)
except ValueError: except ValueError:
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 return subclass_fields

View File

@ -14,11 +14,12 @@ class PolymorphicChildModelFilter(admin.SimpleListFilter):
list_filter = (PolymorphicChildModelFilter,) list_filter = (PolymorphicChildModelFilter,)
""" """
title = _('Type')
parameter_name = 'polymorphic_ctype' title = _("Type")
parameter_name = "polymorphic_ctype"
def lookups(self, request, model_admin): def lookups(self, request, model_admin):
return model_admin.get_child_type_choices(request, 'change') return model_admin.get_child_type_choices(request, "change")
def queryset(self, request, queryset): def queryset(self, request, queryset):
try: try:
@ -31,5 +32,8 @@ class PolymorphicChildModelFilter(admin.SimpleListFilter):
if choice_value == value: if choice_value == value:
return queryset.filter(polymorphic_ctype_id=choice_value) return queryset.filter(polymorphic_ctype_id=choice_value)
raise PermissionDenied( raise PermissionDenied(
'Invalid ContentType "{0}". It must be registered as child model.'.format(value)) 'Invalid ContentType "{0}". It must be registered as child model.'.format(
value
)
)
return queryset return queryset

View File

@ -7,12 +7,15 @@ class PolymorphicModelChoiceForm(forms.Form):
""" """
The default form for the ``add_type_form``. Can be overwritten and replaced. The default form for the ``add_type_form``. Can be overwritten and replaced.
""" """
#: Define the label for the radiofield
type_label = _('Type')
ct_id = forms.ChoiceField(label=type_label, widget=AdminRadioSelect(attrs={'class': 'radiolist'})) #: Define the label for the radiofield
type_label = _("Type")
ct_id = forms.ChoiceField(
label=type_label, widget=AdminRadioSelect(attrs={"class": "radiolist"})
)
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
# Allow to easily redefine the label (a commonly expected usecase) # Allow to easily redefine the label (a commonly expected usecase)
super(PolymorphicModelChoiceForm, self).__init__(*args, **kwargs) super(PolymorphicModelChoiceForm, self).__init__(*args, **kwargs)
self.fields['ct_id'].label = self.type_label self.fields["ct_id"].label = self.type_label

View File

@ -2,14 +2,22 @@ from django.contrib.contenttypes.admin import GenericInlineModelAdmin
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
from django.utils.functional import cached_property from django.utils.functional import cached_property
from polymorphic.formsets import polymorphic_child_forms_factory, BaseGenericPolymorphicInlineFormSet, GenericPolymorphicFormSetChild from polymorphic.formsets import (
BaseGenericPolymorphicInlineFormSet,
GenericPolymorphicFormSetChild,
polymorphic_child_forms_factory,
)
from .inlines import PolymorphicInlineModelAdmin from .inlines import PolymorphicInlineModelAdmin
class GenericPolymorphicInlineModelAdmin(PolymorphicInlineModelAdmin, GenericInlineModelAdmin): class GenericPolymorphicInlineModelAdmin(
PolymorphicInlineModelAdmin, GenericInlineModelAdmin
):
""" """
Base class for variation of inlines based on generic foreign keys. Base class for variation of inlines based on generic foreign keys.
""" """
#: The formset class #: The formset class
formset = BaseGenericPolymorphicInlineFormSet formset = BaseGenericPolymorphicInlineFormSet
@ -31,6 +39,7 @@ class GenericPolymorphicInlineModelAdmin(PolymorphicInlineModelAdmin, GenericInl
""" """
Variation for generic inlines. Variation for generic inlines.
""" """
# Make sure that the GFK fields are excluded from the child forms # Make sure that the GFK fields are excluded from the child forms
formset_child = GenericPolymorphicFormSetChild formset_child = GenericPolymorphicFormSetChild
ct_field = "content_type" ct_field = "content_type"
@ -42,22 +51,24 @@ class GenericPolymorphicInlineModelAdmin(PolymorphicInlineModelAdmin, GenericInl
Expose the ContentType that the child relates to. Expose the ContentType that the child relates to.
This can be used for the ``polymorphic_ctype`` field. This can be used for the ``polymorphic_ctype`` field.
""" """
return ContentType.objects.get_for_model(self.model, for_concrete_model=False) return ContentType.objects.get_for_model(
self.model, for_concrete_model=False
)
def get_formset_child(self, request, obj=None, **kwargs): def get_formset_child(self, request, obj=None, **kwargs):
# Similar to GenericInlineModelAdmin.get_formset(), # Similar to GenericInlineModelAdmin.get_formset(),
# make sure the GFK is automatically excluded from the form # make sure the GFK is automatically excluded from the form
defaults = { defaults = {"ct_field": self.ct_field, "fk_field": self.ct_fk_field}
"ct_field": self.ct_field,
"fk_field": self.ct_fk_field,
}
defaults.update(kwargs) defaults.update(kwargs)
return super(GenericPolymorphicInlineModelAdmin.Child, self).get_formset_child(request, obj=obj, **defaults) return super(
GenericPolymorphicInlineModelAdmin.Child, self
).get_formset_child(request, obj=obj, **defaults)
class GenericStackedPolymorphicInline(GenericPolymorphicInlineModelAdmin): class GenericStackedPolymorphicInline(GenericPolymorphicInlineModelAdmin):
""" """
The stacked layout for generic inlines. The stacked layout for generic inlines.
""" """
#: The default template to use. #: The default template to use.
template = 'admin/polymorphic/edit_inline/stacked.html' template = "admin/polymorphic/edit_inline/stacked.html"

View File

@ -5,7 +5,7 @@ This makes sure that admin fieldsets/layout settings are exported to the templat
""" """
import json import json
from django.contrib.admin.helpers import InlineAdminFormSet, InlineAdminForm, AdminField from django.contrib.admin.helpers import AdminField, InlineAdminForm, InlineAdminFormSet
from django.utils.encoding import force_text from django.utils.encoding import force_text
from django.utils.text import capfirst from django.utils.text import capfirst
from django.utils.translation import ugettext from django.utils.translation import ugettext
@ -19,11 +19,11 @@ class PolymorphicInlineAdminForm(InlineAdminForm):
""" """
def polymorphic_ctype_field(self): def polymorphic_ctype_field(self):
return AdminField(self.form, 'polymorphic_ctype', False) return AdminField(self.form, "polymorphic_ctype", False)
@property @property
def is_empty(self): def is_empty(self):
return '__prefix__' in self.form.prefix return "__prefix__" in self.form.prefix
class PolymorphicInlineAdminFormSet(InlineAdminFormSet): class PolymorphicInlineAdminFormSet(InlineAdminFormSet):
@ -32,15 +32,19 @@ class PolymorphicInlineAdminFormSet(InlineAdminFormSet):
""" """
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
self.request = kwargs.pop('request', None) # Assigned later via PolymorphicInlineSupportMixin later. self.request = kwargs.pop(
self.obj = kwargs.pop('obj', None) "request", None
) # Assigned later via PolymorphicInlineSupportMixin later.
self.obj = kwargs.pop("obj", None)
super(PolymorphicInlineAdminFormSet, self).__init__(*args, **kwargs) super(PolymorphicInlineAdminFormSet, self).__init__(*args, **kwargs)
def __iter__(self): def __iter__(self):
""" """
Output all forms using the proper subtype settings. Output all forms using the proper subtype settings.
""" """
for form, original in zip(self.formset.initial_forms, self.formset.get_queryset()): for form, original in zip(
self.formset.initial_forms, self.formset.get_queryset()
):
# Output the form # Output the form
model = original.get_real_instance_class() model = original.get_real_instance_class()
child_inline = self.opts.get_child_inline_instance(model) child_inline = self.opts.get_child_inline_instance(model)
@ -54,7 +58,7 @@ class PolymorphicInlineAdminFormSet(InlineAdminFormSet):
original=original, original=original,
readonly_fields=self.get_child_readonly_fields(child_inline), readonly_fields=self.get_child_readonly_fields(child_inline),
model_admin=child_inline, model_admin=child_inline,
view_on_site_url=view_on_site_url view_on_site_url=view_on_site_url,
) )
# Extra rows, and empty prefixed forms. # Extra rows, and empty prefixed forms.
@ -88,22 +92,24 @@ class PolymorphicInlineAdminFormSet(InlineAdminFormSet):
This overrides the default Django version to add the ``childTypes`` data. This overrides the default Django version to add the ``childTypes`` data.
""" """
verbose_name = self.opts.verbose_name verbose_name = self.opts.verbose_name
return json.dumps({ return json.dumps(
'name': '#%s' % self.formset.prefix, {
'options': { "name": "#%s" % self.formset.prefix,
'prefix': self.formset.prefix, "options": {
'addText': ugettext('Add another %(verbose_name)s') % { "prefix": self.formset.prefix,
'verbose_name': capfirst(verbose_name), "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): class PolymorphicInlineSupportMixin(object):
@ -119,14 +125,17 @@ class PolymorphicInlineSupportMixin(object):
:class:`~django.contrib.admin.helpers.InlineAdminFormSet` for the polymorphic formsets. :class:`~django.contrib.admin.helpers.InlineAdminFormSet` for the polymorphic formsets.
""" """
def get_inline_formsets(self, request, formsets, inline_instances, obj=None, *args, **kwargs): def get_inline_formsets(
self, request, formsets, inline_instances, obj=None, *args, **kwargs
):
""" """
Overwritten version to produce the proper admin wrapping for the Overwritten version to produce the proper admin wrapping for the
polymorphic inline formset. This fixes the media and form appearance polymorphic inline formset. This fixes the media and form appearance
of the inline polymorphic models. of the inline polymorphic models.
""" """
inline_admin_formsets = super(PolymorphicInlineSupportMixin, self).get_inline_formsets( inline_admin_formsets = super(
request, formsets, inline_instances, obj=obj) PolymorphicInlineSupportMixin, self
).get_inline_formsets(request, formsets, inline_instances, obj=obj)
for admin_formset in inline_admin_formsets: for admin_formset in inline_admin_formsets:
if isinstance(admin_formset.formset, BasePolymorphicModelFormSet): if isinstance(admin_formset.formset, BasePolymorphicModelFormSet):

View File

@ -10,9 +10,14 @@ from django.contrib.admin.utils import flatten_fieldsets
from django.core.exceptions import ImproperlyConfigured from django.core.exceptions import ImproperlyConfigured
from django.forms import Media from django.forms import Media
from polymorphic.formsets import polymorphic_child_forms_factory, BasePolymorphicInlineFormSet, \ from polymorphic.formsets import (
PolymorphicFormSetChild, UnsupportedChildType BasePolymorphicInlineFormSet,
PolymorphicFormSetChild,
UnsupportedChildType,
polymorphic_child_forms_factory,
)
from polymorphic.formsets.utils import add_media from polymorphic.formsets.utils import add_media
from .helpers import PolymorphicInlineSupportMixin from .helpers import PolymorphicInlineSupportMixin
@ -31,14 +36,8 @@ class PolymorphicInlineModelAdmin(InlineModelAdmin):
#: The extra media to add for the polymorphic inlines effect. #: The extra media to add for the polymorphic inlines effect.
#: This can be redefined for subclasses. #: This can be redefined for subclasses.
polymorphic_media = Media( polymorphic_media = Media(
js=( js=("polymorphic/js/polymorphic_inlines.js",),
'polymorphic/js/polymorphic_inlines.js', css={"all": ("polymorphic/css/polymorphic_inlines.css",)},
),
css={
'all': (
'polymorphic/css/polymorphic_inlines.css',
)
}
) )
#: The extra forms to show #: The extra forms to show
@ -90,7 +89,9 @@ class PolymorphicInlineModelAdmin(InlineModelAdmin):
try: try:
return self._child_inlines_lookup[model] return self._child_inlines_lookup[model]
except KeyError: except KeyError:
raise UnsupportedChildType("Model '{0}' not found in child_inlines".format(model.__name__)) raise UnsupportedChildType(
"Model '{0}' not found in child_inlines".format(model.__name__)
)
def get_formset(self, request, obj=None, **kwargs): def get_formset(self, request, obj=None, **kwargs):
""" """
@ -101,7 +102,9 @@ class PolymorphicInlineModelAdmin(InlineModelAdmin):
:rtype: type :rtype: type
""" """
# Construct the FormSet class # Construct the FormSet class
FormSet = super(PolymorphicInlineModelAdmin, self).get_formset(request, obj=obj, **kwargs) FormSet = super(PolymorphicInlineModelAdmin, self).get_formset(
request, obj=obj, **kwargs
)
# Instead of completely redefining super().get_formset(), we use # Instead of completely redefining super().get_formset(), we use
# the regular inlineformset_factory(), and amend that with our extra bits. # the regular inlineformset_factory(), and amend that with our extra bits.
@ -151,7 +154,10 @@ class PolymorphicInlineModelAdmin(InlineModelAdmin):
child_media = child_instance.media child_media = child_instance.media
# Avoid adding the same media object again and again # Avoid adding the same media object again and again
if child_media._css != base_media._css and child_media._js != base_media._js: if (
child_media._css != base_media._css
and child_media._js != base_media._js
):
add_media(all_media, child_media) add_media(all_media, child_media)
add_media(all_media, self.polymorphic_media) add_media(all_media, self.polymorphic_media)
@ -170,12 +176,15 @@ class PolymorphicInlineModelAdmin(InlineModelAdmin):
The model form options however, will all be read. The model form options however, will all be read.
""" """
formset_child = PolymorphicFormSetChild formset_child = PolymorphicFormSetChild
extra = 0 # TODO: currently unused for the children. extra = 0 # TODO: currently unused for the children.
def __init__(self, parent_inline): def __init__(self, parent_inline):
self.parent_inline = parent_inline self.parent_inline = parent_inline
super(PolymorphicInlineModelAdmin.Child, self).__init__(parent_inline.parent_model, parent_inline.admin_site) super(PolymorphicInlineModelAdmin.Child, self).__init__(
parent_inline.parent_model, parent_inline.admin_site
)
def get_formset(self, request, obj=None, **kwargs): def get_formset(self, request, obj=None, **kwargs):
# The child inline is only used to construct the form, # The child inline is only used to construct the form,
@ -209,8 +218,8 @@ class PolymorphicInlineModelAdmin(InlineModelAdmin):
# #
# Transfer the local inline attributes to the formset child, # Transfer the local inline attributes to the formset child,
# this allows overriding settings. # this allows overriding settings.
if 'fields' in kwargs: if "fields" in kwargs:
fields = kwargs.pop('fields') fields = kwargs.pop("fields")
else: else:
fields = flatten_fieldsets(self.get_fieldsets(request, obj)) fields = flatten_fieldsets(self.get_fieldsets(request, obj))
@ -220,9 +229,15 @@ class PolymorphicInlineModelAdmin(InlineModelAdmin):
exclude = list(self.exclude) exclude = list(self.exclude)
exclude.extend(self.get_readonly_fields(request, obj)) exclude.extend(self.get_readonly_fields(request, obj))
exclude.append('polymorphic_ctype') # Django 1.10 blocks it, as it's a readonly field. exclude.append(
"polymorphic_ctype"
) # Django 1.10 blocks it, as it's a readonly field.
if self.exclude is None and hasattr(self.form, '_meta') and self.form._meta.exclude: if (
self.exclude is None
and hasattr(self.form, "_meta")
and self.form._meta.exclude
):
# Take the custom ModelForm's Meta.exclude into account only if the # Take the custom ModelForm's Meta.exclude into account only if the
# InlineModelAdmin doesn't define its own. # InlineModelAdmin doesn't define its own.
exclude.extend(self.form._meta.exclude) exclude.extend(self.form._meta.exclude)
@ -232,7 +247,9 @@ class PolymorphicInlineModelAdmin(InlineModelAdmin):
"form": self.form, "form": self.form,
"fields": fields, "fields": fields,
"exclude": exclude or None, "exclude": exclude or None,
"formfield_callback": partial(self.formfield_for_dbfield, request=request), "formfield_callback": partial(
self.formfield_for_dbfield, request=request
),
} }
defaults.update(kwargs) defaults.update(kwargs)
@ -247,5 +264,6 @@ class StackedPolymorphicInline(PolymorphicInlineModelAdmin):
Stacked inline for django-polymorphic models. Stacked inline for django-polymorphic models.
Since tabular doesn't make much sense with changed fields, just offer this one. Since tabular doesn't make much sense with changed fields, just offer this one.
""" """
#: The default template to use. #: The default template to use.
template = 'admin/polymorphic/edit_inline/stacked.html' template = "admin/polymorphic/edit_inline/stacked.html"

View File

@ -7,7 +7,7 @@ from django.contrib import admin
from django.contrib.admin.helpers import AdminErrorList, AdminForm from django.contrib.admin.helpers import AdminErrorList, AdminForm
from django.contrib.admin.templatetags.admin_urls import add_preserved_filters from django.contrib.admin.templatetags.admin_urls import add_preserved_filters
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import PermissionDenied, ImproperlyConfigured from django.core.exceptions import ImproperlyConfigured, PermissionDenied
from django.db import models from django.db import models
from django.http import Http404, HttpResponseRedirect from django.http import Http404, HttpResponseRedirect
from django.template.response import TemplateResponse from django.template.response import TemplateResponse
@ -15,9 +15,10 @@ from django.utils.encoding import force_text
from django.utils.http import urlencode from django.utils.http import urlencode
from django.utils.safestring import mark_safe from django.utils.safestring import mark_safe
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from polymorphic.utils import get_base_polymorphic_model
from .forms import PolymorphicModelChoiceForm
from polymorphic.utils import get_base_polymorphic_model
from .forms import PolymorphicModelChoiceForm
try: try:
# Django 2.0+ # Django 2.0+
@ -73,7 +74,9 @@ class PolymorphicParentModelAdmin(admin.ModelAdmin):
pk_regex = r"(\d+|__fk__)" pk_regex = r"(\d+|__fk__)"
def __init__(self, model, admin_site, *args, **kwargs): def __init__(self, model, admin_site, *args, **kwargs):
super(PolymorphicParentModelAdmin, self).__init__(model, admin_site, *args, **kwargs) super(PolymorphicParentModelAdmin, self).__init__(
model, admin_site, *args, **kwargs
)
self._is_setup = False self._is_setup = False
if self.base_model is None: if self.base_model is None:
@ -105,12 +108,22 @@ class PolymorphicParentModelAdmin(admin.ModelAdmin):
# After the get_urls() is called, the URLs of the child model can't be exposed anymore to the Django URLconf, # After the get_urls() is called, the URLs of the child model can't be exposed anymore to the Django URLconf,
# which also means that a "Save and continue editing" button won't work. # which also means that a "Save and continue editing" button won't work.
if self._is_setup: if self._is_setup:
raise RegistrationClosed("The admin model can't be registered anymore at this point.") raise RegistrationClosed(
"The admin model can't be registered anymore at this point."
)
if not issubclass(model, self.base_model): if not issubclass(model, self.base_model):
raise TypeError("{0} should be a subclass of {1}".format(model.__name__, self.base_model.__name__)) raise TypeError(
"{0} should be a subclass of {1}".format(
model.__name__, self.base_model.__name__
)
)
if not issubclass(model_admin, admin.ModelAdmin): if not issubclass(model_admin, admin.ModelAdmin):
raise TypeError("{0} should be a subclass of {1}".format(model_admin.__name__, admin.ModelAdmin.__name__)) raise TypeError(
"{0} should be a subclass of {1}".format(
model_admin.__name__, admin.ModelAdmin.__name__
)
)
self._child_admin_site.register(model, model_admin) self._child_admin_site.register(model, model_admin)
@ -134,7 +147,7 @@ class PolymorphicParentModelAdmin(admin.ModelAdmin):
self._lazy_setup() self._lazy_setup()
choices = [] choices = []
for model in self.get_child_models(): for model in self.get_child_models():
perm_function_name = 'has_{0}_permission'.format(action) perm_function_name = "has_{0}_permission".format(action)
model_admin = self._get_real_admin_by_model(model) model_admin = self._get_real_admin_by_model(model)
perm_function = getattr(model_admin, perm_function_name) perm_function = getattr(model_admin, perm_function_name)
if not perm_function(request): if not perm_function(request):
@ -145,21 +158,28 @@ class PolymorphicParentModelAdmin(admin.ModelAdmin):
def _get_real_admin(self, object_id, super_if_self=True): def _get_real_admin(self, object_id, super_if_self=True):
try: try:
obj = self.model.objects.non_polymorphic() \ obj = (
.values('polymorphic_ctype').get(pk=object_id) self.model.objects.non_polymorphic()
.values("polymorphic_ctype")
.get(pk=object_id)
)
except self.model.DoesNotExist: except self.model.DoesNotExist:
raise Http404 raise Http404
return self._get_real_admin_by_ct(obj['polymorphic_ctype'], super_if_self=super_if_self) return self._get_real_admin_by_ct(
obj["polymorphic_ctype"], super_if_self=super_if_self
)
def _get_real_admin_by_ct(self, ct_id, super_if_self=True): def _get_real_admin_by_ct(self, ct_id, super_if_self=True):
try: try:
ct = ContentType.objects.get_for_id(ct_id) ct = ContentType.objects.get_for_id(ct_id)
except ContentType.DoesNotExist as e: except ContentType.DoesNotExist as e:
raise Http404(e) # Handle invalid GET parameters raise Http404(e) # Handle invalid GET parameters
model_class = ct.model_class() model_class = ct.model_class()
if not model_class: if not model_class:
raise Http404("No model found for '{0}.{1}'.".format(*ct.natural_key())) # Handle model deletion raise Http404(
"No model found for '{0}.{1}'.".format(*ct.natural_key())
) # Handle model deletion
return self._get_real_admin_by_model(model_class, super_if_self=super_if_self) return self._get_real_admin_by_model(model_class, super_if_self=super_if_self)
@ -167,14 +187,22 @@ class PolymorphicParentModelAdmin(admin.ModelAdmin):
# In case of a ?ct_id=### parameter, the view is already checked for permissions. # In case of a ?ct_id=### parameter, the view is already checked for permissions.
# Hence, make sure this is a derived object, or risk exposing other admin interfaces. # Hence, make sure this is a derived object, or risk exposing other admin interfaces.
if model_class not in self._child_models: if model_class not in self._child_models:
raise PermissionDenied("Invalid model '{0}', it must be registered as child model.".format(model_class)) raise PermissionDenied(
"Invalid model '{0}', it must be registered as child model.".format(
model_class
)
)
try: try:
# HACK: the only way to get the instance of an model admin, # HACK: the only way to get the instance of an model admin,
# is to read the registry of the AdminSite. # is to read the registry of the AdminSite.
real_admin = self._child_admin_site._registry[model_class] real_admin = self._child_admin_site._registry[model_class]
except KeyError: except KeyError:
raise ChildAdminNotRegistered("No child admin site was registered for a '{0}' model.".format(model_class)) raise ChildAdminNotRegistered(
"No child admin site was registered for a '{0}' model.".format(
model_class
)
)
if super_if_self and real_admin is self: if super_if_self and real_admin is self:
return super(PolymorphicParentModelAdmin, self) return super(PolymorphicParentModelAdmin, self)
@ -188,19 +216,21 @@ class PolymorphicParentModelAdmin(admin.ModelAdmin):
qs = qs.non_polymorphic() qs = qs.non_polymorphic()
return qs return qs
def add_view(self, request, form_url='', extra_context=None): def add_view(self, request, form_url="", extra_context=None):
"""Redirect the add view to the real admin.""" """Redirect the add view to the real admin."""
ct_id = int(request.GET.get('ct_id', 0)) ct_id = int(request.GET.get("ct_id", 0))
if not ct_id: if not ct_id:
# Display choices # Display choices
return self.add_type_view(request) return self.add_type_view(request)
else: else:
real_admin = self._get_real_admin_by_ct(ct_id) real_admin = self._get_real_admin_by_ct(ct_id)
# rebuild form_url, otherwise libraries below will override it. # rebuild form_url, otherwise libraries below will override it.
form_url = add_preserved_filters({ form_url = add_preserved_filters(
'preserved_filters': urlencode({'ct_id': ct_id}), {
'opts': self.model._meta}, "preserved_filters": urlencode({"ct_id": ct_id}),
form_url "opts": self.model._meta,
},
form_url,
) )
return real_admin.add_view(request, form_url, extra_context) return real_admin.add_view(request, form_url, extra_context)
@ -218,7 +248,9 @@ class PolymorphicParentModelAdmin(admin.ModelAdmin):
return real_admin.changeform_view(request, object_id, *args, **kwargs) return real_admin.changeform_view(request, object_id, *args, **kwargs)
else: else:
# Add view. As it should already be handled via `add_view`, this means something custom is done here! # Add view. As it should already be handled via `add_view`, this means something custom is done here!
return super(PolymorphicParentModelAdmin, self).changeform_view(request, object_id, *args, **kwargs) return super(PolymorphicParentModelAdmin, self).changeform_view(
request, object_id, *args, **kwargs
)
def history_view(self, request, object_id, extra_context=None): def history_view(self, request, object_id, extra_context=None):
"""Redirect the history view to the real admin.""" """Redirect the history view to the real admin."""
@ -231,14 +263,14 @@ class PolymorphicParentModelAdmin(admin.ModelAdmin):
return real_admin.delete_view(request, object_id, extra_context) return real_admin.delete_view(request, object_id, extra_context)
def get_preserved_filters(self, request): def get_preserved_filters(self, request):
if '_changelist_filters' in request.GET: if "_changelist_filters" in request.GET:
request.GET = request.GET.copy() request.GET = request.GET.copy()
filters = request.GET.get('_changelist_filters') filters = request.GET.get("_changelist_filters")
f = filters.split("&") f = filters.split("&")
for x in f: for x in f:
c = x.split('=') c = x.split("=")
request.GET[c[0]] = c[1] request.GET[c[0]] = c[1]
del request.GET['_changelist_filters'] del request.GET["_changelist_filters"]
return super(PolymorphicParentModelAdmin, self).get_preserved_filters(request) return super(PolymorphicParentModelAdmin, self).get_preserved_filters(request)
def get_urls(self): def get_urls(self):
@ -256,91 +288,100 @@ class PolymorphicParentModelAdmin(admin.ModelAdmin):
""" """
Forward any request to a custom view of the real admin. Forward any request to a custom view of the real admin.
""" """
ct_id = int(request.GET.get('ct_id', 0)) ct_id = int(request.GET.get("ct_id", 0))
if not ct_id: if not ct_id:
# See if the path started with an ID. # See if the path started with an ID.
try: try:
pos = path.find('/') pos = path.find("/")
if pos == -1: if pos == -1:
object_id = long(path) object_id = long(path)
else: else:
object_id = long(path[0:pos]) object_id = long(path[0:pos])
except ValueError: except ValueError:
raise Http404("No ct_id parameter, unable to find admin subclass for path '{0}'.".format(path)) raise Http404(
"No ct_id parameter, unable to find admin subclass for path '{0}'.".format(
path
)
)
ct_id = self.model.objects.values_list('polymorphic_ctype_id', flat=True).get(pk=object_id) ct_id = self.model.objects.values_list(
"polymorphic_ctype_id", flat=True
).get(pk=object_id)
real_admin = self._get_real_admin_by_ct(ct_id) real_admin = self._get_real_admin_by_ct(ct_id)
resolver = URLResolver('^', real_admin.urls) resolver = URLResolver("^", real_admin.urls)
resolvermatch = resolver.resolve(path) # May raise Resolver404 resolvermatch = resolver.resolve(path) # May raise Resolver404
if not resolvermatch: if not resolvermatch:
raise Http404("No match for path '{0}' in admin subclass.".format(path)) raise Http404("No match for path '{0}' in admin subclass.".format(path))
return resolvermatch.func(request, *resolvermatch.args, **resolvermatch.kwargs) return resolvermatch.func(request, *resolvermatch.args, **resolvermatch.kwargs)
def add_type_view(self, request, form_url=''): def add_type_view(self, request, form_url=""):
""" """
Display a choice form to select which page type to add. Display a choice form to select which page type to add.
""" """
if not self.has_add_permission(request): if not self.has_add_permission(request):
raise PermissionDenied raise PermissionDenied
extra_qs = '' extra_qs = ""
if request.META['QUERY_STRING']: if request.META["QUERY_STRING"]:
# QUERY_STRING is bytes in Python 3, using force_text() to decode it as string. # QUERY_STRING is bytes in Python 3, using force_text() to decode it as string.
# See QueryDict how Django deals with that. # See QueryDict how Django deals with that.
extra_qs = '&{0}'.format(force_text(request.META['QUERY_STRING'])) extra_qs = "&{0}".format(force_text(request.META["QUERY_STRING"]))
choices = self.get_child_type_choices(request, 'add') choices = self.get_child_type_choices(request, "add")
if len(choices) == 1: if len(choices) == 1:
return HttpResponseRedirect('?ct_id={0}{1}'.format(choices[0][0], extra_qs)) return HttpResponseRedirect("?ct_id={0}{1}".format(choices[0][0], extra_qs))
# Create form # Create form
form = self.add_type_form( form = self.add_type_form(
data=request.POST if request.method == 'POST' else None, data=request.POST if request.method == "POST" else None,
initial={'ct_id': choices[0][0]} initial={"ct_id": choices[0][0]},
) )
form.fields['ct_id'].choices = choices form.fields["ct_id"].choices = choices
if form.is_valid(): if form.is_valid():
return HttpResponseRedirect('?ct_id={0}{1}'.format(form.cleaned_data['ct_id'], extra_qs)) return HttpResponseRedirect(
"?ct_id={0}{1}".format(form.cleaned_data["ct_id"], extra_qs)
)
# Wrap in all admin layout # Wrap in all admin layout
fieldsets = ((None, {'fields': ('ct_id',)}),) fieldsets = ((None, {"fields": ("ct_id",)}),)
adminForm = AdminForm(form, fieldsets, {}, model_admin=self) adminForm = AdminForm(form, fieldsets, {}, model_admin=self)
media = self.media + adminForm.media media = self.media + adminForm.media
opts = self.model._meta opts = self.model._meta
context = { context = {
'title': _('Add %s') % force_text(opts.verbose_name), "title": _("Add %s") % force_text(opts.verbose_name),
'adminform': adminForm, "adminform": adminForm,
'is_popup': ("_popup" in request.POST or "is_popup": ("_popup" in request.POST or "_popup" in request.GET),
"_popup" in request.GET), "media": mark_safe(media),
'media': mark_safe(media), "errors": AdminErrorList(form, ()),
'errors': AdminErrorList(form, ()), "app_label": opts.app_label,
'app_label': opts.app_label,
} }
return self.render_add_type_form(request, context, form_url) return self.render_add_type_form(request, context, form_url)
def render_add_type_form(self, request, context, form_url=''): def render_add_type_form(self, request, context, form_url=""):
""" """
Render the page type choice form. Render the page type choice form.
""" """
opts = self.model._meta opts = self.model._meta
app_label = opts.app_label app_label = opts.app_label
context.update({ context.update(
'has_change_permission': self.has_change_permission(request), {
'form_url': mark_safe(form_url), "has_change_permission": self.has_change_permission(request),
'opts': opts, "form_url": mark_safe(form_url),
'add': True, "opts": opts,
'save_on_top': self.save_on_top, "add": True,
}) "save_on_top": self.save_on_top,
}
)
templates = self.add_type_template or [ templates = self.add_type_template or [
"admin/%s/%s/add_type_form.html" % (app_label, opts.object_name.lower()), "admin/%s/%s/add_type_form.html" % (app_label, opts.object_name.lower()),
"admin/%s/add_type_form.html" % app_label, "admin/%s/add_type_form.html" % app_label,
"admin/polymorphic/add_type_form.html", # added default here "admin/polymorphic/add_type_form.html", # added default here
"admin/add_type_form.html" "admin/add_type_form.html",
] ]
request.current_app = self.admin_site.name request.current_app = self.admin_site.name
@ -359,7 +400,8 @@ class PolymorphicParentModelAdmin(admin.ModelAdmin):
"admin/%s/%s/change_list.html" % (app_label, opts.object_name.lower()), "admin/%s/%s/change_list.html" % (app_label, opts.object_name.lower()),
"admin/%s/change_list.html" % app_label, "admin/%s/change_list.html" % app_label,
# Added base class: # Added base class:
"admin/%s/%s/change_list.html" % (base_app_label, base_opts.object_name.lower()), "admin/%s/%s/change_list.html"
% (base_app_label, base_opts.object_name.lower()),
"admin/%s/change_list.html" % base_app_label, "admin/%s/change_list.html" % base_app_label,
"admin/change_list.html" "admin/change_list.html",
] ]

View File

@ -20,9 +20,11 @@ from .query import PolymorphicQuerySet
# PolymorphicQuerySet Q objects (and filter()) support these additional key words. # PolymorphicQuerySet Q objects (and filter()) support these additional key words.
# These are forbidden as field names (a descriptive exception is raised) # These are forbidden as field names (a descriptive exception is raised)
POLYMORPHIC_SPECIAL_Q_KWORDS = ['instance_of', 'not_instance_of'] POLYMORPHIC_SPECIAL_Q_KWORDS = ["instance_of", "not_instance_of"]
DUMPDATA_COMMAND = os.path.join('django', 'core', 'management', 'commands', 'dumpdata.py') DUMPDATA_COMMAND = os.path.join(
"django", "core", "management", "commands", "dumpdata.py"
)
class ManagerInheritanceWarning(RuntimeWarning): class ManagerInheritanceWarning(RuntimeWarning):
@ -32,6 +34,7 @@ class ManagerInheritanceWarning(RuntimeWarning):
################################################################################### ###################################################################################
# PolymorphicModel meta class # PolymorphicModel meta class
class PolymorphicModelBase(ModelBase): class PolymorphicModelBase(ModelBase):
""" """
Manager inheritance is a pretty complex topic which may need Manager inheritance is a pretty complex topic which may need
@ -60,17 +63,21 @@ class PolymorphicModelBase(ModelBase):
# print; print '###', model_name, '- bases:', bases # print; print '###', model_name, '- bases:', bases
# Workaround compatibility issue with six.with_metaclass() and custom Django model metaclasses: # Workaround compatibility issue with six.with_metaclass() and custom Django model metaclasses:
if not attrs and model_name == 'NewBase': if not attrs and model_name == "NewBase":
return super(PolymorphicModelBase, self).__new__(self, model_name, bases, attrs) return super(PolymorphicModelBase, self).__new__(
self, model_name, bases, attrs
)
# Make sure that manager_inheritance_from_future is set, since django-polymorphic 1.x already # Make sure that manager_inheritance_from_future is set, since django-polymorphic 1.x already
# simulated that behavior on the polymorphic manager to all subclasses behave like polymorphics # simulated that behavior on the polymorphic manager to all subclasses behave like polymorphics
if django.VERSION < (2, 0): if django.VERSION < (2, 0):
if 'Meta' in attrs: if "Meta" in attrs:
if not hasattr(attrs['Meta'], 'manager_inheritance_from_future'): if not hasattr(attrs["Meta"], "manager_inheritance_from_future"):
attrs['Meta'].manager_inheritance_from_future = True attrs["Meta"].manager_inheritance_from_future = True
else: else:
attrs['Meta'] = type('Meta', (object,), {'manager_inheritance_from_future': True}) attrs["Meta"] = type(
"Meta", (object,), {"manager_inheritance_from_future": True}
)
# create new model # create new model
new_class = self.call_superclass_new_method(model_name, bases, attrs) new_class = self.call_superclass_new_method(model_name, bases, attrs)
@ -103,17 +110,21 @@ class PolymorphicModelBase(ModelBase):
# We run into this problem if polymorphic.py is located in a top-level directory # We run into this problem if polymorphic.py is located in a top-level directory
# which is directly in the python path. To work around this we temporarily set # which is directly in the python path. To work around this we temporarily set
# app_label here for PolymorphicModel. # app_label here for PolymorphicModel.
meta = attrs.get('Meta', None) meta = attrs.get("Meta", None)
do_app_label_workaround = (meta do_app_label_workaround = (
and attrs['__module__'] == 'polymorphic' meta
and model_name == 'PolymorphicModel' and attrs["__module__"] == "polymorphic"
and getattr(meta, 'app_label', None) is None) and model_name == "PolymorphicModel"
and getattr(meta, "app_label", None) is None
)
if do_app_label_workaround: if do_app_label_workaround:
meta.app_label = 'poly_dummy_app_label' meta.app_label = "poly_dummy_app_label"
new_class = super(PolymorphicModelBase, self).__new__(self, model_name, bases, attrs) new_class = super(PolymorphicModelBase, self).__new__(
self, model_name, bases, attrs
)
if do_app_label_workaround: if do_app_label_workaround:
del(meta.app_label) del meta.app_label
return new_class return new_class
@classmethod @classmethod
@ -133,17 +144,25 @@ class PolymorphicModelBase(ModelBase):
if django.VERSION < (2, 0): if django.VERSION < (2, 0):
extra = "\nConsider using Meta.manager_inheritance_from_future = True for Django 1.x projects" extra = "\nConsider using Meta.manager_inheritance_from_future = True for Django 1.x projects"
else: else:
extra = '' extra = ""
e = ('PolymorphicModel: "{0}.{1}" manager is of type "{2}", but must be a subclass of' e = (
' PolymorphicManager.{extra} to support retrieving subclasses'.format( 'PolymorphicModel: "{0}.{1}" manager is of type "{2}", but must be a subclass of'
model_name, manager_name, type(manager).__name__, extra=extra)) " PolymorphicManager.{extra} to support retrieving subclasses".format(
model_name, manager_name, type(manager).__name__, extra=extra
)
)
warnings.warn(e, ManagerInheritanceWarning, stacklevel=3) warnings.warn(e, ManagerInheritanceWarning, stacklevel=3)
return manager return manager
if not getattr(manager, 'queryset_class', None) or not issubclass(manager.queryset_class, PolymorphicQuerySet): if not getattr(manager, "queryset_class", None) or not issubclass(
e = ('PolymorphicModel: "{0}.{1}" has been instantiated with a queryset class ' manager.queryset_class, PolymorphicQuerySet
'which is not a subclass of PolymorphicQuerySet (which is required)'.format( ):
model_name, manager_name)) e = (
'PolymorphicModel: "{0}.{1}" has been instantiated with a queryset class '
"which is not a subclass of PolymorphicQuerySet (which is required)".format(
model_name, manager_name
)
)
warnings.warn(e, ManagerInheritanceWarning, stacklevel=3) warnings.warn(e, ManagerInheritanceWarning, stacklevel=3)
return manager return manager
@ -151,8 +170,12 @@ class PolymorphicModelBase(ModelBase):
def base_objects(self): def base_objects(self):
warnings.warn( warnings.warn(
"Using PolymorphicModel.base_objects is deprecated.\n" "Using PolymorphicModel.base_objects is deprecated.\n"
"Use {0}.objects.non_polymorphic() instead.".format(self.__class__.__name__), "Use {0}.objects.non_polymorphic() instead.".format(
DeprecationWarning, stacklevel=2) self.__class__.__name__
),
DeprecationWarning,
stacklevel=2,
)
return self._base_objects return self._base_objects
@property @property
@ -162,13 +185,13 @@ class PolymorphicModelBase(ModelBase):
# manager as default manager for the third level of inheritance when # manager as default manager for the third level of inheritance when
# that third level doesn't define a manager at all. # that third level doesn't define a manager at all.
manager = models.Manager() manager = models.Manager()
manager.name = 'base_objects' manager.name = "base_objects"
manager.model = self manager.model = self
return manager return manager
@property @property
def _default_manager(self): def _default_manager(self):
if len(sys.argv) > 1 and sys.argv[1] == 'dumpdata': if len(sys.argv) > 1 and sys.argv[1] == "dumpdata":
# TODO: investigate Django how this can be avoided # TODO: investigate Django how this can be avoided
# hack: a small patch to Django would be a better solution. # hack: a small patch to Django would be a better solution.
# Django's management command 'dumpdata' relies on non-polymorphic # Django's management command 'dumpdata' relies on non-polymorphic
@ -178,14 +201,19 @@ class PolymorphicModelBase(ModelBase):
# (non-polymorphic default manager is 'base_objects' for polymorphic models). # (non-polymorphic default manager is 'base_objects' for polymorphic models).
# This way we don't need to patch django.core.management.commands.dumpdata # This way we don't need to patch django.core.management.commands.dumpdata
# for all supported Django versions. # for all supported Django versions.
frm = inspect.stack()[1] # frm[1] is caller file name, frm[3] is caller function name frm = inspect.stack()[
1
] # frm[1] is caller file name, frm[3] is caller function name
if DUMPDATA_COMMAND in frm[1]: if DUMPDATA_COMMAND in frm[1]:
return self._base_objects return self._base_objects
manager = super(PolymorphicModelBase, self)._default_manager manager = super(PolymorphicModelBase, self)._default_manager
if not isinstance(manager, PolymorphicManager): if not isinstance(manager, PolymorphicManager):
warnings.warn("{0}._default_manager is not a PolymorphicManager".format( warnings.warn(
self.__class__.__name__ "{0}._default_manager is not a PolymorphicManager".format(
), ManagerInheritanceWarning) self.__class__.__name__
),
ManagerInheritanceWarning,
)
return manager return manager

View File

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

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. This package provides classes that support both options for polymorphic formsets.
""" """
from __future__ import absolute_import from __future__ import absolute_import
from django.core.exceptions import ImproperlyConfigured
import extra_views
from polymorphic.formsets import polymorphic_child_forms_factory, BasePolymorphicModelFormSet, BasePolymorphicInlineFormSet
import extra_views
from django.core.exceptions import ImproperlyConfigured
from polymorphic.formsets import (
BasePolymorphicInlineFormSet,
BasePolymorphicModelFormSet,
polymorphic_child_forms_factory,
)
__all__ = ( __all__ = (
'PolymorphicFormSetView', "PolymorphicFormSetView",
'PolymorphicInlineFormSetView', "PolymorphicInlineFormSetView",
'PolymorphicInlineFormSet', "PolymorphicInlineFormSet",
) )
@ -25,7 +30,7 @@ class PolymorphicFormSetMixin(object):
formset_class = BasePolymorphicModelFormSet formset_class = BasePolymorphicModelFormSet
#: Default 0 extra forms #: Default 0 extra forms
factory_kwargs = {'extra': 0} factory_kwargs = {"extra": 0}
#: Define the children #: Define the children
# :type: list[PolymorphicFormSetChild] # :type: list[PolymorphicFormSetChild]
@ -36,7 +41,9 @@ class PolymorphicFormSetMixin(object):
:rtype: list[PolymorphicFormSetChild] :rtype: list[PolymorphicFormSetChild]
""" """
if not self.formset_children: if not self.formset_children:
raise ImproperlyConfigured("Define 'formset_children' as list of `PolymorphicFormSetChild`") raise ImproperlyConfigured(
"Define 'formset_children' as list of `PolymorphicFormSetChild`"
)
return self.formset_children return self.formset_children
def get_formset_child_kwargs(self): def get_formset_child_kwargs(self):
@ -51,7 +58,9 @@ class PolymorphicFormSetMixin(object):
# reuse the standard factories, and then add `child_forms`, the same can be done here. # reuse the standard factories, and then add `child_forms`, the same can be done here.
# This makes sure the base class construction is completely honored. # This makes sure the base class construction is completely honored.
FormSet = super(PolymorphicFormSetMixin, self).get_formset() FormSet = super(PolymorphicFormSetMixin, self).get_formset()
FormSet.child_forms = polymorphic_child_forms_factory(self.get_formset_children(), **self.get_formset_child_kwargs()) FormSet.child_forms = polymorphic_child_forms_factory(
self.get_formset_children(), **self.get_formset_child_kwargs()
)
return FormSet return FormSet
@ -72,10 +81,13 @@ class PolymorphicFormSetView(PolymorphicFormSetMixin, extra_views.ModelFormSetVi
] ]
""" """
formset_class = BasePolymorphicModelFormSet formset_class = BasePolymorphicModelFormSet
class PolymorphicInlineFormSetView(PolymorphicFormSetMixin, extra_views.InlineFormSetView): class PolymorphicInlineFormSetView(
PolymorphicFormSetMixin, extra_views.InlineFormSetView
):
""" """
A view that displays a single polymorphic formset - with one parent object. A view that displays a single polymorphic formset - with one parent object.
This is a variation of the :mod:`extra_views` package classes for django-polymorphic. This is a variation of the :mod:`extra_views` package classes for django-polymorphic.
@ -93,10 +105,13 @@ class PolymorphicInlineFormSetView(PolymorphicFormSetMixin, extra_views.InlineFo
PolymorphicFormSetChild(ItemSubclass2), PolymorphicFormSetChild(ItemSubclass2),
] ]
""" """
formset_class = BasePolymorphicInlineFormSet formset_class = BasePolymorphicInlineFormSet
class PolymorphicInlineFormSet(PolymorphicFormSetMixin, extra_views.InlineFormSetFactory): class PolymorphicInlineFormSet(
PolymorphicFormSetMixin, extra_views.InlineFormSetFactory
):
""" """
An inline to add to the ``inlines`` of An inline to add to the ``inlines`` of
the :class:`~extra_views.advanced.CreateWithInlinesView` the :class:`~extra_views.advanced.CreateWithInlinesView`
@ -123,4 +138,5 @@ class PolymorphicInlineFormSet(PolymorphicFormSetMixin, extra_views.InlineFormSe
return self.object.get_absolute_url() return self.object.get_absolute_url()
""" """
formset_class = BasePolymorphicInlineFormSet formset_class = BasePolymorphicInlineFormSet

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 https://django-guardian.readthedocs.io/en/latest/configuration.html#guardian-get-content-type
""" """
if hasattr(obj, 'polymorphic_model_marker'): if hasattr(obj, "polymorphic_model_marker"):
try: try:
superclasses = list(obj.__class__.mro()) superclasses = list(obj.__class__.mro())
except TypeError: except TypeError:
@ -19,11 +19,11 @@ def get_polymorphic_base_content_type(obj):
polymorphic_superclasses = list() polymorphic_superclasses = list()
for sclass in superclasses: for sclass in superclasses:
if hasattr(sclass, 'polymorphic_model_marker'): if hasattr(sclass, "polymorphic_model_marker"):
polymorphic_superclasses.append(sclass) polymorphic_superclasses.append(sclass)
# PolymorphicMPTT adds an additional class between polymorphic and base class. # PolymorphicMPTT adds an additional class between polymorphic and base class.
if hasattr(obj, 'can_have_children'): if hasattr(obj, "can_have_children"):
root_polymorphic_class = polymorphic_superclasses[-3] root_polymorphic_class = polymorphic_superclasses[-3]
else: else:
root_polymorphic_class = polymorphic_superclasses[-2] root_polymorphic_class = polymorphic_superclasses[-2]

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. that describes how to display and construct the child.
It's parameters are very similar to the parent's factory method. It's parameters are very similar to the parent's factory method.
""" """
from .models import ( from .generic import ( # Can import generic here, as polymorphic already depends on the 'contenttypes' app.
BasePolymorphicModelFormSet,
BasePolymorphicInlineFormSet,
PolymorphicFormSetChild,
UnsupportedChildType,
polymorphic_modelformset_factory,
polymorphic_inlineformset_factory,
polymorphic_child_forms_factory,
)
from .generic import (
# Can import generic here, as polymorphic already depends on the 'contenttypes' app.
BaseGenericPolymorphicInlineFormSet, BaseGenericPolymorphicInlineFormSet,
GenericPolymorphicFormSetChild, GenericPolymorphicFormSetChild,
generic_polymorphic_inlineformset_factory, generic_polymorphic_inlineformset_factory,
) )
from .models import (
BasePolymorphicInlineFormSet,
BasePolymorphicModelFormSet,
PolymorphicFormSetChild,
UnsupportedChildType,
polymorphic_child_forms_factory,
polymorphic_inlineformset_factory,
polymorphic_modelformset_factory,
)
__all__ = ( __all__ = (
'BasePolymorphicModelFormSet', "BasePolymorphicModelFormSet",
'BasePolymorphicInlineFormSet', "BasePolymorphicInlineFormSet",
'PolymorphicFormSetChild', "PolymorphicFormSetChild",
'UnsupportedChildType', "UnsupportedChildType",
'polymorphic_modelformset_factory', "polymorphic_modelformset_factory",
'polymorphic_inlineformset_factory', "polymorphic_inlineformset_factory",
'polymorphic_child_forms_factory', "polymorphic_child_forms_factory",
'BaseGenericPolymorphicInlineFormSet', "BaseGenericPolymorphicInlineFormSet",
'GenericPolymorphicFormSetChild', "GenericPolymorphicFormSetChild",
'generic_polymorphic_inlineformset_factory', "generic_polymorphic_inlineformset_factory",
) )

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.contrib.contenttypes.models import ContentType
from django.db import models from django.db import models
from django.forms.models import ModelForm from django.forms.models import ModelForm
from .models import BasePolymorphicModelFormSet, polymorphic_child_forms_factory, PolymorphicFormSetChild from .models import (
BasePolymorphicModelFormSet,
PolymorphicFormSetChild,
polymorphic_child_forms_factory,
)
class GenericPolymorphicFormSetChild(PolymorphicFormSetChild): class GenericPolymorphicFormSetChild(PolymorphicFormSetChild):
@ -12,8 +19,8 @@ class GenericPolymorphicFormSetChild(PolymorphicFormSetChild):
""" """
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
self.ct_field = kwargs.pop('ct_field', 'content_type') self.ct_field = kwargs.pop("ct_field", "content_type")
self.fk_field = kwargs.pop('fk_field', 'object_id') self.fk_field = kwargs.pop("fk_field", "object_id")
super(GenericPolymorphicFormSetChild, self).__init__(*args, **kwargs) super(GenericPolymorphicFormSetChild, self).__init__(*args, **kwargs)
def get_form(self, ct_field="content_type", fk_field="object_id", **kwargs): def get_form(self, ct_field="content_type", fk_field="object_id", **kwargs):
@ -21,7 +28,7 @@ class GenericPolymorphicFormSetChild(PolymorphicFormSetChild):
Construct the form class for the formset child. Construct the form class for the formset child.
""" """
exclude = list(self.exclude) exclude = list(self.exclude)
extra_exclude = kwargs.pop('extra_exclude', None) extra_exclude = kwargs.pop("extra_exclude", None)
if extra_exclude: if extra_exclude:
exclude += list(extra_exclude) exclude += list(extra_exclude)
@ -31,33 +38,52 @@ class GenericPolymorphicFormSetChild(PolymorphicFormSetChild):
opts = self.model._meta opts = self.model._meta
ct_field = opts.get_field(self.ct_field) ct_field = opts.get_field(self.ct_field)
if not isinstance(ct_field, models.ForeignKey) or ct_field.remote_field.model != ContentType: if (
raise Exception("fk_name '%s' is not a ForeignKey to ContentType" % ct_field) not isinstance(ct_field, models.ForeignKey)
or ct_field.remote_field.model != ContentType
):
raise Exception(
"fk_name '%s' is not a ForeignKey to ContentType" % ct_field
)
fk_field = opts.get_field(self.fk_field) # let the exception propagate fk_field = opts.get_field(self.fk_field) # let the exception propagate
exclude.extend([ct_field.name, fk_field.name]) exclude.extend([ct_field.name, fk_field.name])
kwargs['exclude'] = exclude kwargs["exclude"] = exclude
return super(GenericPolymorphicFormSetChild, self).get_form(**kwargs) return super(GenericPolymorphicFormSetChild, self).get_form(**kwargs)
class BaseGenericPolymorphicInlineFormSet(BaseGenericInlineFormSet, BasePolymorphicModelFormSet): class BaseGenericPolymorphicInlineFormSet(
BaseGenericInlineFormSet, BasePolymorphicModelFormSet
):
""" """
Polymorphic formset variation for inline generic formsets Polymorphic formset variation for inline generic formsets
""" """
def generic_polymorphic_inlineformset_factory(model, formset_children, form=ModelForm, def generic_polymorphic_inlineformset_factory(
formset=BaseGenericPolymorphicInlineFormSet, model,
ct_field="content_type", fk_field="object_id", formset_children,
# Base form form=ModelForm,
# TODO: should these fields be removed in favor of creating formset=BaseGenericPolymorphicInlineFormSet,
# the base form as a formset child too? ct_field="content_type",
fields=None, exclude=None, fk_field="object_id",
extra=1, can_order=False, can_delete=True, # Base form
max_num=None, formfield_callback=None, # TODO: should these fields be removed in favor of creating
validate_max=False, for_concrete_model=True, # the base form as a formset child too?
min_num=None, validate_min=False, child_form_kwargs=None): 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. Construct the class for a generic inline polymorphic formset.
@ -70,22 +96,22 @@ def generic_polymorphic_inlineformset_factory(model, formset_children, form=Mode
:rtype: type :rtype: type
""" """
kwargs = { kwargs = {
'model': model, "model": model,
'form': form, "form": form,
'formfield_callback': formfield_callback, "formfield_callback": formfield_callback,
'formset': formset, "formset": formset,
'ct_field': ct_field, "ct_field": ct_field,
'fk_field': fk_field, "fk_field": fk_field,
'extra': extra, "extra": extra,
'can_delete': can_delete, "can_delete": can_delete,
'can_order': can_order, "can_order": can_order,
'fields': fields, "fields": fields,
'exclude': exclude, "exclude": exclude,
'min_num': min_num, "min_num": min_num,
'max_num': max_num, "max_num": max_num,
'validate_min': validate_min, "validate_min": validate_min,
'validate_max': validate_max, "validate_max": validate_max,
'for_concrete_model': for_concrete_model, "for_concrete_model": for_concrete_model,
# 'localized_fields': localized_fields, # 'localized_fields': localized_fields,
# 'labels': labels, # 'labels': labels,
# 'help_texts': help_texts, # 'help_texts': help_texts,
@ -97,12 +123,14 @@ def generic_polymorphic_inlineformset_factory(model, formset_children, form=Mode
child_kwargs = { child_kwargs = {
# 'exclude': exclude, # 'exclude': exclude,
'ct_field': ct_field, "ct_field": ct_field,
'fk_field': fk_field, "fk_field": fk_field,
} }
if child_form_kwargs: if child_form_kwargs:
child_kwargs.update(child_form_kwargs) child_kwargs.update(child_form_kwargs)
FormSet = generic_inlineformset_factory(**kwargs) FormSet = generic_inlineformset_factory(**kwargs)
FormSet.child_forms = polymorphic_child_forms_factory(formset_children, **child_kwargs) FormSet.child_forms = polymorphic_child_forms_factory(
formset_children, **child_kwargs
)
return FormSet return FormSet

View File

@ -3,10 +3,18 @@ from collections import OrderedDict
from django import forms from django import forms
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import ImproperlyConfigured, ValidationError from django.core.exceptions import ImproperlyConfigured, ValidationError
from django.forms.models import ModelForm, BaseModelFormSet, BaseInlineFormSet, modelform_factory, modelformset_factory, inlineformset_factory from django.forms.models import (
BaseInlineFormSet,
BaseModelFormSet,
ModelForm,
inlineformset_factory,
modelform_factory,
modelformset_factory,
)
from django.utils.functional import cached_property from django.utils.functional import cached_property
from polymorphic.models import PolymorphicModel from polymorphic.models import PolymorphicModel
from .utils import add_media from .utils import add_media
@ -20,9 +28,19 @@ class PolymorphicFormSetChild(object):
Provide this information in the :func:'polymorphic_inlineformset_factory' construction. Provide this information in the :func:'polymorphic_inlineformset_factory' construction.
""" """
def __init__(self, model, form=ModelForm, fields=None, exclude=None, def __init__(
formfield_callback=None, widgets=None, localized_fields=None, self,
labels=None, help_texts=None, error_messages=None): model,
form=ModelForm,
fields=None,
exclude=None,
formfield_callback=None,
widgets=None,
localized_fields=None,
labels=None,
help_texts=None,
error_messages=None,
):
self.model = model self.model = model
@ -59,20 +77,20 @@ class PolymorphicFormSetChild(object):
# we allow to define things like 'extra_...' fields that are amended to the current child settings. # we allow to define things like 'extra_...' fields that are amended to the current child settings.
exclude = list(self.exclude) exclude = list(self.exclude)
extra_exclude = kwargs.pop('extra_exclude', None) extra_exclude = kwargs.pop("extra_exclude", None)
if extra_exclude: if extra_exclude:
exclude += list(extra_exclude) exclude += list(extra_exclude)
defaults = { defaults = {
'form': self._form_base, "form": self._form_base,
'formfield_callback': self.formfield_callback, "formfield_callback": self.formfield_callback,
'fields': self.fields, "fields": self.fields,
'exclude': exclude, "exclude": exclude,
# 'for_concrete_model': for_concrete_model, # 'for_concrete_model': for_concrete_model,
'localized_fields': self.localized_fields, "localized_fields": self.localized_fields,
'labels': self.labels, "labels": self.labels,
'help_texts': self.help_texts, "help_texts": self.help_texts,
'error_messages': self.error_messages, "error_messages": self.error_messages,
# 'field_classes': field_classes, # 'field_classes': field_classes,
} }
defaults.update(kwargs) defaults.update(kwargs)
@ -125,61 +143,71 @@ class BasePolymorphicModelFormSet(BaseModelFormSet):
pk_field = self.model._meta.pk pk_field = self.model._meta.pk
to_python = self._get_to_python(pk_field) to_python = self._get_to_python(pk_field)
pk = to_python(pk) pk = to_python(pk)
kwargs['instance'] = self._existing_object(pk) kwargs["instance"] = self._existing_object(pk)
if i < self.initial_form_count() and 'instance' not in kwargs: if i < self.initial_form_count() and "instance" not in kwargs:
kwargs['instance'] = self.get_queryset()[i] kwargs["instance"] = self.get_queryset()[i]
if i >= self.initial_form_count() and self.initial_extra: if i >= self.initial_form_count() and self.initial_extra:
# Set initial values for extra forms # Set initial values for extra forms
try: try:
kwargs['initial'] = self.initial_extra[i - self.initial_form_count()] kwargs["initial"] = self.initial_extra[i - self.initial_form_count()]
except IndexError: except IndexError:
pass pass
# BaseFormSet logic, with custom formset_class # BaseFormSet logic, with custom formset_class
defaults = { defaults = {
'auto_id': self.auto_id, "auto_id": self.auto_id,
'prefix': self.add_prefix(i), "prefix": self.add_prefix(i),
'error_class': self.error_class, "error_class": self.error_class,
} }
if self.is_bound: if self.is_bound:
defaults['data'] = self.data defaults["data"] = self.data
defaults['files'] = self.files defaults["files"] = self.files
if self.initial and 'initial' not in kwargs: if self.initial and "initial" not in kwargs:
try: try:
defaults['initial'] = self.initial[i] defaults["initial"] = self.initial[i]
except IndexError: except IndexError:
pass pass
# Allow extra forms to be empty, unless they're part of # Allow extra forms to be empty, unless they're part of
# the minimum forms. # the minimum forms.
if i >= self.initial_form_count() and i >= self.min_num: if i >= self.initial_form_count() and i >= self.min_num:
defaults['empty_permitted'] = True defaults["empty_permitted"] = True
defaults['use_required_attribute'] = False defaults["use_required_attribute"] = False
defaults.update(kwargs) defaults.update(kwargs)
# Need to find the model that will be displayed in this form. # Need to find the model that will be displayed in this form.
# Hence, peeking in the self.queryset_data beforehand. # Hence, peeking in the self.queryset_data beforehand.
if self.is_bound: if self.is_bound:
if 'instance' in defaults: if "instance" in defaults:
# Object is already bound to a model, won't change the content type # Object is already bound to a model, won't change the content type
model = defaults['instance'].get_real_instance_class() # allow proxy models model = defaults[
"instance"
].get_real_instance_class() # allow proxy models
else: else:
# Extra or empty form, use the provided type. # Extra or empty form, use the provided type.
# Note this completely tru # Note this completely tru
prefix = defaults['prefix'] prefix = defaults["prefix"]
try: try:
ct_id = int(self.data["{0}-polymorphic_ctype".format(prefix)]) ct_id = int(self.data["{0}-polymorphic_ctype".format(prefix)])
except (KeyError, ValueError): except (KeyError, ValueError):
raise ValidationError("Formset row {0} has no 'polymorphic_ctype' defined!".format(prefix)) raise ValidationError(
"Formset row {0} has no 'polymorphic_ctype' defined!".format(
prefix
)
)
model = ContentType.objects.get_for_id(ct_id).model_class() model = ContentType.objects.get_for_id(ct_id).model_class()
if model not in self.child_forms: if model not in self.child_forms:
# Perform basic validation, as we skip the ChoiceField here. # Perform basic validation, as we skip the ChoiceField here.
raise UnsupportedChildType("Child model type {0} is not part of the formset".format(model)) raise UnsupportedChildType(
"Child model type {0} is not part of the formset".format(model)
)
else: else:
if 'instance' in defaults: if "instance" in defaults:
model = defaults['instance'].get_real_instance_class() # allow proxy models model = defaults[
elif 'polymorphic_ctype' in defaults.get('initial', {}): "instance"
model = defaults['initial']['polymorphic_ctype'].model_class() ].get_real_instance_class() # allow proxy models
elif "polymorphic_ctype" in defaults.get("initial", {}):
model = defaults["initial"]["polymorphic_ctype"].model_class()
elif i < len(self.queryset_data): elif i < len(self.queryset_data):
model = self.queryset_data[i].__class__ model = self.queryset_data[i].__class__
else: else:
@ -196,9 +224,13 @@ class BasePolymorphicModelFormSet(BaseModelFormSet):
def add_fields(self, form, index): def add_fields(self, form, index):
"""Add a hidden field for the content type.""" """Add a hidden field for the content type."""
ct = ContentType.objects.get_for_model(form._meta.model, for_concrete_model=False) ct = ContentType.objects.get_for_model(
form._meta.model, for_concrete_model=False
)
choices = [(ct.pk, ct)] # Single choice, existing forms can't change the value. choices = [(ct.pk, ct)] # Single choice, existing forms can't change the value.
form.fields['polymorphic_ctype'] = forms.ChoiceField(choices=choices, initial=ct.pk, required=False, widget=forms.HiddenInput) form.fields["polymorphic_ctype"] = forms.ChoiceField(
choices=choices, initial=ct.pk, required=False, widget=forms.HiddenInput
)
super(BasePolymorphicModelFormSet, self).add_fields(form, index) super(BasePolymorphicModelFormSet, self).add_fields(form, index)
def get_form_class(self, model): def get_form_class(self, model):
@ -206,7 +238,9 @@ class BasePolymorphicModelFormSet(BaseModelFormSet):
Return the proper form class for the given model. Return the proper form class for the given model.
""" """
if not self.child_forms: if not self.child_forms:
raise ImproperlyConfigured("No 'child_forms' defined in {0}".format(self.__class__.__name__)) raise ImproperlyConfigured(
"No 'child_forms' defined in {0}".format(self.__class__.__name__)
)
if not issubclass(model, PolymorphicModel): if not issubclass(model, PolymorphicModel):
raise TypeError("Expect polymorphic model type, not {0}".format(model)) raise TypeError("Expect polymorphic model type, not {0}".format(model))
@ -218,7 +252,8 @@ class BasePolymorphicModelFormSet(BaseModelFormSet):
"The '{0}' found a '{1}' model in the queryset, " "The '{0}' found a '{1}' model in the queryset, "
"but no form class is registered to display it.".format( "but no form class is registered to display it.".format(
self.__class__.__name__, model.__name__ self.__class__.__name__, model.__name__
)) )
)
def is_multipart(self): def is_multipart(self):
""" """
@ -247,7 +282,7 @@ class BasePolymorphicModelFormSet(BaseModelFormSet):
form = form_class( form = form_class(
auto_id=self.auto_id, auto_id=self.auto_id,
prefix=self.add_prefix('__prefix__'), prefix=self.add_prefix("__prefix__"),
empty_permitted=True, empty_permitted=True,
use_required_attribute=False, use_required_attribute=False,
**kwargs **kwargs
@ -259,20 +294,37 @@ class BasePolymorphicModelFormSet(BaseModelFormSet):
@property @property
def empty_form(self): def empty_form(self):
# TODO: make an exception when can_add_base is defined? # TODO: make an exception when can_add_base is defined?
raise RuntimeError("'empty_form' is not used in polymorphic formsets, use 'empty_forms' instead.") raise RuntimeError(
"'empty_form' is not used in polymorphic formsets, use 'empty_forms' instead."
)
def polymorphic_modelformset_factory(model, formset_children, def polymorphic_modelformset_factory(
formset=BasePolymorphicModelFormSet, model,
# Base field formset_children,
# TODO: should these fields be removed in favor of creating formset=BasePolymorphicModelFormSet,
# the base form as a formset child too? # Base field
form=ModelForm, # TODO: should these fields be removed in favor of creating
fields=None, exclude=None, extra=1, can_order=False, # the base form as a formset child too?
can_delete=True, max_num=None, formfield_callback=None, form=ModelForm,
widgets=None, validate_max=False, localized_fields=None, fields=None,
labels=None, help_texts=None, error_messages=None, exclude=None,
min_num=None, validate_min=False, field_classes=None, child_form_kwargs=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. Construct the class for an polymorphic model formset.
@ -285,25 +337,25 @@ def polymorphic_modelformset_factory(model, formset_children,
:rtype: type :rtype: type
""" """
kwargs = { kwargs = {
'model': model, "model": model,
'form': form, "form": form,
'formfield_callback': formfield_callback, "formfield_callback": formfield_callback,
'formset': formset, "formset": formset,
'extra': extra, "extra": extra,
'can_delete': can_delete, "can_delete": can_delete,
'can_order': can_order, "can_order": can_order,
'fields': fields, "fields": fields,
'exclude': exclude, "exclude": exclude,
'min_num': min_num, "min_num": min_num,
'max_num': max_num, "max_num": max_num,
'widgets': widgets, "widgets": widgets,
'validate_min': validate_min, "validate_min": validate_min,
'validate_max': validate_max, "validate_max": validate_max,
'localized_fields': localized_fields, "localized_fields": localized_fields,
'labels': labels, "labels": labels,
'help_texts': help_texts, "help_texts": help_texts,
'error_messages': error_messages, "error_messages": error_messages,
'field_classes': field_classes, "field_classes": field_classes,
} }
FormSet = modelformset_factory(**kwargs) FormSet = modelformset_factory(**kwargs)
@ -313,7 +365,9 @@ def polymorphic_modelformset_factory(model, formset_children,
if child_form_kwargs: if child_form_kwargs:
child_kwargs.update(child_form_kwargs) child_kwargs.update(child_form_kwargs)
FormSet.child_forms = polymorphic_child_forms_factory(formset_children, **child_kwargs) FormSet.child_forms = polymorphic_child_forms_factory(
formset_children, **child_kwargs
)
return FormSet return FormSet
@ -326,17 +380,34 @@ class BasePolymorphicInlineFormSet(BaseInlineFormSet, BasePolymorphicModelFormSe
return super(BasePolymorphicInlineFormSet, self)._construct_form(i, **kwargs) return super(BasePolymorphicInlineFormSet, self)._construct_form(i, **kwargs)
def polymorphic_inlineformset_factory(parent_model, model, formset_children, def polymorphic_inlineformset_factory(
formset=BasePolymorphicInlineFormSet, fk_name=None, parent_model,
# Base field model,
# TODO: should these fields be removed in favor of creating formset_children,
# the base form as a formset child too? formset=BasePolymorphicInlineFormSet,
form=ModelForm, fk_name=None,
fields=None, exclude=None, extra=1, can_order=False, # Base field
can_delete=True, max_num=None, formfield_callback=None, # TODO: should these fields be removed in favor of creating
widgets=None, validate_max=False, localized_fields=None, # the base form as a formset child too?
labels=None, help_texts=None, error_messages=None, form=ModelForm,
min_num=None, validate_min=False, field_classes=None, child_form_kwargs=None): 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. Construct the class for an inline polymorphic formset.
@ -349,27 +420,27 @@ def polymorphic_inlineformset_factory(parent_model, model, formset_children,
:rtype: type :rtype: type
""" """
kwargs = { kwargs = {
'parent_model': parent_model, "parent_model": parent_model,
'model': model, "model": model,
'form': form, "form": form,
'formfield_callback': formfield_callback, "formfield_callback": formfield_callback,
'formset': formset, "formset": formset,
'fk_name': fk_name, "fk_name": fk_name,
'extra': extra, "extra": extra,
'can_delete': can_delete, "can_delete": can_delete,
'can_order': can_order, "can_order": can_order,
'fields': fields, "fields": fields,
'exclude': exclude, "exclude": exclude,
'min_num': min_num, "min_num": min_num,
'max_num': max_num, "max_num": max_num,
'widgets': widgets, "widgets": widgets,
'validate_min': validate_min, "validate_min": validate_min,
'validate_max': validate_max, "validate_max": validate_max,
'localized_fields': localized_fields, "localized_fields": localized_fields,
'labels': labels, "labels": labels,
'help_texts': help_texts, "help_texts": help_texts,
'error_messages': error_messages, "error_messages": error_messages,
'field_classes': field_classes, "field_classes": field_classes,
} }
FormSet = inlineformset_factory(**kwargs) FormSet = inlineformset_factory(**kwargs)
@ -379,5 +450,7 @@ def polymorphic_inlineformset_factory(parent_model, model, formset_children,
if child_form_kwargs: if child_form_kwargs:
child_kwargs.update(child_form_kwargs) child_kwargs.update(child_form_kwargs)
FormSet.child_forms = polymorphic_child_forms_factory(formset_children, **child_kwargs) FormSet.child_forms = polymorphic_child_forms_factory(
formset_children, **child_kwargs
)
return FormSet return FormSet

View File

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

View File

@ -6,10 +6,14 @@ from __future__ import absolute_import
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
from django.db import models from django.db import models
from django.db.models.fields.related import ReverseOneToOneDescriptor, ForwardManyToOneDescriptor from django.db.models.fields.related import (
ForwardManyToOneDescriptor,
ReverseOneToOneDescriptor,
)
from django.db.utils import DEFAULT_DB_ALIAS from django.db.utils import DEFAULT_DB_ALIAS
from polymorphic.compat import with_metaclass from polymorphic.compat import with_metaclass
from .base import PolymorphicModelBase from .base import PolymorphicModelBase
from .managers import PolymorphicManager from .managers import PolymorphicManager
from .query_translate import translate_polymorphic_Q_object from .query_translate import translate_polymorphic_Q_object
@ -44,12 +48,15 @@ class PolymorphicModel(with_metaclass(PolymorphicModelBase, models.Model)):
# avoid ContentType related field accessor clash (an error emitted by model validation) # avoid ContentType related field accessor clash (an error emitted by model validation)
#: The model field that stores the :class:`~django.contrib.contenttypes.models.ContentType` reference to the actual class. #: The model field that stores the :class:`~django.contrib.contenttypes.models.ContentType` reference to the actual class.
polymorphic_ctype = models.ForeignKey( polymorphic_ctype = models.ForeignKey(
ContentType, null=True, editable=False, on_delete=models.CASCADE, ContentType,
related_name='polymorphic_%(app_label)s.%(class)s_set+' null=True,
editable=False,
on_delete=models.CASCADE,
related_name="polymorphic_%(app_label)s.%(class)s_set+",
) )
# some applications want to know the name of the fields that are added to its models # some applications want to know the name of the fields that are added to its models
polymorphic_internal_model_fields = ['polymorphic_ctype'] polymorphic_internal_model_fields = ["polymorphic_ctype"]
# Note that Django 1.5 removes these managers because the model is abstract. # Note that Django 1.5 removes these managers because the model is abstract.
# They are pretended to be there by the metaclass in PolymorphicModelBase.get_inherited_managers() # They are pretended to be there by the metaclass in PolymorphicModelBase.get_inherited_managers()
@ -73,14 +80,18 @@ class PolymorphicModel(with_metaclass(PolymorphicModelBase, models.Model)):
# field to figure out the real class of this object # field to figure out the real class of this object
# (used by PolymorphicQuerySet._get_real_instances) # (used by PolymorphicQuerySet._get_real_instances)
if not self.polymorphic_ctype_id: if not self.polymorphic_ctype_id:
self.polymorphic_ctype = ContentType.objects.db_manager(using).get_for_model(self, for_concrete_model=False) self.polymorphic_ctype = ContentType.objects.db_manager(
using
).get_for_model(self, for_concrete_model=False)
pre_save_polymorphic.alters_data = True pre_save_polymorphic.alters_data = True
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
"""Calls :meth:`pre_save_polymorphic` and saves the model.""" """Calls :meth:`pre_save_polymorphic` and saves the model."""
using = kwargs.get('using', self._state.db or DEFAULT_DB_ALIAS) using = kwargs.get("using", self._state.db or DEFAULT_DB_ALIAS)
self.pre_save_polymorphic(using=using) self.pre_save_polymorphic(using=using)
return super(PolymorphicModel, self).save(*args, **kwargs) return super(PolymorphicModel, self).save(*args, **kwargs)
save.alters_data = True save.alters_data = True
def get_real_instance_class(self): def get_real_instance_class(self):
@ -92,28 +103,40 @@ class PolymorphicModel(with_metaclass(PolymorphicModelBase, models.Model)):
determined using this method. determined using this method.
""" """
if self.polymorphic_ctype_id is None: if self.polymorphic_ctype_id is None:
raise PolymorphicTypeUndefined(( raise PolymorphicTypeUndefined(
"The model {}#{} does not have a `polymorphic_ctype_id` value defined.\n" (
"If you created models outside polymorphic, e.g. through an import or migration, " "The model {}#{} does not have a `polymorphic_ctype_id` value defined.\n"
"make sure the `polymorphic_ctype_id` field points to the ContentType ID of the model subclass." "If you created models outside polymorphic, e.g. through an import or migration, "
).format(self.__class__.__name__, self.pk)) "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 # the following line would be the easiest way to do this, but it produces sql queries
# return self.polymorphic_ctype.model_class() # return self.polymorphic_ctype.model_class()
# so we use the following version, which uses the ContentType manager cache. # so we use the following version, which uses the ContentType manager cache.
# Note that model_class() can return None for stale content types; # Note that model_class() can return None for stale content types;
# when the content type record still exists but no longer refers to an existing model. # when the content type record still exists but no longer refers to an existing model.
model = ContentType.objects.db_manager(self._state.db).get_for_id(self.polymorphic_ctype_id).model_class() model = (
ContentType.objects.db_manager(self._state.db)
.get_for_id(self.polymorphic_ctype_id)
.model_class()
)
# Protect against bad imports (dumpdata without --natural) or other # Protect against bad imports (dumpdata without --natural) or other
# issues missing with the ContentType models. # issues missing with the ContentType models.
if model is not None \ if (
and not issubclass(model, self.__class__) \ model is not None
and (self.__class__._meta.proxy_for_model is None \ and not issubclass(model, self.__class__)
or not issubclass(model, self.__class__._meta.proxy_for_model)): and (
raise PolymorphicTypeInvalid("ContentType {0} for {1} #{2} does not point to a subclass!".format( self.__class__._meta.proxy_for_model is None
self.polymorphic_ctype_id, model, self.pk, or not issubclass(model, self.__class__._meta.proxy_for_model)
)) )
):
raise PolymorphicTypeInvalid(
"ContentType {0} for {1} #{2} does not point to a subclass!".format(
self.polymorphic_ctype_id, model, self.pk
)
)
return model return model
@ -121,13 +144,21 @@ class PolymorphicModel(with_metaclass(PolymorphicModelBase, models.Model)):
model_class = self.get_real_instance_class() model_class = self.get_real_instance_class()
if model_class is None: if model_class is None:
return None return None
return ContentType.objects.db_manager(self._state.db).get_for_model(model_class, for_concrete_model=True).pk return (
ContentType.objects.db_manager(self._state.db)
.get_for_model(model_class, for_concrete_model=True)
.pk
)
def get_real_concrete_instance_class(self): def get_real_concrete_instance_class(self):
model_class = self.get_real_instance_class() model_class = self.get_real_instance_class()
if model_class is None: if model_class is None:
return None return None
return ContentType.objects.db_manager(self._state.db).get_for_model(model_class, for_concrete_model=True).model_class() return (
ContentType.objects.db_manager(self._state.db)
.get_for_model(model_class, for_concrete_model=True)
.model_class()
)
def get_real_instance(self): def get_real_instance(self):
""" """
@ -147,7 +178,7 @@ class PolymorphicModel(with_metaclass(PolymorphicModelBase, models.Model)):
return self return self
return real_model.objects.db_manager(self._state.db).get(pk=self.pk) 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 """Replace Django's inheritance accessor member functions for our model
(self.__class__) with our own versions. (self.__class__) with our own versions.
We monkey patch them until a patch can be added to Django 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 But they should not. So we replace them with our own accessors that use
our appropriate base_objects manager. 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: if self.__class__.polymorphic_super_sub_accessors_replaced:
return return
@ -176,15 +207,25 @@ class PolymorphicModel(with_metaclass(PolymorphicModelBase, models.Model)):
def accessor_function(self): def accessor_function(self):
attr = model._base_objects.get(pk=self.pk) attr = model._base_objects.get(pk=self.pk)
return attr return attr
return accessor_function return accessor_function
subclasses_and_superclasses_accessors = self._get_inheritance_relation_fields_and_models() subclasses_and_superclasses_accessors = (
self._get_inheritance_relation_fields_and_models()
)
for name, model in subclasses_and_superclasses_accessors.items(): for name, model in subclasses_and_superclasses_accessors.items():
# Here be dragons. # Here be dragons.
orig_accessor = getattr(self.__class__, name, None) orig_accessor = getattr(self.__class__, name, None)
if issubclass(type(orig_accessor), (ReverseOneToOneDescriptor, ForwardManyToOneDescriptor)): if issubclass(
setattr(self.__class__, name, property(create_accessor_function_for_model(model, name))) type(orig_accessor),
(ReverseOneToOneDescriptor, ForwardManyToOneDescriptor),
):
setattr(
self.__class__,
name,
property(create_accessor_function_for_model(model, name)),
)
def _get_inheritance_relation_fields_and_models(self): def _get_inheritance_relation_fields_and_models(self):
"""helper function for __init__: """helper function for __init__:
@ -194,31 +235,45 @@ class PolymorphicModel(with_metaclass(PolymorphicModelBase, models.Model)):
result[field_name] = model result[field_name] = model
def add_model_if_regular(model, field_name, result): def add_model_if_regular(model, field_name, result):
if (issubclass(model, models.Model) if (
and model != models.Model issubclass(model, models.Model)
and model != self.__class__ and model != models.Model
and model != PolymorphicModel): and model != self.__class__
and model != PolymorphicModel
):
add_model(model, field_name, result) add_model(model, field_name, result)
def add_all_super_models(model, result): def add_all_super_models(model, result):
for super_cls, field_to_super in model._meta.parents.items(): for super_cls, field_to_super in model._meta.parents.items():
if field_to_super is not None: # if not a link to a proxy model if field_to_super is not None: # if not a link to a proxy model
field_name = field_to_super.name # the field on model can have a different name to super_cls._meta.module_name, if the field is created manually using 'parent_link' field_name = (
field_to_super.name
) # the field on model can have a different name to super_cls._meta.module_name, if the field is created manually using 'parent_link'
add_model_if_regular(super_cls, field_name, result) add_model_if_regular(super_cls, field_name, result)
add_all_super_models(super_cls, result) add_all_super_models(super_cls, result)
def add_all_sub_models(super_cls, result): def add_all_sub_models(super_cls, result):
for sub_cls in super_cls.__subclasses__(): # go through all subclasses of model for (
if super_cls in sub_cls._meta.parents: # super_cls may not be in sub_cls._meta.parents if super_cls is a proxy model sub_cls
field_to_super = sub_cls._meta.parents[super_cls] # get the field that links sub_cls to super_cls ) in super_cls.__subclasses__(): # go through all subclasses of model
if field_to_super is not None: # if filed_to_super is not a link to a proxy model if (
super_cls in sub_cls._meta.parents
): # super_cls may not be in sub_cls._meta.parents if super_cls is a proxy model
field_to_super = sub_cls._meta.parents[
super_cls
] # get the field that links sub_cls to super_cls
if (
field_to_super is not None
): # if filed_to_super is not a link to a proxy model
super_to_sub_related_field = field_to_super.remote_field super_to_sub_related_field = field_to_super.remote_field
if super_to_sub_related_field.related_name is None: if super_to_sub_related_field.related_name is None:
# if related name is None the related field is the name of the subclass # if related name is None the related field is the name of the subclass
to_subclass_fieldname = sub_cls.__name__.lower() to_subclass_fieldname = sub_cls.__name__.lower()
else: else:
# otherwise use the given related name # otherwise use the given related name
to_subclass_fieldname = super_to_sub_related_field.related_name to_subclass_fieldname = (
super_to_sub_related_field.related_name
)
add_model_if_regular(sub_cls, to_subclass_fieldname, result) add_model_if_regular(sub_cls, to_subclass_fieldname, result)

View File

@ -12,8 +12,12 @@ from django.db.models import FieldDoesNotExist
from django.db.models.query import ModelIterable, Q, QuerySet from django.db.models.query import ModelIterable, Q, QuerySet
from . import compat from . import compat
from .query_translate import translate_polymorphic_filter_definitions_in_kwargs, translate_polymorphic_filter_definitions_in_args from .query_translate import (
from .query_translate import translate_polymorphic_field_path, translate_polymorphic_Q_object translate_polymorphic_field_path,
translate_polymorphic_filter_definitions_in_args,
translate_polymorphic_filter_definitions_in_kwargs,
translate_polymorphic_Q_object,
)
# chunk-size: maximum number of objects requested per db-request # chunk-size: maximum number of objects requested per db-request
# by the polymorphic queryset.iterator() implementation # by the polymorphic queryset.iterator() implementation
@ -72,7 +76,7 @@ def transmogrify(cls, obj):
""" """
Upcast a class to a different type without asking questions. Upcast a class to a different type without asking questions.
""" """
if '__init__' not in obj.__dict__: if "__init__" not in obj.__dict__:
# Just assign __class__ to a different value. # Just assign __class__ to a different value.
new = obj new = obj
new.__class__ = cls new.__class__ = cls
@ -87,6 +91,7 @@ def transmogrify(cls, obj):
################################################################################### ###################################################################################
# PolymorphicQuerySet # PolymorphicQuerySet
class PolymorphicQuerySet(QuerySet): class PolymorphicQuerySet(QuerySet):
""" """
QuerySet for PolymorphicModel QuerySet for PolymorphicModel
@ -115,14 +120,17 @@ class PolymorphicQuerySet(QuerySet):
new.polymorphic_disabled = self.polymorphic_disabled new.polymorphic_disabled = self.polymorphic_disabled
new.polymorphic_deferred_loading = ( new.polymorphic_deferred_loading = (
copy.copy(self.polymorphic_deferred_loading[0]), copy.copy(self.polymorphic_deferred_loading[0]),
self.polymorphic_deferred_loading[1]) self.polymorphic_deferred_loading[1],
)
return new return new
def as_manager(cls): def as_manager(cls):
from .managers import PolymorphicManager from .managers import PolymorphicManager
manager = PolymorphicManager.from_queryset(cls)() manager = PolymorphicManager.from_queryset(cls)()
manager._built_with_as_manager = True manager._built_with_as_manager = True
return manager return manager
as_manager.queryset_only = True as_manager.queryset_only = True
as_manager = classmethod(as_manager) as_manager = classmethod(as_manager)
@ -154,15 +162,22 @@ class PolymorphicQuerySet(QuerySet):
def _filter_or_exclude(self, negate, *args, **kwargs): def _filter_or_exclude(self, negate, *args, **kwargs):
# We override this internal Django functon as it is used for all filter member functions. # We override this internal Django functon as it is used for all filter member functions.
q_objects = translate_polymorphic_filter_definitions_in_args(self.model, args, using=self.db) # the Q objects q_objects = translate_polymorphic_filter_definitions_in_args(
additional_args = translate_polymorphic_filter_definitions_in_kwargs(self.model, kwargs, using=self.db) # filter_field='data' self.model, args, using=self.db
return super(PolymorphicQuerySet, self)._filter_or_exclude(negate, *(list(q_objects) + additional_args), **kwargs) ) # the Q objects
additional_args = translate_polymorphic_filter_definitions_in_kwargs(
self.model, kwargs, using=self.db
) # filter_field='data'
return super(PolymorphicQuerySet, self)._filter_or_exclude(
negate, *(list(q_objects) + additional_args), **kwargs
)
def order_by(self, *field_names): def order_by(self, *field_names):
"""translate the field paths in the args, then call vanilla order_by.""" """translate the field paths in the args, then call vanilla order_by."""
field_names = [ field_names = [
translate_polymorphic_field_path(self.model, a) translate_polymorphic_field_path(self.model, a)
if isinstance(a, compat.string_types) else a # allow expressions to pass unchanged if isinstance(a, compat.string_types)
else a # allow expressions to pass unchanged
for a in field_names for a in field_names
] ]
return super(PolymorphicQuerySet, self).order_by(*field_names) return super(PolymorphicQuerySet, self).order_by(*field_names)
@ -213,8 +228,8 @@ class PolymorphicQuerySet(QuerySet):
""" """
existing, defer = self.polymorphic_deferred_loading existing, defer = self.polymorphic_deferred_loading
field_names = set(field_names) field_names = set(field_names)
if 'pk' in field_names: if "pk" in field_names:
field_names.remove('pk') field_names.remove("pk")
field_names.add(self.model._meta.pk.name) field_names.add(self.model._meta.pk.name)
if defer: if defer:
@ -228,14 +243,14 @@ class PolymorphicQuerySet(QuerySet):
def _process_aggregate_args(self, args, kwargs): def _process_aggregate_args(self, args, kwargs):
"""for aggregate and annotate kwargs: allow ModelX___field syntax for kwargs, forbid it for args. """for aggregate and annotate kwargs: allow ModelX___field syntax for kwargs, forbid it for args.
Modifies kwargs if needed (these are Aggregate objects, we translate the lookup member variable)""" Modifies kwargs if needed (these are Aggregate objects, we translate the lookup member variable)"""
___lookup_assert_msg = 'PolymorphicModel: annotate()/aggregate(): ___ model lookup supported for keyword arguments only' ___lookup_assert_msg = "PolymorphicModel: annotate()/aggregate(): ___ model lookup supported for keyword arguments only"
def patch_lookup(a): def patch_lookup(a):
# The field on which the aggregate operates is # The field on which the aggregate operates is
# stored inside a complex query expression. # stored inside a complex query expression.
if isinstance(a, Q): if isinstance(a, Q):
translate_polymorphic_Q_object(self.model, a) translate_polymorphic_Q_object(self.model, a)
elif hasattr(a, 'get_source_expressions'): elif hasattr(a, "get_source_expressions"):
for source_expression in a.get_source_expressions(): for source_expression in a.get_source_expressions():
if source_expression is not None: if source_expression is not None:
patch_lookup(source_expression) patch_lookup(source_expression)
@ -246,6 +261,7 @@ class PolymorphicQuerySet(QuerySet):
""" *args might be complex expressions too in django 1.8 so """ *args might be complex expressions too in django 1.8 so
the testing for a '___' is rather complex on this one """ the testing for a '___' is rather complex on this one """
if isinstance(a, Q): if isinstance(a, Q):
def tree_node_test___lookup(my_model, node): def tree_node_test___lookup(my_model, node):
" process all children of this Q node " " process all children of this Q node "
for i in range(len(node.children)): for i in range(len(node.children)):
@ -253,17 +269,17 @@ class PolymorphicQuerySet(QuerySet):
if type(child) == tuple: if type(child) == tuple:
# this Q object child is a tuple => a kwarg like Q( instance_of=ModelB ) # this Q object child is a tuple => a kwarg like Q( instance_of=ModelB )
assert '___' not in child[0], ___lookup_assert_msg assert "___" not in child[0], ___lookup_assert_msg
else: else:
# this Q object child is another Q object, recursively process this as well # this Q object child is another Q object, recursively process this as well
tree_node_test___lookup(my_model, child) tree_node_test___lookup(my_model, child)
tree_node_test___lookup(self.model, a) tree_node_test___lookup(self.model, a)
elif hasattr(a, 'get_source_expressions'): elif hasattr(a, "get_source_expressions"):
for source_expression in a.get_source_expressions(): for source_expression in a.get_source_expressions():
test___lookup(source_expression) test___lookup(source_expression)
else: else:
assert '___' not in a.name, ___lookup_assert_msg assert "___" not in a.name, ___lookup_assert_msg
for a in args: for a in args:
test___lookup(a) test___lookup(a)
@ -325,7 +341,7 @@ class PolymorphicQuerySet(QuerySet):
Finally we re-sort the resulting objects into the correct order and Finally we re-sort the resulting objects into the correct order and
return them as a list. 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, # dict contains one entry per unique model type occurring in result,
# in the format idlist_per_model[modelclass]=[list-of-object-ids] # 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; # - sort base_result_object ids into idlist_per_model lists, depending on their real class;
# - store objects that already have the correct class into "results" # - store objects that already have the correct class into "results"
content_type_manager = ContentType.objects.db_manager(self.db) content_type_manager = ContentType.objects.db_manager(self.db)
self_model_class_id = content_type_manager.get_for_model(self.model, for_concrete_model=False).pk self_model_class_id = content_type_manager.get_for_model(
self_concrete_model_class_id = content_type_manager.get_for_model(self.model, for_concrete_model=True).pk self.model, for_concrete_model=False
).pk
self_concrete_model_class_id = content_type_manager.get_for_model(
self.model, for_concrete_model=True
).pk
for i, base_object in enumerate(base_result_objects): for i, base_object in enumerate(base_result_objects):
@ -354,7 +374,9 @@ class PolymorphicQuerySet(QuerySet):
resultlist.append(base_object) resultlist.append(base_object)
else: else:
real_concrete_class = base_object.get_real_instance_class() real_concrete_class = base_object.get_real_instance_class()
real_concrete_class_id = base_object.get_real_concrete_instance_class_id() real_concrete_class_id = (
base_object.get_real_concrete_instance_class_id()
)
if real_concrete_class_id is None: if real_concrete_class_id is None:
# Dealing with a stale content type # Dealing with a stale content type
@ -365,9 +387,15 @@ class PolymorphicQuerySet(QuerySet):
resultlist.append(transmogrify(real_concrete_class, base_object)) resultlist.append(transmogrify(real_concrete_class, base_object))
else: else:
# This model has a concrete derived class, track it for bulk retrieval. # This model has a concrete derived class, track it for bulk retrieval.
real_concrete_class = content_type_manager.get_for_id(real_concrete_class_id).model_class() real_concrete_class = content_type_manager.get_for_id(
idlist_per_model[real_concrete_class].append(getattr(base_object, pk_name)) real_concrete_class_id
indexlist_per_model[real_concrete_class].append((i, len(resultlist))) ).model_class()
idlist_per_model[real_concrete_class].append(
getattr(base_object, pk_name)
)
indexlist_per_model[real_concrete_class].append(
(i, len(resultlist))
)
resultlist.append(None) resultlist.append(None)
# For each model in "idlist_per_model" request its objects (the real model) # For each model in "idlist_per_model" request its objects (the real model)
@ -377,10 +405,12 @@ class PolymorphicQuerySet(QuerySet):
# TODO: defer(), only(): support for these would be around here # TODO: defer(), only(): support for these would be around here
for real_concrete_class, idlist in idlist_per_model.items(): for real_concrete_class, idlist in idlist_per_model.items():
indices = indexlist_per_model[real_concrete_class] indices = indexlist_per_model[real_concrete_class]
real_objects = real_concrete_class._base_objects.db_manager(self.db).filter(**{ real_objects = real_concrete_class._base_objects.db_manager(self.db).filter(
('%s__in' % pk_name): idlist, **{("%s__in" % pk_name): idlist}
}) )
real_objects.query.select_related = self.query.select_related # copy select related configuration to new qs real_objects.query.select_related = (
self.query.select_related
) # copy select related configuration to new qs
# Copy deferred fields configuration to the new queryset # Copy deferred fields configuration to the new queryset
deferred_loading_fields = [] deferred_loading_fields = []
@ -388,14 +418,15 @@ class PolymorphicQuerySet(QuerySet):
for field in existing_fields: for field in existing_fields:
try: try:
translated_field_name = translate_polymorphic_field_path( translated_field_name = translate_polymorphic_field_path(
real_concrete_class, field) real_concrete_class, field
)
except AssertionError: except AssertionError:
if '___' in field: if "___" in field:
# The originally passed argument to .defer() or .only() # The originally passed argument to .defer() or .only()
# was in the form Model2B___field2, where Model2B is # was in the form Model2B___field2, where Model2B is
# now a superclass of real_concrete_class. Thus it's # now a superclass of real_concrete_class. Thus it's
# sufficient to just use the field name. # sufficient to just use the field name.
translated_field_name = field.rpartition('___')[-1] translated_field_name = field.rpartition("___")[-1]
# Check if the field does exist. # Check if the field does exist.
# Ignore deferred fields that don't exist in this subclass type. # Ignore deferred fields that don't exist in this subclass type.
@ -407,7 +438,10 @@ class PolymorphicQuerySet(QuerySet):
raise raise
deferred_loading_fields.append(translated_field_name) deferred_loading_fields.append(translated_field_name)
real_objects.query.deferred_loading = (set(deferred_loading_fields), self.query.deferred_loading[1]) real_objects.query.deferred_loading = (
set(deferred_loading_fields),
self.query.deferred_loading[1],
)
real_objects_dict = { real_objects_dict = {
getattr(real_object, pk_name): real_object getattr(real_object, pk_name): real_object
@ -445,13 +479,17 @@ class PolymorphicQuerySet(QuerySet):
# set polymorphic_annotate_names in all objects (currently just used for debugging/printing) # set polymorphic_annotate_names in all objects (currently just used for debugging/printing)
if self.query.annotations: if self.query.annotations:
annotate_names = list(self.query.annotations.keys()) # get annotate field list annotate_names = list(
self.query.annotations.keys()
) # get annotate field list
for real_object in resultlist: for real_object in resultlist:
real_object.polymorphic_annotate_names = annotate_names real_object.polymorphic_annotate_names = annotate_names
# set polymorphic_extra_select_names in all objects (currently just used for debugging/printing) # set polymorphic_extra_select_names in all objects (currently just used for debugging/printing)
if self.query.extra_select: if self.query.extra_select:
extra_select_names = list(self.query.extra_select.keys()) # get extra select field list extra_select_names = list(
self.query.extra_select.keys()
) # get extra select field list
for real_object in resultlist: for real_object in resultlist:
real_object.polymorphic_extra_select_names = extra_select_names real_object.polymorphic_extra_select_names = extra_select_names
@ -460,15 +498,14 @@ class PolymorphicQuerySet(QuerySet):
def __repr__(self, *args, **kwargs): def __repr__(self, *args, **kwargs):
if self.model.polymorphic_query_multiline_output: if self.model.polymorphic_query_multiline_output:
result = [repr(o) for o in self.all()] result = [repr(o) for o in self.all()]
return '[ ' + ',\n '.join(result) + ' ]' return "[ " + ",\n ".join(result) + " ]"
else: else:
return super(PolymorphicQuerySet, self).__repr__(*args, **kwargs) return super(PolymorphicQuerySet, self).__repr__(*args, **kwargs)
class _p_list_class(list): class _p_list_class(list):
def __repr__(self, *args, **kwargs): def __repr__(self, *args, **kwargs):
result = [repr(o) for o in self] result = [repr(o) for o in self]
return '[ ' + ',\n '.join(result) + ' ]' return "[ " + ",\n ".join(result) + " ]"
def get_real_instances(self, base_result_objects=None): def get_real_instances(self, base_result_objects=None):
""" """

View File

@ -15,18 +15,19 @@ from django.db.models import Q
from django.db.models.fields.related import ForeignObjectRel, RelatedField from django.db.models.fields.related import ForeignObjectRel, RelatedField
from django.db.utils import DEFAULT_DB_ALIAS from django.db.utils import DEFAULT_DB_ALIAS
###################################################################################
# PolymorphicQuerySet support functions
# These functions implement the additional filter- and Q-object functionality. # These functions implement the additional filter- and Q-object functionality.
# They form a kind of small framework for easily adding more # They form a kind of small framework for easily adding more
# functionality to filters and Q objects. # functionality to filters and Q objects.
# Probably a more general queryset enhancement class could be made out of them. # Probably a more general queryset enhancement class could be made out of them.
from polymorphic import compat from polymorphic import compat
###################################################################################
# PolymorphicQuerySet support functions
def translate_polymorphic_filter_definitions_in_kwargs(queryset_model, kwargs, using=DEFAULT_DB_ALIAS):
def translate_polymorphic_filter_definitions_in_kwargs(
queryset_model, kwargs, using=DEFAULT_DB_ALIAS
):
""" """
Translate the keyword argument list for PolymorphicQuerySet.filter() Translate the keyword argument list for PolymorphicQuerySet.filter()
@ -43,21 +44,25 @@ def translate_polymorphic_filter_definitions_in_kwargs(queryset_model, kwargs, u
additional_args = [] additional_args = []
for field_path, val in kwargs.copy().items(): # Python 3 needs copy for field_path, val in kwargs.copy().items(): # Python 3 needs copy
new_expr = _translate_polymorphic_filter_definition(queryset_model, field_path, val, using=using) new_expr = _translate_polymorphic_filter_definition(
queryset_model, field_path, val, using=using
)
if type(new_expr) == tuple: if type(new_expr) == tuple:
# replace kwargs element # replace kwargs element
del(kwargs[field_path]) del kwargs[field_path]
kwargs[new_expr[0]] = new_expr[1] kwargs[new_expr[0]] = new_expr[1]
elif isinstance(new_expr, models.Q): elif isinstance(new_expr, models.Q):
del(kwargs[field_path]) del kwargs[field_path]
additional_args.append(new_expr) additional_args.append(new_expr)
return additional_args return additional_args
def translate_polymorphic_Q_object(queryset_model, potential_q_object, using=DEFAULT_DB_ALIAS): def translate_polymorphic_Q_object(
queryset_model, potential_q_object, using=DEFAULT_DB_ALIAS
):
def tree_node_correct_field_specs(my_model, node): def tree_node_correct_field_specs(my_model, node):
" process all children of this Q node " " process all children of this Q node "
for i in range(len(node.children)): for i in range(len(node.children)):
@ -66,7 +71,9 @@ def translate_polymorphic_Q_object(queryset_model, potential_q_object, using=DEF
if type(child) == tuple: if type(child) == tuple:
# this Q object child is a tuple => a kwarg like Q( instance_of=ModelB ) # this Q object child is a tuple => a kwarg like Q( instance_of=ModelB )
key, val = child key, val = child
new_expr = _translate_polymorphic_filter_definition(my_model, key, val, using=using) new_expr = _translate_polymorphic_filter_definition(
my_model, key, val, using=using
)
if new_expr: if new_expr:
node.children[i] = new_expr node.children[i] = new_expr
else: else:
@ -79,7 +86,9 @@ def translate_polymorphic_Q_object(queryset_model, potential_q_object, using=DEF
return potential_q_object return potential_q_object
def translate_polymorphic_filter_definitions_in_args(queryset_model, args, using=DEFAULT_DB_ALIAS): def translate_polymorphic_filter_definitions_in_args(
queryset_model, args, using=DEFAULT_DB_ALIAS
):
""" """
Translate the non-keyword argument list for PolymorphicQuerySet.filter() Translate the non-keyword argument list for PolymorphicQuerySet.filter()
@ -90,10 +99,15 @@ def translate_polymorphic_filter_definitions_in_args(queryset_model, args, using
Returns: modified Q objects Returns: modified Q objects
""" """
return [translate_polymorphic_Q_object(queryset_model, copy.deepcopy(q), using=using) for q in args] return [
translate_polymorphic_Q_object(queryset_model, copy.deepcopy(q), using=using)
for q in args
]
def _translate_polymorphic_filter_definition(queryset_model, field_path, field_val, using=DEFAULT_DB_ALIAS): def _translate_polymorphic_filter_definition(
queryset_model, field_path, field_val, using=DEFAULT_DB_ALIAS
):
""" """
Translate a keyword argument (field_path=field_val), as used for Translate a keyword argument (field_path=field_val), as used for
PolymorphicQuerySet.filter()-like functions (and Q objects). PolymorphicQuerySet.filter()-like functions (and Q objects).
@ -107,11 +121,11 @@ def _translate_polymorphic_filter_definition(queryset_model, field_path, field_v
# handle instance_of expressions or alternatively, # handle instance_of expressions or alternatively,
# if this is a normal Django filter expression, return None # if this is a normal Django filter expression, return None
if field_path == 'instance_of': if field_path == "instance_of":
return create_instanceof_q(field_val, using=using) return create_instanceof_q(field_val, using=using)
elif field_path == 'not_instance_of': elif field_path == "not_instance_of":
return create_instanceof_q(field_val, not_instance_of=True, using=using) return create_instanceof_q(field_val, not_instance_of=True, using=using)
elif '___' not in field_path: elif "___" not in field_path:
return None # no change return None # no change
# filter expression contains '___' (i.e. filter for polymorphic field) # filter expression contains '___' (i.e. filter for polymorphic field)
@ -133,23 +147,32 @@ def translate_polymorphic_field_path(queryset_model, field_path):
if not isinstance(field_path, compat.string_types): if not isinstance(field_path, compat.string_types):
raise ValueError("Expected field name as string: {0}".format(field_path)) raise ValueError("Expected field name as string: {0}".format(field_path))
classname, sep, pure_field_path = field_path.partition('___') classname, sep, pure_field_path = field_path.partition("___")
if not sep: if not sep:
return field_path return field_path
assert classname, 'PolymorphicModel: %s: bad field specification' % field_path assert classname, "PolymorphicModel: %s: bad field specification" % field_path
negated = False negated = False
if classname[0] == '-': if classname[0] == "-":
negated = True negated = True
classname = classname.lstrip('-') classname = classname.lstrip("-")
if '__' in classname: if "__" in classname:
# the user has app label prepended to class name via __ => use Django's get_model function # the user has app label prepended to class name via __ => use Django's get_model function
appname, sep, classname = classname.partition('__') appname, sep, classname = classname.partition("__")
model = apps.get_model(appname, classname) model = apps.get_model(appname, classname)
assert model, 'PolymorphicModel: model %s (in app %s) not found!' % (model.__name__, appname) assert model, "PolymorphicModel: model %s (in app %s) not found!" % (
model.__name__,
appname,
)
if not issubclass(model, queryset_model): if not issubclass(model, queryset_model):
e = 'PolymorphicModel: queryset filter error: "' + model.__name__ + '" is not derived from "' + queryset_model.__name__ + '"' e = (
'PolymorphicModel: queryset filter error: "'
+ model.__name__
+ '" is not derived from "'
+ queryset_model.__name__
+ '"'
)
raise AssertionError(e) raise AssertionError(e)
else: else:
@ -171,18 +194,21 @@ def translate_polymorphic_field_path(queryset_model, field_path):
submodels = _get_all_sub_models(queryset_model) submodels = _get_all_sub_models(queryset_model)
model = submodels.get(classname, None) model = submodels.get(classname, None)
assert model, 'PolymorphicModel: model %s not found (not a subclass of %s)!' % (classname, queryset_model.__name__) assert model, "PolymorphicModel: model %s not found (not a subclass of %s)!" % (
classname,
queryset_model.__name__,
)
basepath = _create_base_path(queryset_model, model) basepath = _create_base_path(queryset_model, model)
if negated: if negated:
newpath = '-' newpath = "-"
else: else:
newpath = '' newpath = ""
newpath += basepath newpath += basepath
if basepath: if basepath:
newpath += '__' newpath += "__"
newpath += pure_field_path newpath += pure_field_path
return newpath return newpath
@ -199,11 +225,13 @@ def _get_all_sub_models(base_model):
# model name is occurring twice in submodel inheritance tree => Error # model name is occurring twice in submodel inheritance tree => Error
if model.__name__ in result and model != result[model.__name__]: if model.__name__ in result and model != result[model.__name__]:
raise FieldError( raise FieldError(
'PolymorphicModel: model name alone is ambiguous: %s.%s and %s.%s match!\n' "PolymorphicModel: model name alone is ambiguous: %s.%s and %s.%s match!\n"
'In this case, please use the syntax: applabel__ModelName___field' % ( "In this case, please use the syntax: applabel__ModelName___field"
model._meta.app_label, model.__name__, % (
model._meta.app_label,
model.__name__,
result[model.__name__]._meta.app_label, result[model.__name__]._meta.app_label,
result[model.__name__].__name__ result[model.__name__].__name__,
) )
) )
@ -225,8 +253,8 @@ def _create_base_path(baseclass, myclass):
if b._meta.abstract or b._meta.proxy: if b._meta.abstract or b._meta.proxy:
return _get_query_related_name(myclass) return _get_query_related_name(myclass)
else: else:
return path + '__' + _get_query_related_name(myclass) return path + "__" + _get_query_related_name(myclass)
return '' return ""
def _get_query_related_name(myclass): def _get_query_related_name(myclass):
@ -256,12 +284,13 @@ def create_instanceof_q(modellist, not_instance_of=False, using=DEFAULT_DB_ALIAS
if not isinstance(modellist, (list, tuple)): if not isinstance(modellist, (list, tuple)):
from .models import PolymorphicModel from .models import PolymorphicModel
if issubclass(modellist, PolymorphicModel): if issubclass(modellist, PolymorphicModel):
modellist = [modellist] modellist = [modellist]
else: else:
raise TypeError( raise TypeError(
'PolymorphicModel: instance_of expects a list of (polymorphic) ' "PolymorphicModel: instance_of expects a list of (polymorphic) "
'models or a single (polymorphic) model' "models or a single (polymorphic) model"
) )
contenttype_ids = _get_mro_content_type_ids(modellist, using) contenttype_ids = _get_mro_content_type_ids(modellist, using)
@ -274,7 +303,9 @@ def create_instanceof_q(modellist, not_instance_of=False, using=DEFAULT_DB_ALIAS
def _get_mro_content_type_ids(models, using): def _get_mro_content_type_ids(models, using):
contenttype_ids = set() contenttype_ids = set()
for model in models: for model in models:
ct = ContentType.objects.db_manager(using).get_for_model(model, for_concrete_model=False) ct = ContentType.objects.db_manager(using).get_for_model(
model, for_concrete_model=False
)
contenttype_ids.add(ct.pk) contenttype_ids.add(ct.pk)
subclasses = model.__subclasses__() subclasses = model.__subclasses__()
if subclasses: if subclasses:

View File

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

View File

@ -1,14 +1,14 @@
from django.template import Library, Node, TemplateSyntaxError from django.template import Library, Node, TemplateSyntaxError
from polymorphic import compat from polymorphic import compat
register = Library() register = Library()
class BreadcrumbScope(Node): class BreadcrumbScope(Node):
def __init__(self, base_opts, nodelist): def __init__(self, base_opts, nodelist):
self.base_opts = base_opts self.base_opts = base_opts
self.nodelist = nodelist # Note, takes advantage of Node.child_nodelists self.nodelist = nodelist # Note, takes advantage of Node.child_nodelists
@classmethod @classmethod
def parse(cls, parser, token): def parse(cls, parser, token):
@ -16,15 +16,14 @@ class BreadcrumbScope(Node):
if len(bits) == 2: if len(bits) == 2:
(tagname, base_opts) = bits (tagname, base_opts) = bits
base_opts = parser.compile_filter(base_opts) base_opts = parser.compile_filter(base_opts)
nodelist = parser.parse(('endbreadcrumb_scope',)) nodelist = parser.parse(("endbreadcrumb_scope",))
parser.delete_first_token() parser.delete_first_token()
return cls( return cls(base_opts=base_opts, nodelist=nodelist)
base_opts=base_opts,
nodelist=nodelist
)
else: else:
raise TemplateSyntaxError("{0} tag expects 1 argument".format(token.contents[0])) raise TemplateSyntaxError(
"{0} tag expects 1 argument".format(token.contents[0])
)
def render(self, context): def render(self, context):
# app_label is really hard to overwrite in the standard Django ModelAdmin. # app_label is really hard to overwrite in the standard Django ModelAdmin.
@ -34,8 +33,8 @@ class BreadcrumbScope(Node):
new_vars = {} new_vars = {}
if base_opts and not isinstance(base_opts, compat.string_types): if base_opts and not isinstance(base_opts, compat.string_types):
new_vars = { new_vars = {
'app_label': base_opts.app_label, # What this is all about "app_label": base_opts.app_label, # What this is all about
'opts': base_opts, "opts": base_opts,
} }
new_scope = context.push() new_scope = context.push()

View File

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

View File

@ -12,6 +12,7 @@ class AdminTestCase(TestCase):
""" """
Testing the admin site Testing the admin site
""" """
#: The model to test #: The model to test
model = None model = None
#: The admin class to test #: The admin class to test
@ -20,7 +21,9 @@ class AdminTestCase(TestCase):
@classmethod @classmethod
def setUpClass(cls): def setUpClass(cls):
super(AdminTestCase, cls).setUpClass() super(AdminTestCase, cls).setUpClass()
cls.admin_user = User.objects.create_superuser('admin', 'admin@example.org', password='admin') cls.admin_user = User.objects.create_superuser(
"admin", "admin@example.org", password="admin"
)
def setUp(self): def setUp(self):
super(AdminTestCase, self).setUp() super(AdminTestCase, self).setUp()
@ -37,9 +40,11 @@ class AdminTestCase(TestCase):
def register(self, model): def register(self, model):
"""Decorator, like admin.register()""" """Decorator, like admin.register()"""
def _dec(admin_class): def _dec(admin_class):
self.admin_register(model, admin_class) self.admin_register(model, admin_class)
return admin_class return admin_class
return _dec return _dec
def admin_register(self, model, admin_site): def admin_register(self, model, admin_site):
@ -48,9 +53,7 @@ class AdminTestCase(TestCase):
# Make sure the URLs are reachable by reverse() # Make sure the URLs are reachable by reverse()
clear_url_caches() clear_url_caches()
set_urlconf(tuple([ set_urlconf(tuple([url("^tmp-admin/", self.admin_site.urls)]))
url('^tmp-admin/', self.admin_site.urls)
]))
def get_admin_instance(self, model): def get_admin_instance(self, model):
try: try:
@ -66,40 +69,42 @@ class AdminTestCase(TestCase):
def get_add_url(self, model): def get_add_url(self, model):
admin_instance = self.get_admin_instance(model) admin_instance = self.get_admin_instance(model)
return reverse(admin_urlname(admin_instance.opts, 'add')) return reverse(admin_urlname(admin_instance.opts, "add"))
def get_changelist_url(self, model): def get_changelist_url(self, model):
admin_instance = self.get_admin_instance(model) admin_instance = self.get_admin_instance(model)
return reverse(admin_urlname(admin_instance.opts, 'changelist')) return reverse(admin_urlname(admin_instance.opts, "changelist"))
def get_change_url(self, model, object_id): def get_change_url(self, model, object_id):
admin_instance = self.get_admin_instance(model) admin_instance = self.get_admin_instance(model)
return reverse(admin_urlname(admin_instance.opts, 'change'), args=(object_id,)) return reverse(admin_urlname(admin_instance.opts, "change"), args=(object_id,))
def get_history_url(self, model, object_id): def get_history_url(self, model, object_id):
admin_instance = self.get_admin_instance(model) admin_instance = self.get_admin_instance(model)
return reverse(admin_urlname(admin_instance.opts, 'history'), args=(object_id,)) return reverse(admin_urlname(admin_instance.opts, "history"), args=(object_id,))
def get_delete_url(self, model, object_id): def get_delete_url(self, model, object_id):
admin_instance = self.get_admin_instance(model) admin_instance = self.get_admin_instance(model)
return reverse(admin_urlname(admin_instance.opts, 'delete'), args=(object_id,)) return reverse(admin_urlname(admin_instance.opts, "delete"), args=(object_id,))
def admin_get_add(self, model, qs=''): def admin_get_add(self, model, qs=""):
""" """
Make a direct "add" call to the admin page, circumvening login checks. Make a direct "add" call to the admin page, circumvening login checks.
""" """
admin_instance = self.get_admin_instance(model) admin_instance = self.get_admin_instance(model)
request = self.create_admin_request('get', self.get_add_url(model) + qs) request = self.create_admin_request("get", self.get_add_url(model) + qs)
response = admin_instance.add_view(request) response = admin_instance.add_view(request)
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
return response return response
def admin_post_add(self, model, formdata, qs=''): def admin_post_add(self, model, formdata, qs=""):
""" """
Make a direct "add" call to the admin page, circumvening login checks. Make a direct "add" call to the admin page, circumvening login checks.
""" """
admin_instance = self.get_admin_instance(model) admin_instance = self.get_admin_instance(model)
request = self.create_admin_request('post', self.get_add_url(model) + qs, data=formdata) request = self.create_admin_request(
"post", self.get_add_url(model) + qs, data=formdata
)
response = admin_instance.add_view(request) response = admin_instance.add_view(request)
self.assertFormSuccess(request.path, response) self.assertFormSuccess(request.path, response)
return response return response
@ -109,7 +114,7 @@ class AdminTestCase(TestCase):
Make a direct "add" call to the admin page, circumvening login checks. Make a direct "add" call to the admin page, circumvening login checks.
""" """
admin_instance = self.get_admin_instance(model) admin_instance = self.get_admin_instance(model)
request = self.create_admin_request('get', self.get_changelist_url(model)) request = self.create_admin_request("get", self.get_changelist_url(model))
response = admin_instance.changelist_view(request) response = admin_instance.changelist_view(request)
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
return response return response
@ -119,7 +124,9 @@ class AdminTestCase(TestCase):
Perform a GET request on the admin page Perform a GET request on the admin page
""" """
admin_instance = self.get_admin_instance(model) admin_instance = self.get_admin_instance(model)
request = self.create_admin_request('get', self.get_change_url(model, object_id), data=query, **extra) request = self.create_admin_request(
"get", self.get_change_url(model, object_id), data=query, **extra
)
response = admin_instance.change_view(request, str(object_id)) response = admin_instance.change_view(request, str(object_id))
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
return response return response
@ -129,7 +136,9 @@ class AdminTestCase(TestCase):
Make a direct "add" call to the admin page, circumvening login checks. Make a direct "add" call to the admin page, circumvening login checks.
""" """
admin_instance = self.get_admin_instance(model) admin_instance = self.get_admin_instance(model)
request = self.create_admin_request('post', self.get_change_url(model, object_id), data=formdata, **extra) request = self.create_admin_request(
"post", self.get_change_url(model, object_id), data=formdata, **extra
)
response = admin_instance.change_view(request, str(object_id)) response = admin_instance.change_view(request, str(object_id))
self.assertFormSuccess(request.path, response) self.assertFormSuccess(request.path, response)
return response return response
@ -139,7 +148,9 @@ class AdminTestCase(TestCase):
Perform a GET request on the admin page Perform a GET request on the admin page
""" """
admin_instance = self.get_admin_instance(model) admin_instance = self.get_admin_instance(model)
request = self.create_admin_request('get', self.get_history_url(model, object_id), data=query, **extra) request = self.create_admin_request(
"get", self.get_history_url(model, object_id), data=query, **extra
)
response = admin_instance.history_view(request, str(object_id)) response = admin_instance.history_view(request, str(object_id))
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
return response return response
@ -149,7 +160,9 @@ class AdminTestCase(TestCase):
Perform a GET request on the admin delete page Perform a GET request on the admin delete page
""" """
admin_instance = self.get_admin_instance(model) admin_instance = self.get_admin_instance(model)
request = self.create_admin_request('get', self.get_delete_url(model, object_id), data=query, **extra) request = self.create_admin_request(
"get", self.get_delete_url(model, object_id), data=query, **extra
)
response = admin_instance.delete_view(request, str(object_id)) response = admin_instance.delete_view(request, str(object_id))
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
return response return response
@ -159,12 +172,16 @@ class AdminTestCase(TestCase):
Make a direct "add" call to the admin page, circumvening login checks. Make a direct "add" call to the admin page, circumvening login checks.
""" """
if not extra: if not extra:
extra = {'data': {'post': 'yes'}} extra = {"data": {"post": "yes"}}
admin_instance = self.get_admin_instance(model) admin_instance = self.get_admin_instance(model)
request = self.create_admin_request('post', self.get_delete_url(model, object_id), **extra) request = self.create_admin_request(
"post", self.get_delete_url(model, object_id), **extra
)
response = admin_instance.delete_view(request, str(object_id)) response = admin_instance.delete_view(request, str(object_id))
self.assertEqual(response.status_code, 302, "Form errors in calling {0}".format(request.path)) self.assertEqual(
response.status_code, 302, "Form errors in calling {0}".format(request.path)
)
return response return response
def create_admin_request(self, method, url, data=None, **extra): def create_admin_request(self, method, url, data=None, **extra):
@ -174,8 +191,8 @@ class AdminTestCase(TestCase):
factory_method = getattr(RequestFactory(), method) factory_method = getattr(RequestFactory(), method)
if data is not None: if data is not None:
if method != 'get': if method != "get":
data['csrfmiddlewaretoken'] = 'foo' data["csrfmiddlewaretoken"] = "foo"
dummy_request = factory_method(url, data=data) dummy_request = factory_method(url, data=data)
dummy_request.user = self.admin_user dummy_request.user = self.admin_user
@ -185,7 +202,7 @@ class AdminTestCase(TestCase):
# data = base_data # data = base_data
request = factory_method(url, data=data, **extra) request = factory_method(url, data=data, **extra)
request.COOKIES[settings.CSRF_COOKIE_NAME] = 'foo' request.COOKIES[settings.CSRF_COOKIE_NAME] = "foo"
request.csrf_processing_done = True request.csrf_processing_done = True
# Add properties which middleware would typically do # Add properties which middleware would typically do
@ -201,14 +218,21 @@ class AdminTestCase(TestCase):
self.assertIn(response.status_code, [200, 302]) self.assertIn(response.status_code, [200, 302])
if response.status_code != 302: if response.status_code != 302:
context_data = response.context_data context_data = response.context_data
if 'errors' in context_data: if "errors" in context_data:
errors = response.context_data['errors'] errors = response.context_data["errors"]
elif 'form' in context_data: elif "form" in context_data:
errors = context_data['form'].errors errors = context_data["form"].errors
else: else:
raise KeyError("Unknown field for errors in the TemplateResponse!") raise KeyError("Unknown field for errors in the TemplateResponse!")
self.assertEqual(response.status_code, 302, self.assertEqual(
"Form errors in calling {0}:\n{1}".format(request_url, errors.as_text())) response.status_code,
self.assertTrue('/login/?next=' not in response['Location'], 302,
"Received login response for {0}".format(request_url)) "Form errors in calling {0}:\n{1}".format(
request_url, errors.as_text()
),
)
self.assertTrue(
"/login/?next=" not in response["Location"],
"Received login response for {0}".format(request_url),
)

File diff suppressed because it is too large Load Diff

View File

@ -1,7 +1,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import django
import uuid import uuid
import django
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
from django.db import models from django.db import models
from django.db.models.query import QuerySet from django.db.models.query import QuerySet
@ -9,7 +9,11 @@ from django.db.models.query import QuerySet
from polymorphic.managers import PolymorphicManager from polymorphic.managers import PolymorphicManager
from polymorphic.models import PolymorphicModel from polymorphic.models import PolymorphicModel
from polymorphic.query import PolymorphicQuerySet from polymorphic.query import PolymorphicQuerySet
from polymorphic.showfields import ShowFieldContent, ShowFieldType, ShowFieldTypeAndContent from polymorphic.showfields import (
ShowFieldContent,
ShowFieldType,
ShowFieldTypeAndContent,
)
class PlainA(models.Model): class PlainA(models.Model):
@ -59,17 +63,17 @@ class ModelExtraExternal(models.Model):
class ModelShow1(ShowFieldType, PolymorphicModel): class ModelShow1(ShowFieldType, PolymorphicModel):
field1 = models.CharField(max_length=10) field1 = models.CharField(max_length=10)
m2m = models.ManyToManyField('self') m2m = models.ManyToManyField("self")
class ModelShow2(ShowFieldContent, PolymorphicModel): class ModelShow2(ShowFieldContent, PolymorphicModel):
field1 = models.CharField(max_length=10) field1 = models.CharField(max_length=10)
m2m = models.ManyToManyField('self') m2m = models.ManyToManyField("self")
class ModelShow3(ShowFieldTypeAndContent, PolymorphicModel): class ModelShow3(ShowFieldTypeAndContent, PolymorphicModel):
field1 = models.CharField(max_length=10) field1 = models.CharField(max_length=10)
m2m = models.ManyToManyField('self') m2m = models.ManyToManyField("self")
class ModelShow1_plain(PolymorphicModel): class ModelShow1_plain(PolymorphicModel):
@ -108,8 +112,10 @@ class Enhance_Inherit(Enhance_Base, Enhance_Plain):
class RelationBase(ShowFieldTypeAndContent, PolymorphicModel): class RelationBase(ShowFieldTypeAndContent, PolymorphicModel):
field_base = models.CharField(max_length=10) field_base = models.CharField(max_length=10)
fk = models.ForeignKey('self', on_delete=models.CASCADE, null=True, related_name='relationbase_set') fk = models.ForeignKey(
m2m = models.ManyToManyField('self') "self", on_delete=models.CASCADE, null=True, related_name="relationbase_set"
)
m2m = models.ManyToManyField("self")
class RelationA(RelationBase): class RelationA(RelationBase):
@ -143,21 +149,24 @@ class ModelUnderRelParent(PolymorphicModel):
class ModelUnderRelChild(PolymorphicModel): class ModelUnderRelChild(PolymorphicModel):
parent = models.ForeignKey(ModelUnderRelParent, on_delete=models.CASCADE, related_name='children') parent = models.ForeignKey(
ModelUnderRelParent, on_delete=models.CASCADE, related_name="children"
)
_private2 = models.CharField(max_length=10) _private2 = models.CharField(max_length=10)
class MyManagerQuerySet(PolymorphicQuerySet): class MyManagerQuerySet(PolymorphicQuerySet):
def my_queryset_foo(self): def my_queryset_foo(self):
return self.all() # Just a method to prove the existance of the custom queryset. return (
self.all()
) # Just a method to prove the existance of the custom queryset.
class MyManager(PolymorphicManager): class MyManager(PolymorphicManager):
queryset_class = MyManagerQuerySet queryset_class = MyManagerQuerySet
def get_queryset(self): def get_queryset(self):
return super(MyManager, self).get_queryset().order_by('-field1') return super(MyManager, self).get_queryset().order_by("-field1")
def my_queryset_foo(self): def my_queryset_foo(self):
return self.all().my_queryset_foo() return self.all().my_queryset_foo()
@ -196,12 +205,15 @@ class MROBase2(MROBase1):
class MROBase3(models.Model): class MROBase3(models.Model):
base_3_id = models.AutoField(primary_key=True) # make sure 'id' field doesn't clash, detected by Django 1.11 base_3_id = models.AutoField(
primary_key=True
) # make sure 'id' field doesn't clash, detected by Django 1.11
objects = models.Manager() objects = models.Manager()
class MRODerived(MROBase2, MROBase3): class MRODerived(MROBase2, MROBase3):
if django.VERSION < (3, 0): if django.VERSION < (3, 0):
class Meta: class Meta:
manager_inheritance_from_future = True manager_inheritance_from_future = True
@ -212,18 +224,20 @@ class ParentModelWithManager(PolymorphicModel):
class ChildModelWithManager(PolymorphicModel): class ChildModelWithManager(PolymorphicModel):
# Also test whether foreign keys receive the manager: # Also test whether foreign keys receive the manager:
fk = models.ForeignKey(ParentModelWithManager, on_delete=models.CASCADE, related_name='childmodel_set') fk = models.ForeignKey(
ParentModelWithManager, on_delete=models.CASCADE, related_name="childmodel_set"
)
objects = MyManager() objects = MyManager()
class PlainMyManagerQuerySet(QuerySet): class PlainMyManagerQuerySet(QuerySet):
def my_queryset_foo(self): def my_queryset_foo(self):
return self.all() # Just a method to prove the existence of the custom queryset. return (
self.all()
) # Just a method to prove the existence of the custom queryset.
class PlainMyManager(models.Manager): class PlainMyManager(models.Manager):
def my_queryset_foo(self): def my_queryset_foo(self):
return self.get_queryset().my_queryset_foo() return self.get_queryset().my_queryset_foo()
@ -236,7 +250,11 @@ class PlainParentModelWithManager(models.Model):
class PlainChildModelWithManager(models.Model): class PlainChildModelWithManager(models.Model):
fk = models.ForeignKey(PlainParentModelWithManager, on_delete=models.CASCADE, related_name='childmodel_set') fk = models.ForeignKey(
PlainParentModelWithManager,
on_delete=models.CASCADE,
related_name="childmodel_set",
)
objects = PlainMyManager() objects = PlainMyManager()
@ -270,14 +288,14 @@ class InitTestModel(ShowFieldType, PolymorphicModel):
bar = models.CharField(max_length=100) bar = models.CharField(max_length=100)
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
kwargs['bar'] = self.x() kwargs["bar"] = self.x()
super(InitTestModel, self).__init__(*args, **kwargs) super(InitTestModel, self).__init__(*args, **kwargs)
class InitTestModelSubclass(InitTestModel): class InitTestModelSubclass(InitTestModel):
def x(self): def x(self):
return 'XYZ' return "XYZ"
# models from github issue # models from github issue
@ -319,6 +337,7 @@ class UUIDPlainB(UUIDPlainA):
class UUIDPlainC(UUIDPlainB): class UUIDPlainC(UUIDPlainB):
field3 = models.CharField(max_length=10) field3 = models.CharField(max_length=10)
# base -> proxy # base -> proxy
@ -327,7 +346,6 @@ class ProxyBase(PolymorphicModel):
class ProxyChild(ProxyBase): class ProxyChild(ProxyBase):
class Meta: class Meta:
proxy = True proxy = True
@ -335,6 +353,7 @@ class ProxyChild(ProxyBase):
class NonProxyChild(ProxyBase): class NonProxyChild(ProxyBase):
name = models.CharField(max_length=10) name = models.CharField(max_length=10)
# base -> proxy -> real models # base -> proxy -> real models
@ -343,7 +362,6 @@ class ProxiedBase(ShowFieldTypeAndContent, PolymorphicModel):
class ProxyModelBase(ProxiedBase): class ProxyModelBase(ProxiedBase):
class Meta: class Meta:
proxy = True proxy = True
@ -364,14 +382,20 @@ class ProxyModelB(ProxyModelBase):
# with related field 'ContentType.relatednameclash_set'." (reported by Andrew Ingram) # with related field 'ContentType.relatednameclash_set'." (reported by Andrew Ingram)
# fixed with related_name # fixed with related_name
class RelatedNameClash(ShowFieldType, PolymorphicModel): class RelatedNameClash(ShowFieldType, PolymorphicModel):
ctype = models.ForeignKey(ContentType, on_delete=models.CASCADE, null=True, editable=False) ctype = models.ForeignKey(
ContentType, on_delete=models.CASCADE, null=True, editable=False
)
# class with a parent_link to superclass, and a related_name back to subclass # class with a parent_link to superclass, and a related_name back to subclass
class TestParentLinkAndRelatedName(ModelShow1_plain): class TestParentLinkAndRelatedName(ModelShow1_plain):
superclass = models.OneToOneField( superclass = models.OneToOneField(
ModelShow1_plain, on_delete=models.CASCADE, parent_link=True, related_name='related_name_subclass' ModelShow1_plain,
on_delete=models.CASCADE,
parent_link=True,
related_name="related_name_subclass",
) )
@ -398,7 +422,7 @@ class AbstractModel(PolymorphicModel):
class SwappableModel(AbstractModel): class SwappableModel(AbstractModel):
class Meta: class Meta:
swappable = 'POLYMORPHIC_TEST_SWAPPABLE' swappable = "POLYMORPHIC_TEST_SWAPPABLE"
class SwappedModel(AbstractModel): class SwappedModel(AbstractModel):
@ -410,7 +434,9 @@ class InlineParent(models.Model):
class InlineModelA(PolymorphicModel): class InlineModelA(PolymorphicModel):
parent = models.ForeignKey(InlineParent, related_name='inline_children', on_delete=models.CASCADE) parent = models.ForeignKey(
InlineParent, related_name="inline_children", on_delete=models.CASCADE
)
field1 = models.CharField(max_length=10) field1 = models.CharField(max_length=10)
@ -434,13 +460,11 @@ class Duck(PolymorphicModel):
class RedheadDuck(Duck): class RedheadDuck(Duck):
class Meta: class Meta:
proxy = True proxy = True
class RubberDuck(Duck): class RubberDuck(Duck):
class Meta: class Meta:
proxy = True proxy = True
@ -454,22 +478,22 @@ class MultiTableDerived(MultiTableBase):
class SubclassSelectorAbstractBaseModel(PolymorphicModel): class SubclassSelectorAbstractBaseModel(PolymorphicModel):
base_field = models.CharField(max_length=10, default='test_bf') base_field = models.CharField(max_length=10, default="test_bf")
class SubclassSelectorAbstractModel(SubclassSelectorAbstractBaseModel): class SubclassSelectorAbstractModel(SubclassSelectorAbstractBaseModel):
abstract_field = models.CharField(max_length=10, default='test_af') abstract_field = models.CharField(max_length=10, default="test_af")
class Meta: class Meta:
abstract = True abstract = True
class SubclassSelectorAbstractConcreteModel(SubclassSelectorAbstractModel): class SubclassSelectorAbstractConcreteModel(SubclassSelectorAbstractModel):
concrete_field = models.CharField(max_length=10, default='test_cf') concrete_field = models.CharField(max_length=10, default="test_cf")
class SubclassSelectorProxyBaseModel(PolymorphicModel): class SubclassSelectorProxyBaseModel(PolymorphicModel):
base_field = models.CharField(max_length=10, default='test_bf') base_field = models.CharField(max_length=10, default="test_bf")
class SubclassSelectorProxyModel(SubclassSelectorProxyBaseModel): class SubclassSelectorProxyModel(SubclassSelectorProxyBaseModel):
@ -478,4 +502,4 @@ class SubclassSelectorProxyModel(SubclassSelectorProxyBaseModel):
class SubclassSelectorProxyConcreteModel(SubclassSelectorProxyModel): class SubclassSelectorProxyConcreteModel(SubclassSelectorProxyModel):
concrete_field = models.CharField(max_length=10, default='test_cf') concrete_field = models.CharField(max_length=10, default="test_cf")

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

@ -1,9 +1,9 @@
from django.test import TestCase from django.test import TestCase
from polymorphic.tests.models import Bottom, Middle, Top from polymorphic.tests.models import Bottom, Middle, Top
class RegressionTests(TestCase): class RegressionTests(TestCase):
def test_for_query_result_incomplete_with_inheritance(self): def test_for_query_result_incomplete_with_inheritance(self):
""" https://github.com/bconstantin/django_polymorphic/issues/15 """ """ https://github.com/bconstantin/django_polymorphic/issues/15 """
@ -15,10 +15,16 @@ class RegressionTests(TestCase):
bottom.save() bottom.save()
expected_queryset = [top, middle, bottom] expected_queryset = [top, middle, bottom]
self.assertQuerysetEqual(Top.objects.order_by('pk'), [repr(r) for r in expected_queryset]) self.assertQuerysetEqual(
Top.objects.order_by("pk"), [repr(r) for r in expected_queryset]
)
expected_queryset = [middle, bottom] expected_queryset = [middle, bottom]
self.assertQuerysetEqual(Middle.objects.order_by('pk'), [repr(r) for r in expected_queryset]) self.assertQuerysetEqual(
Middle.objects.order_by("pk"), [repr(r) for r in expected_queryset]
)
expected_queryset = [bottom] expected_queryset = [bottom]
self.assertQuerysetEqual(Bottom.objects.order_by('pk'), [repr(r) for r in expected_queryset]) self.assertQuerysetEqual(
Bottom.objects.order_by("pk"), [repr(r) for r in expected_queryset]
)

View File

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

View File

@ -2,9 +2,9 @@ import sys
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
from django.db import DEFAULT_DB_ALIAS from django.db import DEFAULT_DB_ALIAS
from polymorphic.models import PolymorphicModel
from polymorphic.base import PolymorphicModelBase from polymorphic.base import PolymorphicModelBase
from polymorphic.models import PolymorphicModel
def reset_polymorphic_ctype(*models, **filters): def reset_polymorphic_ctype(*models, **filters):
@ -16,8 +16,8 @@ def reset_polymorphic_ctype(*models, **filters):
Add ``preserve_existing=True`` to skip models which already Add ``preserve_existing=True`` to skip models which already
have a polymorphic content type. have a polymorphic content type.
""" """
using = filters.pop('using', DEFAULT_DB_ALIAS) using = filters.pop("using", DEFAULT_DB_ALIAS)
ignore_existing = filters.pop('ignore_existing', False) ignore_existing = filters.pop("ignore_existing", False)
models = sort_by_subclass(*models) models = sort_by_subclass(*models)
if ignore_existing: if ignore_existing:
@ -26,7 +26,9 @@ def reset_polymorphic_ctype(*models, **filters):
models = reversed(models) models = reversed(models)
for new_model in models: for new_model in models:
new_ct = ContentType.objects.db_manager(using).get_for_model(new_model, for_concrete_model=False) new_ct = ContentType.objects.db_manager(using).get_for_model(
new_model, for_concrete_model=False
)
qs = new_model.objects.db_manager(using) qs = new_model.objects.db_manager(using)
if ignore_existing: if ignore_existing:
@ -61,6 +63,7 @@ def sort_by_subclass(*classes):
return sorted(classes, cmp=_compare_mro) return sorted(classes, cmp=_compare_mro)
else: else:
from functools import cmp_to_key from functools import cmp_to_key
return sorted(classes, key=cmp_to_key(_compare_mro)) return sorted(classes, key=cmp_to_key(_compare_mro))
@ -69,8 +72,10 @@ def get_base_polymorphic_model(ChildModel, allow_abstract=False):
First the first concrete model in the inheritance chain that inherited from the PolymorphicModel. First the first concrete model in the inheritance chain that inherited from the PolymorphicModel.
""" """
for Model in reversed(ChildModel.mro()): for Model in reversed(ChildModel.mro()):
if isinstance(Model, PolymorphicModelBase) and \ if (
Model is not PolymorphicModel and \ isinstance(Model, PolymorphicModelBase)
(allow_abstract or not Model._meta.abstract): and Model is not PolymorphicModel
and (allow_abstract or not Model._meta.abstract)
):
return Model return Model
return None return None

View File

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

View File

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