Convert `admin.py` into a package.
Clears up the code and prepare to receive more changes for formset support.fix_request_path_info
parent
226e5689bd
commit
2a599b5f99
|
|
@ -0,0 +1,15 @@
|
|||
"""
|
||||
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).
|
||||
"""
|
||||
from .parentadmin import PolymorphicParentModelAdmin
|
||||
from .childadmin import PolymorphicChildModelAdmin
|
||||
from .forms import PolymorphicModelChoiceForm
|
||||
from .filters import PolymorphicChildModelFilter
|
||||
|
||||
__all__ = (
|
||||
'PolymorphicModelChoiceForm', 'PolymorphicParentModelAdmin',
|
||||
'PolymorphicChildModelAdmin', 'PolymorphicChildModelFilter'
|
||||
)
|
||||
|
|
@ -0,0 +1,169 @@
|
|||
"""
|
||||
The child admin displays the change/delete view of the subclass model.
|
||||
"""
|
||||
from django.contrib import admin
|
||||
from django.core.urlresolvers import resolve
|
||||
from django.utils import six
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
|
||||
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`` attribute must be set.
|
||||
"""
|
||||
base_model = None
|
||||
base_form = None
|
||||
base_fieldsets = None
|
||||
extra_fieldset_title = _("Contents") # Default title for extra fieldset
|
||||
show_in_index = False
|
||||
|
||||
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 in django 1.6+
|
||||
if not getattr(self, 'declared_fieldsets', None):
|
||||
kwargs.setdefault('fields', None)
|
||||
|
||||
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 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_fieldsets(self, request, obj=None):
|
||||
# If subclass declares fieldsets, this is respected
|
||||
if (hasattr(self, 'declared_fieldset') and self.declared_fieldsets) \
|
||||
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 (
|
||||
self.base_fieldsets[0],
|
||||
(self.extra_fieldset_title, {'fields': other_fields}),
|
||||
) + self.base_fieldsets[1:]
|
||||
else:
|
||||
return self.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(six.iterkeys(form.base_fields)) + list(self.get_readonly_fields(request, obj))
|
||||
|
||||
# Find which fields are not part of the common fields.
|
||||
for fieldset in self.base_fieldsets:
|
||||
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,29 @@
|
|||
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.
|
||||
"""
|
||||
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
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
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
|
||||
|
|
@ -1,27 +1,26 @@
|
|||
"""
|
||||
ModelAdmin code to display polymorphic models.
|
||||
The parent admin displays the list view of the base model.
|
||||
"""
|
||||
import sys
|
||||
import warnings
|
||||
|
||||
import django
|
||||
from django import forms
|
||||
from django.conf.urls import url
|
||||
from django.contrib import admin
|
||||
from django.contrib.admin.helpers import AdminErrorList, AdminForm
|
||||
from django.contrib.admin.widgets import AdminRadioSelect
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.core.exceptions import PermissionDenied
|
||||
from django.core.urlresolvers import RegexURLResolver, resolve
|
||||
from django.core.urlresolvers import RegexURLResolver
|
||||
from django.http import Http404, HttpResponseRedirect
|
||||
from django.shortcuts import render_to_response
|
||||
from django.template.context import RequestContext
|
||||
from django.utils import six
|
||||
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 .forms import PolymorphicModelChoiceForm
|
||||
|
||||
try:
|
||||
# Django 1.6 implements this
|
||||
from django.contrib.admin.templatetags.admin_urls import add_preserved_filters
|
||||
|
|
@ -33,12 +32,6 @@ if sys.version_info[0] >= 3:
|
|||
long = int
|
||||
|
||||
|
||||
__all__ = (
|
||||
'PolymorphicModelChoiceForm', 'PolymorphicParentModelAdmin',
|
||||
'PolymorphicChildModelAdmin', 'PolymorphicChildModelFilter'
|
||||
)
|
||||
|
||||
|
||||
class RegistrationClosed(RuntimeError):
|
||||
"The admin model can't be registered anymore at this point."
|
||||
pass
|
||||
|
|
@ -49,47 +42,6 @@ class ChildAdminNotRegistered(RuntimeError):
|
|||
pass
|
||||
|
||||
|
||||
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
|
||||
|
||||
|
||||
class PolymorphicChildModelFilter(admin.SimpleListFilter):
|
||||
"""
|
||||
An admin list filter for the PolymorphicParentModelAdmin which enables
|
||||
filtering by its child models.
|
||||
"""
|
||||
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
|
||||
|
||||
|
||||
class PolymorphicParentModelAdmin(admin.ModelAdmin):
|
||||
"""
|
||||
A admin interface that can displays different change/delete pages, depending on the polymorphic model.
|
||||
|
|
@ -487,168 +439,6 @@ class PolymorphicParentModelAdmin(admin.ModelAdmin):
|
|||
]
|
||||
|
||||
|
||||
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`` attribute must be set.
|
||||
"""
|
||||
base_model = None
|
||||
base_form = None
|
||||
base_fieldsets = None
|
||||
extra_fieldset_title = _("Contents") # Default title for extra fieldset
|
||||
show_in_index = False
|
||||
|
||||
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 in django 1.6+
|
||||
if not getattr(self, 'declared_fieldsets', None):
|
||||
kwargs.setdefault('fields', None)
|
||||
|
||||
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 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_fieldsets(self, request, obj=None):
|
||||
# If subclass declares fieldsets, this is respected
|
||||
if (hasattr(self, 'declared_fieldset') and self.declared_fieldsets) \
|
||||
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 (
|
||||
self.base_fieldsets[0],
|
||||
(self.extra_fieldset_title, {'fields': other_fields}),
|
||||
) + self.base_fieldsets[1:]
|
||||
else:
|
||||
return self.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(six.iterkeys(form.base_fields)) + list(self.get_readonly_fields(request, obj))
|
||||
|
||||
# Find which fields are not part of the common fields.
|
||||
for fieldset in self.base_fieldsets:
|
||||
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
|
||||
|
||||
|
||||
def _get_opt(model):
|
||||
try:
|
||||
return model._meta.app_label, model._meta.model_name # Django 1.7 format
|
||||
Loading…
Reference in New Issue