如何使用自定义AdminSite类?

35 投票
4 回答
27269 浏览
提问于 2025-04-16 11:06

怎样才能最好地实现我自己的 django.contrib.admin.sites.AdminSite 呢?

其实我在使用 django.contrib.admin.autodiscover 注册 INSTALLED_APPS 时遇到了问题。如果我在 urls.py 中使用我的自定义 AdminSite 类,管理员页面上就不会显示任何应用。

我用一个小技巧解决了这个问题。我写了这个类:

from django.contrib.admin.sites import site as default_site

class AdminSiteRegistryFix( object ):
    '''
    This fix links the '_registry' property to the orginal AdminSites
    '_registry' property. This is necessary, because of the character of
    the admins 'autodiscover' function. Otherwise the admin site will say,
    that you havn't permission to edit anything.
    '''

    def _registry_getter(self):
        return default_site._registry

    def _registry_setter(self,value):
        default_site._registry = value

    _registry = property(_registry_getter, _registry_setter)

然后像这样实现我的自定义 AdminSite:

from wltrweb.hacks.django.admin import AdminSiteRegistryFix
from django.contrib.admin import AdminSite

class MyAdminSite( AdminSite, AdminSiteRegistryFix ):
    # do some magic
    pass        


site = MyAdminSite()

这样我就可以在 urls.py 中使用这个 site 了。

有没有人知道更好的方法?因为我访问了一个以下划线开头的变量,这不过是个小技巧。我不喜欢这种小技巧。

编辑: 另一种方法是重写 django.contrib.admin.autodiscover 函数,但这样的话我就会有重复的代码。

4 个回答

16

从Django 2.1开始,提供了一种现成的解决方案:https://docs.djangoproject.com/en/2.1/ref/contrib/admin/#overriding-the-default-admin-site

from django.contrib import admin

class MyAdminSite(admin.AdminSite):
...

现在,想要更换自定义的管理网站,只需要在已安装的应用中添加你自己的AdminConfig。

from django.contrib.admin.apps import AdminConfig

class MyAdminConfig(AdminConfig):
    default_site = 'myproject.admin.MyAdminSite'


INSTALLED_APPS = [
    ...
    'myproject.apps.MyAdminConfig',  # replaces 'django.contrib.admin'
    ...
]

要注意AdminConfig和SimpleAdminConfig之间的区别,后者不会触发admin.autodiscover()。我目前在一个项目中使用这个解决方案。

31

问题

在项目的管理后台使用一个自定义的类,这个类是从 django.contrib.admin.AdminSite 继承来的。这样做的目的是不需要写额外的代码来注册模型。因为当我使用一些第三方应用时,如果它们的模型有新增或删除,我不想每次都去修改注册代码。

解决方案

你需要在调用 django.contrib.adminautodiscover 函数之前,把默认的管理后台实例换成你自己创建的实例。具体步骤如下:

  1. 创建一个应用来进行这个切换。(我用的是我项目专用的应用,叫 core。)

  2. 有两种选择:

    1. 对于 Django 1.6 到 1.9:使用应用的 __init__ 文件来进行切换。 在 Django 1.8 中,你会收到一个弃用警告,因为在 1.9 中有了变化。需要注意的是,这个方法在 1.9 中也能用,因为下面的代码加载的 Django 模块在 1.9 中已经改过,不再加载模型。当我使用这个方法时,我的 core/__init__.py 文件内容如下:

      from django.contrib import admin
      from django.contrib.admin import sites
      
      class MyAdminSite(admin.AdminSite):
          pass
      
      mysite = MyAdminSite()
      admin.site = mysite
      sites.site = mysite
      
    2. 对于 Django 1.9 及以上版本:使用应用的配置来进行切换。 从 Django 1.9 开始,正如发布说明所说:

      所有模型必须在已安装的应用程序内定义,或者声明一个明确的 app_label。此外,在应用程序加载之前,无法导入它们。特别是,无法在应用程序的根包内导入模型。

      我更喜欢限制在根级别的导入,以避免加载模型的风险。虽然在 1.9 版本中使用上面的 __init__ 方法是可行的,但谁也不能保证 1.10 或更高版本会不会引入变化导致问题。

      当我使用这个方法时,core/__init__.py 设置为 default_app_config = "core.apps.DefaultAppConfig",而我的 core/apps.py 文件内容如下:

      from django.apps import AppConfig
      
      class DefaultAppConfig(AppConfig):
          name = 'core'
      
          def ready(self):
              from django.contrib import admin
              from django.contrib.admin import sites
      
              class MyAdminSite(admin.AdminSite):
                  pass
      
              mysite = MyAdminSite()
              admin.site = mysite
              sites.site = mysite
      

      虽然在 1.7 和 1.8 版本中也可以使用这个方法,但在这些版本中使用它有点风险。请参见下面的说明。

  3. 将这个应用放在 django.contrib.admin 之前,放在 INSTALLED_APPS 列表中。(对于 1.7 及以后的版本,这一点是绝对必要的。在早期版本的 Django 中,即使这个应用在 django.contrib.admin 之后,也可能能正常工作。不过,见下面的说明。)

注意事项和警告

  • 进行切换的应用应该是 INSTALLED_APPS 列表中的第一个应用,以减少其他东西在切换之前抓取 site 值的机会。如果其他应用在切换之前获取了这个值,那么它们就会引用到旧的站点。这样会引发一系列问题。

  • 如果有两个应用试图安装自己的默认管理站点类,上述方法可能会出现问题。这需要逐个案例处理。

  • 未来的 Django 版本可能会破坏这个方法。

  • 对于 1.9 之前的版本,我更喜欢使用 __init__ 来进行站点切换,而不是使用应用配置,因为初始化文档指出,应用配置的 ready() 方法调用得相对较晚。在应用模块加载和 ready() 被调用之间,模型已经被加载,在某些情况下,这可能意味着某个模块在 ready 被调用之前就抓取了 site 的值。因此,为了减少风险,我让应用的 __init__ 代码进行切换。

    我认为在 1.7 和 1.8 版本中存在的风险,通过尽早使用 __init__ 来进行站点切换的方式是可以避免的,而在 1.9 中这种风险不存在。因为现在所有应用都禁止在加载之前加载模块。所以在 INSTALLED_APPS 列表中第一个应用的 ready 回调中进行切换应该是安全的。我已经将一个大型项目升级到 1.9,并使用了应用配置方法,没有遇到任何问题。

6

引用自 https://docs.djangoproject.com/en/1.10/ref/contrib/admin/#customizing-the-adminsite-class

如果你想建立一个自己的管理网站,并且希望它有一些特别的功能,你可以通过继承 AdminSite 来实现,随意修改或添加你想要的内容。然后,只需像使用其他 Python 类一样创建你这个 AdminSite 子类的实例,接着把你的模型和 ModelAdmin 子类注册到这个实例上,而不是使用默认的设置

我觉得这是最直接的方法,但这也意味着你需要在应用的 admin.py 文件中修改注册的代码。

其实在使用你自己的 AdminSite 实例时,根本不需要使用自动发现功能,因为你可能会在 myproject.admin 模块中导入所有应用的 admin.py 模块。

这里的假设是,一旦你开始编写自己的管理网站,它就基本上是针对你这个项目的,你也会提前知道想要包含哪些应用。

所以如果你不想使用上面提到的那种方法,我只看到这两个选择。要么把所有的注册调用替换成你的自定义管理网站,要么在你的 adminsite 模块中明确注册模型。

撰写回答