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. 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. 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 The parent model
---------------- ----------------
@ -22,7 +21,7 @@ The parent model
The parent model needs to inherit ``PolymorphicParentModelAdmin``, and implement the following: The parent model needs to inherit ``PolymorphicParentModelAdmin``, and implement the following:
* ``base_model`` should be set * ``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. The exact implementation can depend on the way your module is structured.
For simple inheritance situations, ``child_models`` is the best solution. 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 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_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. * 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, 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, 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): class ModelCAdmin(ModelBAdmin):
base_model = ModelC base_model = ModelC
show_in_index = True # makes child model admin visible in main admin site
# define custom features here # define custom features here
class ModelAParentAdmin(PolymorphicParentModelAdmin): class ModelAParentAdmin(PolymorphicParentModelAdmin):
""" The parent model admin """ """ The parent model admin """
base_model = ModelA base_model = ModelA
child_models = ( child_models = (ModelB, ModelC)
(ModelB, ModelBAdmin),
(ModelC, ModelCAdmin),
)
class ModelBInline(admin.StackedInline): class ModelBInline(admin.StackedInline):
@ -119,4 +118,6 @@ The models are taken from :ref:`advanced-features`.
# Only the parent needs to be registered: # Only the parent needs to be registered:
admin.site.register(ModelA, ModelAParentAdmin) admin.site.register(ModelA, ModelAParentAdmin)
admin.site.register(ModelB, ModelBAdmin)
admin.site.register(ModelC, ModelCAdmin)
admin.site.register(StandardModel, StandardModelAdmin) admin.site.register(StandardModel, StandardModelAdmin)

View File

@ -2,14 +2,17 @@
ModelAdmin code to display polymorphic models. ModelAdmin code to display polymorphic models.
""" """
import sys import sys
import warnings
import django
from django import forms from django import forms
from django.conf.urls import url from django.conf.urls import url
from django.contrib import admin 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.admin.widgets import AdminRadioSelect
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import PermissionDenied 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.http import Http404, HttpResponseRedirect
from django.shortcuts import render_to_response from django.shortcuts import render_to_response
from django.template.context import RequestContext 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.http import urlencode
from django.utils.safestring import mark_safe from django.utils.safestring import mark_safe
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
import django
try: try:
# Django 1.6 implements this # Django 1.6 implements this
@ -125,8 +126,6 @@ class PolymorphicParentModelAdmin(admin.ModelAdmin):
def __init__(self, model, admin_site, *args, **kwargs): def __init__(self, model, admin_site, *args, **kwargs):
super(PolymorphicParentModelAdmin, self).__init__(model, admin_site, *args, **kwargs) super(PolymorphicParentModelAdmin, self).__init__(model, admin_site, *args, **kwargs)
self._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 self._is_setup = False
def _lazy_setup(self): 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, # 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. # considering an advanced use cases where a plugin system scans for the child models.
child_models = self.get_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: for Model, Admin in child_models:
self.register_child(Model, Admin) self.register_child(Model, Admin)
self._child_models = dict(child_models) 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. Return a list of polymorphic types for which the user has the permission to perform the given action.
""" """
self._lazy_setup()
choices = [] 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) perm_function_name = 'has_{0}_permission'.format(action)
model_admin = self._get_real_admin_by_model(model) model_admin = self._get_real_admin_by_model(model)
perm_function = getattr(model_admin, perm_function_name) perm_function = getattr(model_admin, perm_function_name)
@ -300,6 +321,14 @@ class PolymorphicParentModelAdmin(admin.ModelAdmin):
Expose the custom URLs for the subclasses and the URL resolver. Expose the custom URLs for the subclasses and the URL resolver.
""" """
urls = super(PolymorphicParentModelAdmin, self).get_urls() 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) info = _get_opt(self.model)
# Patch the change view URL so it's not a big catch-all; allowing all # 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)) 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. # 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). # These definitions are masked by the definition above, since it needs special handling (and a ct_id parameter).
dummy_urls = [] dummy_urls = []
@ -478,6 +504,7 @@ class PolymorphicChildModelAdmin(admin.ModelAdmin):
base_form = None base_form = None
base_fieldsets = None base_fieldsets = None
extra_fieldset_title = _("Contents") # Default title for extra fieldset extra_fieldset_title = _("Contents") # Default title for extra fieldset
show_in_index = False
def get_form(self, request, obj=None, **kwargs): def get_form(self, request, obj=None, **kwargs):
# The django admin validation requires the form to have a 'class Meta: model = ..' # 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) 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 @property
def change_form_template(self): def change_form_template(self):
opts = self.model._meta opts = self.model._meta