diff --git a/AUTHORS b/AUTHORS index af350240b1..f5eba42531 100644 --- a/AUTHORS +++ b/AUTHORS @@ -666,6 +666,7 @@ answer newbie questions, and generally made Django that much better: Rachel Tobin Rachel Willmer Radek Švarz + Raffaele Salmaso Rajesh Dhawan Ramez Ashraf Ramin Farajpour Cami diff --git a/django/contrib/admin/apps.py b/django/contrib/admin/apps.py index df7d669ab0..36c157683d 100644 --- a/django/contrib/admin/apps.py +++ b/django/contrib/admin/apps.py @@ -7,6 +7,7 @@ from django.utils.translation import gettext_lazy as _ class SimpleAdminConfig(AppConfig): """Simple AppConfig which does not do automatic discovery.""" + default_site = 'django.contrib.admin.sites.AdminSite' name = 'django.contrib.admin' verbose_name = _("Administration") diff --git a/django/contrib/admin/sites.py b/django/contrib/admin/sites.py index a3871fb534..f7d0ac0fbc 100644 --- a/django/contrib/admin/sites.py +++ b/django/contrib/admin/sites.py @@ -9,6 +9,8 @@ from django.db.models.base import ModelBase from django.http import Http404, HttpResponseRedirect from django.template.response import TemplateResponse from django.urls import NoReverseMatch, reverse +from django.utils.functional import LazyObject +from django.utils.module_loading import import_string from django.utils.text import capfirst from django.utils.translation import gettext as _, gettext_lazy from django.views.decorators.cache import never_cache @@ -518,6 +520,14 @@ class AdminSite: ], context) +class DefaultAdminSite(LazyObject): + def _setup(self): + AdminSiteClass = import_string(apps.get_app_config('admin').default_site) + self._wrapped = AdminSiteClass() + + # This global object represents the default admin site, for the common case. -# You can instantiate AdminSite in your own code to create a custom admin site. -site = AdminSite() +# You can provide your own AdminSite using the (Simple)AdminConfig.default_site +# attribute. You can also instantiate AdminSite in your own code to create a +# custom admin site. +site = DefaultAdminSite() diff --git a/docs/ref/contrib/admin/index.txt b/docs/ref/contrib/admin/index.txt index 39c02f26af..9a2edb698c 100644 --- a/docs/ref/contrib/admin/index.txt +++ b/docs/ref/contrib/admin/index.txt @@ -157,6 +157,15 @@ application and imports it. This class works like :class:`~django.contrib.admin.apps.AdminConfig`, except it doesn't call :func:`~django.contrib.admin.autodiscover()`. + .. attribute:: default_site + + .. versionadded:: 2.1 + + A dotted import path to the default admin site's class or to a callable + that returns a site instance. Defaults to + ``'django.contrib.admin.sites.AdminSite'``. See + :ref:`overriding-default-admin-site` for usage. + .. function:: autodiscover This function attempts to import an ``admin`` module in each installed @@ -2627,6 +2636,9 @@ creating your own ``AdminSite`` instance (see below), and changing the this class is created as ``django.contrib.admin.site`` and you can register your models and ``ModelAdmin`` instances with it. + If you want to customize the default admin site, you can :ref:`override it + `. + When constructing an instance of an ``AdminSite``, you can provide a unique instance name using the ``name`` argument to the constructor. This instance name is used to identify the instance, especially when @@ -2819,6 +2831,43 @@ own ``AdminSite`` instance since you will likely be importing all the per-app put ``'django.contrib.admin.apps.SimpleAdminConfig'`` instead of ``'django.contrib.admin'`` in your :setting:`INSTALLED_APPS` setting. +.. _overriding-default-admin-site: + +Overriding the default admin site +--------------------------------- + +.. versionadded:: 2.1 + +You can override the default ``django.contrib.admin.site`` by setting the +:attr:`~.SimpleAdminConfig.default_site` attribute of a custom ``AppConfig`` +to the dotted import path of either a ``AdminSite`` subclass or a callable that +returns a site instance. + +.. snippet:: + :filename: myproject/admin.py + + from django.contrib import admin + + class MyAdminSite(admin.AdminSite): + ... + +.. snippet:: + :filename: myproject/apps.py + + from django.contrib.admin.apps import AdminConfig + + class MyAdminConfig(AdminConfig): + default_site = 'myproject.admin.MyAdminSite' + +.. snippet:: + :filename: myproject/settings.py + + INSTALLED_APPS = [ + ... + 'myproject.apps.MyAdminConfig', # replaces 'django.contrib.admin' + ... + ] + .. _multiple-admin-sites: Multiple admin sites in the same URLconf diff --git a/docs/releases/2.1.txt b/docs/releases/2.1.txt index f1c3011c9d..ce5fd3551b 100644 --- a/docs/releases/2.1.txt +++ b/docs/releases/2.1.txt @@ -40,6 +40,9 @@ Minor features * The new :meth:`.ModelAdmin.delete_queryset` method allows customizing the deletion process of the "delete selected objects" action. +* You can now :ref:`override the the default admin site + `. + :mod:`django.contrib.admindocs` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/tests/admin_default_site/__init__.py b/tests/admin_default_site/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/admin_default_site/apps.py b/tests/admin_default_site/apps.py new file mode 100644 index 0000000000..92743c18d4 --- /dev/null +++ b/tests/admin_default_site/apps.py @@ -0,0 +1,6 @@ +from django.contrib.admin.apps import SimpleAdminConfig + + +class MyCustomAdminConfig(SimpleAdminConfig): + verbose_name = 'My custom default admin site.' + default_site = 'admin_default_site.sites.CustomAdminSite' diff --git a/tests/admin_default_site/sites.py b/tests/admin_default_site/sites.py new file mode 100644 index 0000000000..f2c33bd790 --- /dev/null +++ b/tests/admin_default_site/sites.py @@ -0,0 +1,5 @@ +from django.contrib import admin + + +class CustomAdminSite(admin.AdminSite): + pass diff --git a/tests/admin_default_site/tests.py b/tests/admin_default_site/tests.py new file mode 100644 index 0000000000..5d05ec9c45 --- /dev/null +++ b/tests/admin_default_site/tests.py @@ -0,0 +1,31 @@ +from django.contrib import admin +from django.contrib.admin import sites +from django.test import SimpleTestCase, override_settings + + +@override_settings(INSTALLED_APPS=[ + 'admin_default_site.apps.MyCustomAdminConfig', + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.messages', + 'django.contrib.staticfiles', +]) +class CustomAdminSiteTests(SimpleTestCase): + + def setUp(self): + # Reset admin.site since it may have already been instantiated by + # another test app. + self._old_site = admin.site + admin.site = sites.site = sites.DefaultAdminSite() + + def tearDown(self): + admin.site = sites.site = self._old_site + + def test_use_custom_admin_site(self): + self.assertEqual(admin.site.__class__.__name__, 'CustomAdminSite') + + +class DefaultAdminSiteTests(SimpleTestCase): + def test_use_default_admin_site(self): + self.assertEqual(admin.site.__class__.__name__, 'AdminSite')