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