Merge pull request #215 from skirsdeda/master

Admin refactoring (based on #58)
fix_request_path_info
Diederik van der Boor 2016-06-10 14:09:24 +02:00 committed by GitHub
commit 226e5689bd
2 changed files with 51 additions and 16 deletions

View File

@ -14,7 +14,6 @@ The polymorphic admin is implemented via a parent admin that forwards the *edit*
to the ``ModelAdmin`` of the derived child model. The *list* page is still implemented by the parent model admin.
Both the parent model and child model need to have a ``ModelAdmin`` class.
Only the ``ModelAdmin`` class of the parent/base model has to be registered in the Django admin site.
The parent model
----------------
@ -22,7 +21,7 @@ The parent model
The parent model needs to inherit ``PolymorphicParentModelAdmin``, and implement the following:
* ``base_model`` should be set
* ``child_models`` or ``get_child_models()`` should return a list with (Model, ModelAdmin) tuple.
* ``child_models`` or ``get_child_models()`` should return an iterable of Model classes.
The exact implementation can depend on the way your module is structured.
For simple inheritance situations, ``child_models`` is the best solution.
@ -49,6 +48,8 @@ This class implements the following features:
* It extends the template lookup paths, to look for both the parent model and child model in the ``admin/app/model/change_form.html`` path.
* 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.
* Although it must be registered with admin site, by default it's hidden from admin site index page.
This can be overriden by adding ``show_in_index = True`` in admin class.
The standard ``ModelAdmin`` attributes ``form`` and ``fieldsets`` should rather be avoided at the base class,
because it will hide any additional fields which are defined in the derived model. Instead,
@ -95,16 +96,14 @@ The models are taken from :ref:`advanced-features`.
class ModelCAdmin(ModelBAdmin):
base_model = ModelC
show_in_index = True # makes child model admin visible in main admin site
# define custom features here
class ModelAParentAdmin(PolymorphicParentModelAdmin):
""" The parent model admin """
base_model = ModelA
child_models = (
(ModelB, ModelBAdmin),
(ModelC, ModelCAdmin),
)
child_models = (ModelB, ModelC)
class ModelBInline(admin.StackedInline):
@ -119,4 +118,6 @@ The models are taken from :ref:`advanced-features`.
# Only the parent needs to be registered:
admin.site.register(ModelA, ModelAParentAdmin)
admin.site.register(ModelB, ModelBAdmin)
admin.site.register(ModelC, ModelCAdmin)
admin.site.register(StandardModel, StandardModelAdmin)

View File

@ -2,14 +2,17 @@
ModelAdmin code to display polymorphic models.
"""
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 AdminForm, AdminErrorList
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
from django.core.urlresolvers import RegexURLResolver, resolve
from django.http import Http404, HttpResponseRedirect
from django.shortcuts import render_to_response
from django.template.context import RequestContext
@ -18,8 +21,6 @@ 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 _
import django
try:
# Django 1.6 implements this
@ -125,8 +126,6 @@ class PolymorphicParentModelAdmin(admin.ModelAdmin):
def __init__(self, model, admin_site, *args, **kwargs):
super(PolymorphicParentModelAdmin, self).__init__(model, admin_site, *args, **kwargs)
self._child_admin_site = self.admin_site.__class__(name=self.admin_site.name)
self._child_admin_site.get_app_list = lambda request: () # HACK: workaround for Django 1.9
self._is_setup = False
def _lazy_setup(self):
@ -136,6 +135,23 @@ class PolymorphicParentModelAdmin(admin.ModelAdmin):
# By not having this in __init__() there is less stress on import dependencies as well,
# considering an advanced use cases where a plugin system scans for the child models.
child_models = self.get_child_models()
# Check if get_child_models() returns an iterable of models (new format) or an iterable
# of (Model, Admin) (legacy format). When iterable is empty, assume the new format.
self._compat_mode = len(child_models) and isinstance(child_models[0], (list, tuple))
if not self._compat_mode:
self._child_models = child_models
self._child_admin_site = self.admin_site
self._is_setup = True
return
# Continue only if in compatibility mode
warnings.warn("Using tuples of (Model, ModelAdmin) in PolymorphicParentModelAdmin.child_models is "
"deprecated; instead child_models should be iterable of child models eg. "
"(Model1, Model2, ..) and child admins should be registered to default admin site",
DeprecationWarning)
self._child_admin_site = self.admin_site.__class__(name=self.admin_site.name)
self._child_admin_site.get_app_list = lambda request: () # HACK: workaround for Django 1.9
for Model, Admin in child_models:
self.register_child(Model, Admin)
self._child_models = dict(child_models)
@ -183,8 +199,13 @@ class PolymorphicParentModelAdmin(admin.ModelAdmin):
"""
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():
for child_model_desc in self.get_child_models():
if self._compat_mode:
model = child_model_desc[0]
else:
model = child_model_desc
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)
@ -300,6 +321,14 @@ class PolymorphicParentModelAdmin(admin.ModelAdmin):
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()
# Continue only if in compatibility mode
if not self._compat_mode:
return urls
info = _get_opt(self.model)
# Patch the change view URL so it's not a big catch-all; allowing all
@ -331,9 +360,6 @@ class PolymorphicParentModelAdmin(admin.ModelAdmin):
url(r'^(?P<path>.+)$', self.admin_site.admin_view(self.subclass_view))
]
# At this point. all admin code needs to be known.
self._lazy_setup()
# Add reverse names for all polymorphic models, so the delete button and "save and add" just work.
# These definitions are masked by the definition above, since it needs special handling (and a ct_id parameter).
dummy_urls = []
@ -478,6 +504,7 @@ class PolymorphicChildModelAdmin(admin.ModelAdmin):
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 = ..'
@ -495,6 +522,13 @@ class PolymorphicChildModelAdmin(admin.ModelAdmin):
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