haven't committed in a while oops. A fuck-ton of HTML stuff
This commit is contained in:
@@ -0,0 +1,40 @@
|
||||
"""
|
||||
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 # noqa
|
||||
from .childadmin import PolymorphicChildModelAdmin
|
||||
from .filters import PolymorphicChildModelFilter
|
||||
|
||||
# Utils
|
||||
from .forms import PolymorphicModelChoiceForm
|
||||
|
||||
# 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
|
||||
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
|
||||
|
||||
__all__ = (
|
||||
"PolymorphicParentModelAdmin",
|
||||
"PolymorphicChildModelAdmin",
|
||||
"PolymorphicModelChoiceForm",
|
||||
"PolymorphicChildModelFilter",
|
||||
"PolymorphicInlineAdminForm",
|
||||
"PolymorphicInlineAdminFormSet",
|
||||
"PolymorphicInlineSupportMixin",
|
||||
"PolymorphicInlineModelAdmin",
|
||||
"StackedPolymorphicInline",
|
||||
"GenericPolymorphicInlineModelAdmin",
|
||||
"GenericStackedPolymorphicInline",
|
||||
)
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
246
venv/lib/python3.8/site-packages/polymorphic/admin/childadmin.py
Normal file
246
venv/lib/python3.8/site-packages/polymorphic/admin/childadmin.py
Normal file
@@ -0,0 +1,246 @@
|
||||
"""
|
||||
The child admin displays the change/delete view of the subclass model.
|
||||
"""
|
||||
import inspect
|
||||
|
||||
from django.contrib import admin
|
||||
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
|
||||
|
||||
|
||||
class ParentAdminNotRegistered(RuntimeError):
|
||||
"The admin site for the model is not registered."
|
||||
|
||||
|
||||
class PolymorphicChildModelAdmin(admin.ModelAdmin):
|
||||
"""
|
||||
The *optional* base class for the admin interface of derived models.
|
||||
|
||||
This base class defines some convenience behavior for the admin interface:
|
||||
|
||||
* It corrects the breadcrumbs in the admin pages.
|
||||
* It adds the base model to the template lookup paths.
|
||||
* It allows to set ``base_form`` so the derived class will automatically include other fields in the form.
|
||||
* It allows to set ``base_fieldsets`` so the derived class will automatically display any extra fields.
|
||||
"""
|
||||
|
||||
#: The base model that the class uses (auto-detected if not set explicitly)
|
||||
base_model = None
|
||||
|
||||
#: By setting ``base_form`` instead of ``form``, any subclass fields are automatically added to the form.
|
||||
#: This is useful when your model admin class is inherited by others.
|
||||
base_form = None
|
||||
|
||||
#: By setting ``base_fieldsets`` instead of ``fieldsets``,
|
||||
#: any subclass fields can be automatically added.
|
||||
#: This is useful when your model admin class is inherited by others.
|
||||
base_fieldsets = None
|
||||
|
||||
#: Default title for extra fieldset
|
||||
extra_fieldset_title = _("Contents")
|
||||
|
||||
#: Whether the child admin model should be visible in the admin index page.
|
||||
show_in_index = False
|
||||
|
||||
def __init__(self, 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)
|
||||
|
||||
def get_form(self, request, obj=None, **kwargs):
|
||||
# The django admin validation requires the form to have a 'class Meta: model = ..'
|
||||
# attribute, or it will complain that the fields are missing.
|
||||
# However, this enforces all derived ModelAdmin classes to redefine the model as well,
|
||||
# because they need to explicitly set the model again - it will stick with the base model.
|
||||
#
|
||||
# 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)
|
||||
|
||||
# prevent infinite recursion when this is called from get_subclass_fields
|
||||
if not self.fieldsets and not self.fields:
|
||||
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}
|
||||
return super(PolymorphicChildModelAdmin, self).get_model_perms(request)
|
||||
|
||||
@property
|
||||
def change_form_template(self):
|
||||
opts = self.model._meta
|
||||
app_label = opts.app_label
|
||||
|
||||
# Pass the base options
|
||||
base_opts = self.base_model._meta
|
||||
base_app_label = base_opts.app_label
|
||||
|
||||
return [
|
||||
"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/change_form.html" % base_app_label,
|
||||
"admin/polymorphic/change_form.html",
|
||||
"admin/change_form.html",
|
||||
]
|
||||
|
||||
@property
|
||||
def delete_confirmation_template(self):
|
||||
opts = self.model._meta
|
||||
app_label = opts.app_label
|
||||
|
||||
# Pass the base options
|
||||
base_opts = self.base_model._meta
|
||||
base_app_label = base_opts.app_label
|
||||
|
||||
return [
|
||||
"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/delete_confirmation.html" % base_app_label,
|
||||
"admin/polymorphic/delete_confirmation.html",
|
||||
"admin/delete_confirmation.html",
|
||||
]
|
||||
|
||||
@property
|
||||
def object_history_template(self):
|
||||
opts = self.model._meta
|
||||
app_label = opts.app_label
|
||||
|
||||
# Pass the base options
|
||||
base_opts = self.base_model._meta
|
||||
base_app_label = base_opts.app_label
|
||||
|
||||
return [
|
||||
"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/object_history.html" % base_app_label,
|
||||
"admin/polymorphic/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
|
||||
if parent_model == self.model:
|
||||
# when parent_model is in among child_models, just return super instance
|
||||
return super(PolymorphicChildModelAdmin, self)
|
||||
|
||||
try:
|
||||
return self.admin_site._registry[parent_model]
|
||||
except KeyError:
|
||||
# Admin is not registered for polymorphic_ctype model, but perhaps it's registered
|
||||
# for a intermediate proxy model, between the parent_model and this model.
|
||||
for klass in inspect.getmro(self.model):
|
||||
if not issubclass(klass, parent_model):
|
||||
continue # e.g. found a mixin.
|
||||
|
||||
# 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
|
||||
):
|
||||
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)
|
||||
)
|
||||
|
||||
def response_post_save_add(self, request, obj):
|
||||
return self._get_parent_admin().response_post_save_add(request, obj)
|
||||
|
||||
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 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
|
||||
)
|
||||
|
||||
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}
|
||||
if extra_context:
|
||||
context.update(extra_context)
|
||||
return super(PolymorphicChildModelAdmin, self).history_view(
|
||||
request, object_id, extra_context=context
|
||||
)
|
||||
|
||||
# ---- Extra: improving the form/fieldset default display ----
|
||||
|
||||
def get_base_fieldsets(self, request, obj=None):
|
||||
return self.base_fieldsets
|
||||
|
||||
def get_fieldsets(self, request, obj=None):
|
||||
base_fieldsets = self.get_base_fieldsets(request, obj)
|
||||
|
||||
# If subclass declares fieldsets or fields, this is respected
|
||||
if self.fieldsets or self.fields or not self.base_fieldsets:
|
||||
return super(PolymorphicChildModelAdmin, self).get_fieldsets(request, obj)
|
||||
|
||||
# Have a reasonable default fieldsets,
|
||||
# where the subclass fields are automatically included.
|
||||
other_fields = self.get_subclass_fields(request, obj)
|
||||
|
||||
if other_fields:
|
||||
return (
|
||||
base_fieldsets[0],
|
||||
(self.extra_fieldset_title, {"fields": other_fields}),
|
||||
) + base_fieldsets[1:]
|
||||
else:
|
||||
return base_fieldsets
|
||||
|
||||
def get_subclass_fields(self, request, obj=None):
|
||||
# Find out how many fields would really be on the form,
|
||||
# if it weren't restricted by declared fields.
|
||||
exclude = list(self.exclude or [])
|
||||
exclude.extend(self.get_readonly_fields(request, obj))
|
||||
|
||||
# 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)
|
||||
)
|
||||
|
||||
# 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"]:
|
||||
try:
|
||||
subclass_fields.remove(field)
|
||||
except ValueError:
|
||||
pass # field not found in form, Django will raise exception later.
|
||||
return subclass_fields
|
||||
@@ -0,0 +1,39 @@
|
||||
from django.contrib import admin
|
||||
from django.core.exceptions import PermissionDenied
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
|
||||
class PolymorphicChildModelFilter(admin.SimpleListFilter):
|
||||
"""
|
||||
An admin list filter for the PolymorphicParentModelAdmin which enables
|
||||
filtering by its child models.
|
||||
|
||||
This can be used in the parent admin:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
list_filter = (PolymorphicChildModelFilter,)
|
||||
"""
|
||||
|
||||
title = _("Type")
|
||||
parameter_name = "polymorphic_ctype"
|
||||
|
||||
def lookups(self, request, model_admin):
|
||||
return model_admin.get_child_type_choices(request, "change")
|
||||
|
||||
def queryset(self, request, queryset):
|
||||
try:
|
||||
value = int(self.value())
|
||||
except TypeError:
|
||||
value = None
|
||||
if value:
|
||||
# ensure the content type is allowed
|
||||
for choice_value, _ in self.lookup_choices:
|
||||
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
|
||||
)
|
||||
)
|
||||
return queryset
|
||||
21
venv/lib/python3.8/site-packages/polymorphic/admin/forms.py
Normal file
21
venv/lib/python3.8/site-packages/polymorphic/admin/forms.py
Normal file
@@ -0,0 +1,21 @@
|
||||
from django import forms
|
||||
from django.contrib.admin.widgets import AdminRadioSelect
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
|
||||
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"})
|
||||
)
|
||||
|
||||
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
|
||||
@@ -0,0 +1,74 @@
|
||||
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 (
|
||||
BaseGenericPolymorphicInlineFormSet,
|
||||
GenericPolymorphicFormSetChild,
|
||||
polymorphic_child_forms_factory,
|
||||
)
|
||||
|
||||
from .inlines import PolymorphicInlineModelAdmin
|
||||
|
||||
|
||||
class GenericPolymorphicInlineModelAdmin(
|
||||
PolymorphicInlineModelAdmin, GenericInlineModelAdmin
|
||||
):
|
||||
"""
|
||||
Base class for variation of inlines based on generic foreign keys.
|
||||
"""
|
||||
|
||||
#: The formset class
|
||||
formset = BaseGenericPolymorphicInlineFormSet
|
||||
|
||||
def get_formset(self, request, obj=None, **kwargs):
|
||||
"""
|
||||
Construct the generic inline formset class.
|
||||
"""
|
||||
# Construct the FormSet class. This is almost the same as parent version,
|
||||
# except that a different super is called so generic_inlineformset_factory() is used.
|
||||
# NOTE that generic_inlineformset_factory() also makes sure the GFK fields are excluded in the form.
|
||||
FormSet = GenericInlineModelAdmin.get_formset(self, request, obj=obj, **kwargs)
|
||||
|
||||
FormSet.child_forms = polymorphic_child_forms_factory(
|
||||
formset_children=self.get_formset_children(request, obj=obj)
|
||||
)
|
||||
return FormSet
|
||||
|
||||
class Child(PolymorphicInlineModelAdmin.Child):
|
||||
"""
|
||||
Variation for generic inlines.
|
||||
"""
|
||||
|
||||
# Make sure that the GFK fields are excluded from the child forms
|
||||
formset_child = GenericPolymorphicFormSetChild
|
||||
ct_field = "content_type"
|
||||
ct_fk_field = "object_id"
|
||||
|
||||
@cached_property
|
||||
def content_type(self):
|
||||
"""
|
||||
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
|
||||
)
|
||||
|
||||
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.update(kwargs)
|
||||
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"
|
||||
146
venv/lib/python3.8/site-packages/polymorphic/admin/helpers.py
Normal file
146
venv/lib/python3.8/site-packages/polymorphic/admin/helpers.py
Normal file
@@ -0,0 +1,146 @@
|
||||
"""
|
||||
Rendering utils for admin forms;
|
||||
|
||||
This makes sure that admin fieldsets/layout settings are exported to the template.
|
||||
"""
|
||||
import json
|
||||
|
||||
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
|
||||
|
||||
from polymorphic.formsets import BasePolymorphicModelFormSet
|
||||
|
||||
|
||||
class PolymorphicInlineAdminForm(InlineAdminForm):
|
||||
"""
|
||||
Expose the admin configuration for a form
|
||||
"""
|
||||
|
||||
def polymorphic_ctype_field(self):
|
||||
return AdminField(self.form, "polymorphic_ctype", False)
|
||||
|
||||
@property
|
||||
def is_empty(self):
|
||||
return "__prefix__" in self.form.prefix
|
||||
|
||||
|
||||
class PolymorphicInlineAdminFormSet(InlineAdminFormSet):
|
||||
"""
|
||||
Internally used class to expose the formset in the template.
|
||||
"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
# Assigned later via PolymorphicInlineSupportMixin later.
|
||||
self.request = kwargs.pop("request", None)
|
||||
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()
|
||||
):
|
||||
# Output the form
|
||||
model = original.get_real_instance_class()
|
||||
child_inline = self.opts.get_child_inline_instance(model)
|
||||
view_on_site_url = self.opts.get_view_on_site_url(original)
|
||||
|
||||
yield PolymorphicInlineAdminForm(
|
||||
formset=self.formset,
|
||||
form=form,
|
||||
fieldsets=self.get_child_fieldsets(child_inline),
|
||||
prepopulated_fields=self.get_child_prepopulated_fields(child_inline),
|
||||
original=original,
|
||||
readonly_fields=self.get_child_readonly_fields(child_inline),
|
||||
model_admin=child_inline,
|
||||
view_on_site_url=view_on_site_url,
|
||||
)
|
||||
|
||||
# Extra rows, and empty prefixed forms.
|
||||
for form in self.formset.extra_forms + self.formset.empty_forms:
|
||||
model = form._meta.model
|
||||
child_inline = self.opts.get_child_inline_instance(model)
|
||||
yield PolymorphicInlineAdminForm(
|
||||
formset=self.formset,
|
||||
form=form,
|
||||
fieldsets=self.get_child_fieldsets(child_inline),
|
||||
prepopulated_fields=self.get_child_prepopulated_fields(child_inline),
|
||||
original=None,
|
||||
readonly_fields=self.get_child_readonly_fields(child_inline),
|
||||
model_admin=child_inline,
|
||||
)
|
||||
|
||||
def get_child_fieldsets(self, child_inline):
|
||||
return list(child_inline.get_fieldsets(self.request, self.obj) or ())
|
||||
|
||||
def get_child_readonly_fields(self, child_inline):
|
||||
return list(child_inline.get_readonly_fields(self.request, self.obj))
|
||||
|
||||
def get_child_prepopulated_fields(self, child_inline):
|
||||
fields = self.prepopulated_fields.copy()
|
||||
fields.update(child_inline.get_prepopulated_fields(self.request, self.obj))
|
||||
return fields
|
||||
|
||||
def inline_formset_data(self):
|
||||
"""
|
||||
A JavaScript data structure for the JavaScript code
|
||||
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": [
|
||||
{
|
||||
"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):
|
||||
"""
|
||||
A Mixin to add to the regular admin, so it can work with our polymorphic inlines.
|
||||
|
||||
This mixin needs to be included in the admin that hosts the ``inlines``.
|
||||
It makes sure the generated admin forms have different fieldsets/fields
|
||||
depending on the polymorphic type of the form instance.
|
||||
|
||||
This is achieved by overwriting :func:`get_inline_formsets` to return
|
||||
an :class:`PolymorphicInlineAdminFormSet` instead of a standard Django
|
||||
:class:`~django.contrib.admin.helpers.InlineAdminFormSet` for the polymorphic formsets.
|
||||
"""
|
||||
|
||||
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)
|
||||
|
||||
for admin_formset in inline_admin_formsets:
|
||||
if isinstance(admin_formset.formset, BasePolymorphicModelFormSet):
|
||||
# This is a polymorphic formset, which belongs to our inline.
|
||||
# Downcast the admin wrapper that generates the form fields.
|
||||
admin_formset.__class__ = PolymorphicInlineAdminFormSet
|
||||
admin_formset.request = request
|
||||
admin_formset.obj = obj
|
||||
return inline_admin_formsets
|
||||
275
venv/lib/python3.8/site-packages/polymorphic/admin/inlines.py
Normal file
275
venv/lib/python3.8/site-packages/polymorphic/admin/inlines.py
Normal file
@@ -0,0 +1,275 @@
|
||||
"""
|
||||
Django Admin support for polymorphic inlines.
|
||||
|
||||
Each row in the inline can correspond with a different subclass.
|
||||
"""
|
||||
from functools import partial
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib.admin.options import InlineModelAdmin
|
||||
from django.contrib.admin.utils import flatten_fieldsets
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
from django.forms import Media
|
||||
|
||||
from polymorphic.formsets import (
|
||||
BasePolymorphicInlineFormSet,
|
||||
PolymorphicFormSetChild,
|
||||
UnsupportedChildType,
|
||||
polymorphic_child_forms_factory,
|
||||
)
|
||||
from polymorphic.formsets.utils import add_media
|
||||
|
||||
from .helpers import PolymorphicInlineSupportMixin
|
||||
|
||||
|
||||
class PolymorphicInlineModelAdmin(InlineModelAdmin):
|
||||
"""
|
||||
A polymorphic inline, where each formset row can be a different form.
|
||||
|
||||
Note that:
|
||||
|
||||
* Permissions are only checked on the base model.
|
||||
* The child inlines can't override the base model fields, only this parent inline can do that.
|
||||
"""
|
||||
|
||||
formset = BasePolymorphicInlineFormSet
|
||||
|
||||
#: The extra media to add for the polymorphic inlines effect.
|
||||
#: This can be redefined for subclasses.
|
||||
polymorphic_media = Media(
|
||||
js=(
|
||||
"admin/js/vendor/jquery/jquery{}.js".format(
|
||||
"" if settings.DEBUG else ".min"
|
||||
),
|
||||
"admin/js/jquery.init.js",
|
||||
"polymorphic/js/polymorphic_inlines.js",
|
||||
),
|
||||
css={"all": ("polymorphic/css/polymorphic_inlines.css",)},
|
||||
)
|
||||
|
||||
#: The extra forms to show
|
||||
#: By default there are no 'extra' forms as the desired type is unknown.
|
||||
#: Instead, add each new item using JavaScript that first offers a type-selection.
|
||||
extra = 0
|
||||
|
||||
#: Inlines for all model sub types that can be displayed in this inline.
|
||||
#: Each row is a :class:`PolymorphicInlineModelAdmin.Child`
|
||||
child_inlines = ()
|
||||
|
||||
def __init__(self, parent_model, admin_site):
|
||||
super(PolymorphicInlineModelAdmin, self).__init__(parent_model, admin_site)
|
||||
|
||||
# Extra check to avoid confusion
|
||||
# While we could monkeypatch the admin here, better stay explicit.
|
||||
parent_admin = admin_site._registry.get(parent_model, None)
|
||||
if parent_admin is not None: # Can be None during check
|
||||
if not isinstance(parent_admin, PolymorphicInlineSupportMixin):
|
||||
raise ImproperlyConfigured(
|
||||
"To use polymorphic inlines, add the `PolymorphicInlineSupportMixin` mixin "
|
||||
"to the ModelAdmin that hosts the inline."
|
||||
)
|
||||
|
||||
# While the inline is created per request, the 'request' object is not known here.
|
||||
# Hence, creating all child inlines unconditionally, without checking permissions.
|
||||
self.child_inline_instances = self.get_child_inline_instances()
|
||||
|
||||
# Create a lookup table
|
||||
self._child_inlines_lookup = {}
|
||||
for child_inline in self.child_inline_instances:
|
||||
self._child_inlines_lookup[child_inline.model] = child_inline
|
||||
|
||||
def get_child_inline_instances(self):
|
||||
"""
|
||||
:rtype List[PolymorphicInlineModelAdmin.Child]
|
||||
"""
|
||||
instances = []
|
||||
for ChildInlineType in self.child_inlines:
|
||||
instances.append(ChildInlineType(parent_inline=self))
|
||||
return instances
|
||||
|
||||
def get_child_inline_instance(self, model):
|
||||
"""
|
||||
Find the child inline for a given model.
|
||||
|
||||
:rtype: PolymorphicInlineModelAdmin.Child
|
||||
"""
|
||||
try:
|
||||
return self._child_inlines_lookup[model]
|
||||
except KeyError:
|
||||
raise UnsupportedChildType(
|
||||
"Model '{0}' not found in child_inlines".format(model.__name__)
|
||||
)
|
||||
|
||||
def get_formset(self, request, obj=None, **kwargs):
|
||||
"""
|
||||
Construct the inline formset class.
|
||||
|
||||
This passes all class attributes to the formset.
|
||||
|
||||
:rtype: type
|
||||
"""
|
||||
# Construct the FormSet class
|
||||
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.
|
||||
# This code line is the essence of what polymorphic_inlineformset_factory() does.
|
||||
FormSet.child_forms = polymorphic_child_forms_factory(
|
||||
formset_children=self.get_formset_children(request, obj=obj)
|
||||
)
|
||||
return FormSet
|
||||
|
||||
def get_formset_children(self, request, obj=None):
|
||||
"""
|
||||
The formset 'children' provide the details for all child models that are part of this formset.
|
||||
It provides a stripped version of the modelform/formset factory methods.
|
||||
"""
|
||||
formset_children = []
|
||||
for child_inline in self.child_inline_instances:
|
||||
# TODO: the children can be limited here per request based on permissions.
|
||||
formset_children.append(child_inline.get_formset_child(request, obj=obj))
|
||||
return formset_children
|
||||
|
||||
def get_fieldsets(self, request, obj=None):
|
||||
"""
|
||||
Hook for specifying fieldsets.
|
||||
"""
|
||||
if self.fieldsets:
|
||||
return self.fieldsets
|
||||
else:
|
||||
return [] # Avoid exposing fields to the child
|
||||
|
||||
def get_fields(self, request, obj=None):
|
||||
if self.fields:
|
||||
return self.fields
|
||||
else:
|
||||
return [] # Avoid exposing fields to the child
|
||||
|
||||
@property
|
||||
def media(self):
|
||||
# The media of the inline focuses on the admin settings,
|
||||
# whether to expose the scripts for filter_horizontal etc..
|
||||
# The admin helper exposes the inline + formset media.
|
||||
base_media = super(PolymorphicInlineModelAdmin, self).media
|
||||
all_media = Media()
|
||||
add_media(all_media, base_media)
|
||||
|
||||
# Add all media of the child inline instances
|
||||
for child_instance in self.child_inline_instances:
|
||||
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
|
||||
):
|
||||
add_media(all_media, child_media)
|
||||
|
||||
add_media(all_media, self.polymorphic_media)
|
||||
|
||||
return all_media
|
||||
|
||||
class Child(InlineModelAdmin):
|
||||
"""
|
||||
The child inline; which allows configuring the admin options
|
||||
for the child appearance.
|
||||
|
||||
Note that not all options will be honored by the parent, notably the formset options:
|
||||
* :attr:`extra`
|
||||
* :attr:`min_num`
|
||||
* :attr:`max_num`
|
||||
|
||||
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
|
||||
)
|
||||
|
||||
def get_formset(self, request, obj=None, **kwargs):
|
||||
# The child inline is only used to construct the form,
|
||||
# and allow to override the form field attributes.
|
||||
# The formset is created by the parent inline.
|
||||
raise RuntimeError("The child get_formset() is not used.")
|
||||
|
||||
def get_fields(self, request, obj=None):
|
||||
if self.fields:
|
||||
return self.fields
|
||||
|
||||
# Standard Django logic, use the form to determine the fields.
|
||||
# The form needs to pass through all factory logic so all 'excludes' are set as well.
|
||||
# Default Django does: form = self.get_formset(request, obj, fields=None).form
|
||||
# Use 'fields=None' avoids recursion in the field autodetection.
|
||||
form = self.get_formset_child(request, obj, fields=None).get_form()
|
||||
return list(form.base_fields) + list(self.get_readonly_fields(request, obj))
|
||||
|
||||
def get_formset_child(self, request, obj=None, **kwargs):
|
||||
"""
|
||||
Return the formset child that the parent inline can use to represent us.
|
||||
|
||||
:rtype: PolymorphicFormSetChild
|
||||
"""
|
||||
# Similar to the normal get_formset(), the caller may pass fields to override the defaults settings
|
||||
# in the inline. In Django's GenericInlineModelAdmin.get_formset() this is also used in the same way,
|
||||
# to make sure the 'exclude' also contains the GFK fields.
|
||||
#
|
||||
# Hence this code is almost identical to InlineModelAdmin.get_formset()
|
||||
# and GenericInlineModelAdmin.get_formset()
|
||||
#
|
||||
# Transfer the local inline attributes to the formset child,
|
||||
# this allows overriding settings.
|
||||
if "fields" in kwargs:
|
||||
fields = kwargs.pop("fields")
|
||||
else:
|
||||
fields = flatten_fieldsets(self.get_fieldsets(request, obj))
|
||||
|
||||
if self.exclude is None:
|
||||
exclude = []
|
||||
else:
|
||||
exclude = list(self.exclude)
|
||||
|
||||
exclude.extend(self.get_readonly_fields(request, obj))
|
||||
# Add forcefully, as Django 1.10 doesn't include readonly fields.
|
||||
exclude.append("polymorphic_ctype")
|
||||
|
||||
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)
|
||||
|
||||
# can_delete = self.can_delete and self.has_delete_permission(request, obj)
|
||||
defaults = {
|
||||
"form": self.form,
|
||||
"fields": fields,
|
||||
"exclude": exclude or None,
|
||||
"formfield_callback": partial(
|
||||
self.formfield_for_dbfield, request=request
|
||||
),
|
||||
}
|
||||
defaults.update(kwargs)
|
||||
|
||||
# This goes through the same logic that get_formset() calls
|
||||
# by passing the inline class attributes to modelform_factory()
|
||||
FormSetChildClass = self.formset_child
|
||||
return FormSetChildClass(self.model, **defaults)
|
||||
|
||||
|
||||
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"
|
||||
@@ -0,0 +1,406 @@
|
||||
"""
|
||||
The parent admin displays the list view of the base model.
|
||||
"""
|
||||
import sys
|
||||
|
||||
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 ImproperlyConfigured, PermissionDenied
|
||||
from django.db import models
|
||||
from django.http import Http404, HttpResponseRedirect
|
||||
from django.template.response import TemplateResponse
|
||||
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
|
||||
|
||||
try:
|
||||
# Django 2.0+
|
||||
from django.urls import URLResolver
|
||||
except ImportError:
|
||||
# Django < 2.0
|
||||
from django.urls import RegexURLResolver as URLResolver
|
||||
|
||||
|
||||
if sys.version_info[0] >= 3:
|
||||
long = int
|
||||
|
||||
|
||||
class RegistrationClosed(RuntimeError):
|
||||
"The admin model can't be registered anymore at this point."
|
||||
|
||||
|
||||
class ChildAdminNotRegistered(RuntimeError):
|
||||
"The admin site for the model is not registered."
|
||||
|
||||
|
||||
class PolymorphicParentModelAdmin(admin.ModelAdmin):
|
||||
"""
|
||||
A admin interface that can displays different change/delete pages, depending on the polymorphic model.
|
||||
To use this class, one attribute need to be defined:
|
||||
|
||||
* :attr:`child_models` should be a list models.
|
||||
|
||||
Alternatively, the following methods can be implemented:
|
||||
|
||||
* :func:`get_child_models` should return a list of models.
|
||||
* optionally, :func:`get_child_type_choices` can be overwritten to refine the choices for the add dialog.
|
||||
|
||||
This class needs to be inherited by the model admin base class that is registered in the site.
|
||||
The derived models should *not* register the ModelAdmin, but instead it should be returned by :func:`get_child_models`.
|
||||
"""
|
||||
|
||||
#: The base model that the class uses (auto-detected if not set explicitly)
|
||||
base_model = None
|
||||
|
||||
#: The child models that should be displayed
|
||||
child_models = None
|
||||
|
||||
#: Whether the list should be polymorphic too, leave to ``False`` to optimize
|
||||
polymorphic_list = False
|
||||
|
||||
add_type_template = None
|
||||
add_type_form = PolymorphicModelChoiceForm
|
||||
|
||||
#: The regular expression to filter the primary key in the URL.
|
||||
#: This accepts only numbers as defensive measure against catch-all URLs.
|
||||
#: If your primary key consists of string values, update this regular expression.
|
||||
pk_regex = r"(\d+|__fk__)"
|
||||
|
||||
def __init__(self, model, admin_site, *args, **kwargs):
|
||||
super(PolymorphicParentModelAdmin, self).__init__(
|
||||
model, admin_site, *args, **kwargs
|
||||
)
|
||||
self._is_setup = False
|
||||
|
||||
if self.base_model is None:
|
||||
self.base_model = get_base_polymorphic_model(model)
|
||||
|
||||
def _lazy_setup(self):
|
||||
if self._is_setup:
|
||||
return
|
||||
|
||||
self._child_models = self.get_child_models()
|
||||
|
||||
# Make absolutely sure that the child models don't use the old 0.9 format,
|
||||
# as of polymorphic 1.4 this deprecated configuration is no longer supported.
|
||||
# Instead, register the child models in the admin too.
|
||||
if self._child_models and not issubclass(self._child_models[0], models.Model):
|
||||
raise ImproperlyConfigured(
|
||||
"Since django-polymorphic 1.4, the `child_models` attribute "
|
||||
"and `get_child_models()` method should be a list of models only.\n"
|
||||
"The model-admin class should be registered in the regular Django admin."
|
||||
)
|
||||
|
||||
self._child_admin_site = self.admin_site
|
||||
self._is_setup = True
|
||||
|
||||
def register_child(self, model, model_admin):
|
||||
"""
|
||||
Register a model with admin to display.
|
||||
"""
|
||||
# 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."
|
||||
)
|
||||
|
||||
if not issubclass(model, self.base_model):
|
||||
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__
|
||||
)
|
||||
)
|
||||
|
||||
self._child_admin_site.register(model, model_admin)
|
||||
|
||||
def get_child_models(self):
|
||||
"""
|
||||
Return the derived model classes which this admin should handle.
|
||||
This should return a list of tuples, exactly like :attr:`child_models` is.
|
||||
|
||||
The model classes can be retrieved as ``base_model.__subclasses__()``,
|
||||
a setting in a config file, or a query of a plugin registration system at your option
|
||||
"""
|
||||
if self.child_models is None:
|
||||
raise NotImplementedError("Implement get_child_models() or child_models")
|
||||
|
||||
return self.child_models
|
||||
|
||||
def get_child_type_choices(self, request, action):
|
||||
"""
|
||||
Return a list of polymorphic types for which the user has the permission to perform the given action.
|
||||
"""
|
||||
self._lazy_setup()
|
||||
choices = []
|
||||
for model in self.get_child_models():
|
||||
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):
|
||||
continue
|
||||
ct = ContentType.objects.get_for_model(model, for_concrete_model=False)
|
||||
choices.append((ct.id, model._meta.verbose_name))
|
||||
return choices
|
||||
|
||||
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)
|
||||
)
|
||||
except self.model.DoesNotExist:
|
||||
raise Http404
|
||||
return self._get_real_admin_by_ct(
|
||||
obj["polymorphic_ctype"], super_if_self=super_if_self
|
||||
)
|
||||
|
||||
def _get_real_admin_by_ct(self, ct_id, super_if_self=True):
|
||||
try:
|
||||
ct = ContentType.objects.get_for_id(ct_id)
|
||||
except ContentType.DoesNotExist as e:
|
||||
raise Http404(e) # Handle invalid GET parameters
|
||||
|
||||
model_class = ct.model_class()
|
||||
if not model_class:
|
||||
# Handle model deletion
|
||||
raise Http404("No model found for '{0}.{1}'.".format(*ct.natural_key()))
|
||||
|
||||
return self._get_real_admin_by_model(model_class, super_if_self=super_if_self)
|
||||
|
||||
def _get_real_admin_by_model(self, model_class, super_if_self=True):
|
||||
# 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
|
||||
)
|
||||
)
|
||||
|
||||
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
|
||||
)
|
||||
)
|
||||
|
||||
if super_if_self and real_admin is self:
|
||||
return super(PolymorphicParentModelAdmin, self)
|
||||
else:
|
||||
return real_admin
|
||||
|
||||
def get_queryset(self, request):
|
||||
# optimize the list display.
|
||||
qs = super(PolymorphicParentModelAdmin, self).get_queryset(request)
|
||||
if not self.polymorphic_list:
|
||||
qs = qs.non_polymorphic()
|
||||
return qs
|
||||
|
||||
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))
|
||||
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,
|
||||
)
|
||||
return real_admin.add_view(request, form_url, extra_context)
|
||||
|
||||
def change_view(self, request, object_id, *args, **kwargs):
|
||||
"""Redirect the change view to the real admin."""
|
||||
real_admin = self._get_real_admin(object_id)
|
||||
return real_admin.change_view(request, object_id, *args, **kwargs)
|
||||
|
||||
def changeform_view(self, request, object_id=None, *args, **kwargs):
|
||||
# The `changeform_view` is available as of Django 1.7, combining the add_view and change_view.
|
||||
# As it's directly called by django-reversion, this method is also overwritten to make sure it
|
||||
# also redirects to the child admin.
|
||||
if object_id:
|
||||
real_admin = self._get_real_admin(object_id)
|
||||
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
|
||||
)
|
||||
|
||||
def history_view(self, request, object_id, extra_context=None):
|
||||
"""Redirect the history view to the real admin."""
|
||||
real_admin = self._get_real_admin(object_id)
|
||||
return real_admin.history_view(request, object_id, extra_context=extra_context)
|
||||
|
||||
def delete_view(self, request, object_id, extra_context=None):
|
||||
"""Redirect the delete view to the real admin."""
|
||||
real_admin = self._get_real_admin(object_id)
|
||||
return real_admin.delete_view(request, object_id, extra_context)
|
||||
|
||||
def get_preserved_filters(self, request):
|
||||
if "_changelist_filters" in request.GET:
|
||||
request.GET = request.GET.copy()
|
||||
filters = request.GET.get("_changelist_filters")
|
||||
f = filters.split("&")
|
||||
for x in f:
|
||||
c = x.split("=")
|
||||
request.GET[c[0]] = c[1]
|
||||
del request.GET["_changelist_filters"]
|
||||
return super(PolymorphicParentModelAdmin, self).get_preserved_filters(request)
|
||||
|
||||
def get_urls(self):
|
||||
"""
|
||||
Expose the custom URLs for the subclasses and the URL resolver.
|
||||
"""
|
||||
urls = super(PolymorphicParentModelAdmin, self).get_urls()
|
||||
|
||||
# At this point. all admin code needs to be known.
|
||||
self._lazy_setup()
|
||||
|
||||
return urls
|
||||
|
||||
def subclass_view(self, request, path):
|
||||
"""
|
||||
Forward any request to a custom view of the real admin.
|
||||
"""
|
||||
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("/")
|
||||
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
|
||||
)
|
||||
)
|
||||
|
||||
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)
|
||||
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=""):
|
||||
"""
|
||||
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"]:
|
||||
# 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"]))
|
||||
|
||||
choices = self.get_child_type_choices(request, "add")
|
||||
if len(choices) == 1:
|
||||
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]},
|
||||
)
|
||||
form.fields["ct_id"].choices = choices
|
||||
|
||||
if form.is_valid():
|
||||
return HttpResponseRedirect(
|
||||
"?ct_id={0}{1}".format(form.cleaned_data["ct_id"], extra_qs)
|
||||
)
|
||||
|
||||
# Wrap in all admin layout
|
||||
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,
|
||||
}
|
||||
return self.render_add_type_form(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,
|
||||
}
|
||||
)
|
||||
|
||||
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",
|
||||
]
|
||||
|
||||
request.current_app = self.admin_site.name
|
||||
return TemplateResponse(request, templates, context)
|
||||
|
||||
@property
|
||||
def change_list_template(self):
|
||||
opts = self.model._meta
|
||||
app_label = opts.app_label
|
||||
|
||||
# Pass the base options
|
||||
base_opts = self.base_model._meta
|
||||
base_app_label = base_opts.app_label
|
||||
|
||||
return [
|
||||
"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/change_list.html" % base_app_label,
|
||||
"admin/change_list.html",
|
||||
]
|
||||
Reference in New Issue
Block a user