如何使用自定义AdminSite类?
怎样才能最好地实现我自己的 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 个回答
从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()
。我目前在一个项目中使用这个解决方案。
问题
在项目的管理后台使用一个自定义的类,这个类是从 django.contrib.admin.AdminSite
继承来的。这样做的目的是不需要写额外的代码来注册模型。因为当我使用一些第三方应用时,如果它们的模型有新增或删除,我不想每次都去修改注册代码。
解决方案
你需要在调用 django.contrib.admin
的 autodiscover
函数之前,把默认的管理后台实例换成你自己创建的实例。具体步骤如下:
创建一个应用来进行这个切换。(我用的是我项目专用的应用,叫
core
。)有两种选择:
对于 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
对于 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 版本中也可以使用这个方法,但在这些版本中使用它有点风险。请参见下面的说明。
将这个应用放在
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,并使用了应用配置方法,没有遇到任何问题。
引用自 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 模块中明确注册模型。