Fixed #19414 -- Added admin registration decorator

Thanks stavros for the suggestion.
This commit is contained in:
Brian Holdefehr 2012-12-17 19:04:10 -05:00 committed by Tim Graham
parent d1c9802811
commit 98514849dc
6 changed files with 127 additions and 1 deletions

View File

@ -1,5 +1,6 @@
# ACTION_CHECKBOX_NAME is unused, but should stay since its import from here # ACTION_CHECKBOX_NAME is unused, but should stay since its import from here
# has been referenced in documentation. # has been referenced in documentation.
from django.contrib.admin.decorators import register
from django.contrib.admin.helpers import ACTION_CHECKBOX_NAME from django.contrib.admin.helpers import ACTION_CHECKBOX_NAME
from django.contrib.admin.options import ModelAdmin, HORIZONTAL, VERTICAL from django.contrib.admin.options import ModelAdmin, HORIZONTAL, VERTICAL
from django.contrib.admin.options import StackedInline, TabularInline from django.contrib.admin.options import StackedInline, TabularInline

View File

@ -0,0 +1,28 @@
def register(*models, **kwargs):
"""
Registers the given model(s) classes and wrapped ModelAdmin class with
admin site:
@register(Author)
class AuthorAdmin(admin.ModelAdmin):
pass
A kwarg of `site` can be passed as the admin site, otherwise the default
admin site will be used.
"""
from django.contrib.admin import ModelAdmin
from django.contrib.admin.sites import site, AdminSite
def _model_admin_wrapper(admin_class):
admin_site = kwargs.pop('site', site)
if not isinstance(admin_site, AdminSite):
raise ValueError('site must subclass AdminSite')
if not issubclass(admin_class, ModelAdmin):
raise ValueError('Wrapped class must sublcass ModelAdmin.')
admin_site.register(models, admin_class=admin_class)
return admin_class
return _model_admin_wrapper

View File

@ -101,6 +101,34 @@ Other topics
admin.site.register(Author) admin.site.register(Author)
The register decorator
----------------------
.. function:: register(*models, [site=django.admin.sites.site])
.. versionadded:: 1.7
There is also a decorator for registering your ``ModelAdmin`` classes::
from django.contrib import admin
from .models import Author
@admin.register(Author)
class AuthorAdmin(admin.ModelAdmin):
pass
It is given one or more model classes to register with the ``ModelAdmin``
and an optional keyword argument ``site`` if you are not using the default
``AdminSite``::
from django.contrib import admin
from .models import Author, Reader, Editor
from myproject.admin_site import custom_admin_site
@admin.register(Author, Reader, Editor, site=custom_admin_site)
class PersonAdmin(admin.ModelAdmin):
pass
``ModelAdmin`` options ``ModelAdmin`` options
---------------------- ----------------------

View File

@ -135,6 +135,10 @@ Minor features
customize the value of :attr:`ModelAdmin.fields customize the value of :attr:`ModelAdmin.fields
<django.contrib.admin.ModelAdmin.fields>`. <django.contrib.admin.ModelAdmin.fields>`.
* In addition to the existing ``admin.site.register`` syntax, you can use the
new :func:`~django.contrib.admin.register` decorator to register a
:class:`~django.contrib.admin.ModelAdmin`.
:mod:`django.contrib.auth` :mod:`django.contrib.auth`
^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^

View File

@ -8,9 +8,15 @@ from django.db import models
class Person(models.Model): class Person(models.Model):
name = models.CharField(max_length=200) name = models.CharField(max_length=200)
class Traveler(Person):
pass
class Location(models.Model): class Location(models.Model):
class Meta: class Meta:
abstract = True abstract = True
class Place(Location): class Place(Location):
name = models.CharField(max_length=200) name = models.CharField(max_length=200)

View File

@ -1,16 +1,23 @@
from __future__ import unicode_literals from __future__ import unicode_literals
from django.contrib import admin from django.contrib import admin
from django.contrib.admin.decorators import register
from django.contrib.admin.sites import site
from django.core.exceptions import ImproperlyConfigured from django.core.exceptions import ImproperlyConfigured
from django.test import TestCase from django.test import TestCase
from .models import Person, Place, Location from .models import Person, Place, Location, Traveler
class NameAdmin(admin.ModelAdmin): class NameAdmin(admin.ModelAdmin):
list_display = ['name'] list_display = ['name']
save_on_top = True save_on_top = True
class CustomSite(admin.AdminSite):
pass
class TestRegistration(TestCase): class TestRegistration(TestCase):
def setUp(self): def setUp(self):
self.site = admin.AdminSite() self.site = admin.AdminSite()
@ -62,3 +69,55 @@ class TestRegistration(TestCase):
Refs #12004. Refs #12004.
""" """
self.assertRaises(ImproperlyConfigured, self.site.register, Location) self.assertRaises(ImproperlyConfigured, self.site.register, Location)
class TestRegistrationDecorator(TestCase):
"""
Tests the register decorator in admin.decorators
For clarity:
@register(Person)
class AuthorAdmin(ModelAdmin):
pass
is functionally equal to (the way it is written in these tests):
AuthorAdmin = register(Person)(AuthorAdmin)
"""
def setUp(self):
self.default_site = site
self.custom_site = CustomSite()
def test_basic_registration(self):
register(Person)(NameAdmin)
self.assertTrue(
isinstance(self.default_site._registry[Person],
admin.options.ModelAdmin)
)
def test_custom_site_registration(self):
register(Person, site=self.custom_site)(NameAdmin)
self.assertTrue(
isinstance(self.custom_site._registry[Person],
admin.options.ModelAdmin)
)
def test_multiple_registration(self):
register(Traveler, Place)(NameAdmin)
self.assertTrue(
isinstance(self.default_site._registry[Traveler],
admin.options.ModelAdmin)
)
self.assertTrue(
isinstance(self.default_site._registry[Place],
admin.options.ModelAdmin)
)
def test_wrapped_class_not_a_model_admin(self):
self.assertRaisesMessage(ValueError, 'Wrapped class must sublcass ModelAdmin.',
register(Person), CustomSite)
def test_custom_site_not_an_admin_site(self):
self.assertRaisesMessage(ValueError, 'site must subclass AdminSite',
register(Person, site=Traveler), NameAdmin)