From 98514849dce07acfaa224a90a784bba9d97249e5 Mon Sep 17 00:00:00 2001 From: Brian Holdefehr Date: Mon, 17 Dec 2012 19:04:10 -0500 Subject: Fixed #19414 -- Added admin registration decorator Thanks stavros for the suggestion. --- django/contrib/admin/__init__.py | 1 + django/contrib/admin/decorators.py | 28 +++++++++++++++++ docs/ref/contrib/admin/index.txt | 28 +++++++++++++++++ docs/releases/1.7.txt | 4 +++ tests/admin_registration/models.py | 6 ++++ tests/admin_registration/tests.py | 61 +++++++++++++++++++++++++++++++++++++- 6 files changed, 127 insertions(+), 1 deletion(-) create mode 100644 django/contrib/admin/decorators.py diff --git a/django/contrib/admin/__init__.py b/django/contrib/admin/__init__.py index 3975993679..cba84cf807 100644 --- a/django/contrib/admin/__init__.py +++ b/django/contrib/admin/__init__.py @@ -1,5 +1,6 @@ # ACTION_CHECKBOX_NAME is unused, but should stay since its import from here # 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.options import ModelAdmin, HORIZONTAL, VERTICAL from django.contrib.admin.options import StackedInline, TabularInline diff --git a/django/contrib/admin/decorators.py b/django/contrib/admin/decorators.py new file mode 100644 index 0000000000..cc6943dbdd --- /dev/null +++ b/django/contrib/admin/decorators.py @@ -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 diff --git a/docs/ref/contrib/admin/index.txt b/docs/ref/contrib/admin/index.txt index bc26a15b42..dd31d11dee 100644 --- a/docs/ref/contrib/admin/index.txt +++ b/docs/ref/contrib/admin/index.txt @@ -101,6 +101,34 @@ Other topics 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 ---------------------- diff --git a/docs/releases/1.7.txt b/docs/releases/1.7.txt index bd22b5513f..d0e4a07374 100644 --- a/docs/releases/1.7.txt +++ b/docs/releases/1.7.txt @@ -135,6 +135,10 @@ Minor features customize the value of :attr:`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` ^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/tests/admin_registration/models.py b/tests/admin_registration/models.py index c6f42fd1f4..0ae9251133 100644 --- a/tests/admin_registration/models.py +++ b/tests/admin_registration/models.py @@ -8,9 +8,15 @@ from django.db import models class Person(models.Model): name = models.CharField(max_length=200) + +class Traveler(Person): + pass + + class Location(models.Model): class Meta: abstract = True + class Place(Location): name = models.CharField(max_length=200) diff --git a/tests/admin_registration/tests.py b/tests/admin_registration/tests.py index 0e444fd2af..994de3bdf7 100644 --- a/tests/admin_registration/tests.py +++ b/tests/admin_registration/tests.py @@ -1,16 +1,23 @@ from __future__ import unicode_literals 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.test import TestCase -from .models import Person, Place, Location +from .models import Person, Place, Location, Traveler class NameAdmin(admin.ModelAdmin): list_display = ['name'] save_on_top = True + +class CustomSite(admin.AdminSite): + pass + + class TestRegistration(TestCase): def setUp(self): self.site = admin.AdminSite() @@ -62,3 +69,55 @@ class TestRegistration(TestCase): Refs #12004. """ 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) -- cgit v1.2.1