如何使用自定义的AdminSite类?

35

如何最好地实现自己的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

有没有更好的方法?由于我访问以下划线开头的变量,所以这不过是一种黑客行为。我不喜欢黑客行为。

编辑: 另一种方法是重写django.contrib.admin.autodiscover函数,但在这种情况下我会有冗余代码。


1
不确定这是否解决了问题,因为还没有尝试过。自Django 2.1起,默认情况下具有管理定制功能https://docs.djangoproject.com/en/dev/ref/contrib/admin/#overriding-default-admin-site。如果有人尝试过,请在评论/答案中添加结果。 - alexche8
4个回答

31

问题

使用自定义的从django.contrib.admin.AdminSite继承得到的类作为项目的管理员站点,而无需编写自定义注册代码来注册新类。当我使用具有其自己模型的第三方应用程序时,我不希望仅因为这些应用程序添加或删除了模型就必须编辑自定义注册代码。

解决方案

在调用django.contrib.adminautodiscover函数之前,您需要将使用默认类创建的实例切换为使用自己的类创建的实例。我通过以下方式实现:

  1. 拥有一个将执行切换的应用程序。(我为我的特定项目使用名为core的应用程序,但您也可以选择其他名称。)

  2. 有两种选择:

    1. Django 1.6至1.9:使用应用程序的__init__方法执行切换。在Django 1.8中,由于Django 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. 将此应用程序放置在INSTALLED_APPS列表中django.contrib.admin之前。 (对于1.7及更高版本,这绝对是必需的。在早期版本的Django中,即使该应用程序晚于django.contrib.admin,它也可能正常工作。请参见下面的说明。)

    4. 备注和注意事项

      • 执行切换的应用程序应该真正成为INSTALLED_APPS列表中的第一个应用程序,以最小化其他应用程序在进行切换之前从django.contrib.admin中获取site值的机会。如果另一个应用程序在进行切换之前成功获取了该值,则该应用程序将引用旧站点。这肯定会引起一些麻烦。

      • 如果两个应用程序尝试安装自己的新默认管理员站点类,则上述方法不会很好地工作。这将根据情况进行处理。

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

      • 对于1.9之前的版本,我更喜欢使用__init__执行站点切换而不是使用应用程序配置,因为初始化文档指示应用程序配置的ready()方法相对较晚调用。在加载应用程序模块和调用ready()之间,已经加载了模型,并且在某些情况下,可能意味着模块已从django.contrib.admin中获取了sitebefore调用ready()。为了最小化风险,我让应用程序的__init__代码执行切换。

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


4
关于兼容性的说明,我正在使用您的方法与Django 1.9.4一起使用,效果非常好。 - Paolo
1
对我来说部分功能无法正常工作。我使用的是django 1.10.7版本。我自定义了django管理页面的索引页,它很好用。但是我想为我的celery workers使用相同的设置文件,每次初始化celery启动时都会抛出错误:“django.core.exceptions.AppRegistryNotReady: Apps aren't loaded yet.” - alexche8
@alexche8 我在回答中所描述的方法已经在一个公共网站上使用了很长时间,从Django 1.6一直使用到Django 1.11,并且一直使用Celery。所以你是说如果你只是从INSTALLED_APPS中删除特殊的应用程序,那么 celery start 就可以正常工作,但是当你重新添加它时,它就会失败? - Louis
是的,没错。我在INSTALLED_APPS中运行了app.custom_admin.apps.CustomAdminConfig的应用程序,并使用命令celery -A config.celery worker --loglevel=info,但它失败了。 - alexche8
没有使用Celery应用程序也能正常工作。如果您有时间,我可以在其他地方添加您的联系方式,而不是在StackOverflow上发布。我不想在公共场合分享日志,如果我遇到了一些特定情况,您可以更新您的答案。 - alexche8
显示剩余4条评论

15
从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()。我目前正在项目中使用这个解决方案。


5
使用这个配置时,我遇到了一个错误:ImportError: 模块“myproject.admin”没有定义“MyAdminSite”属性/类。 有什么想法? - 4ndt3s
3
当你像这样交换管理员站点时,不建议导入自己的管理站点来注册模型。只需使用from django.contrib.admin import sitesite.register(MyModel,MyModelAdmin) - Elwin
谢谢 @Elwin。我正在尝试拥有两个自定义管理站点。 - 4ndt3s
@Elwin,应用程序的模型持有自定义AdminSite类,但在管理面板中不显示,你有解决方法吗? - Oussama He
@Oussama请发布一个新问题,这样我们才能适当地解决它。 - Elwin
显示剩余8条评论

10

在实现Django 3.2中全站自定义AdminSite时,我遇到了相同的问题,但我找到了一种解决方法使其正常工作。

看来文档应该相应地进行更新。

from django.contrib.admin.apps import AdminConfig

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

这会引发异常:

RuntimeError: 'myproject.apps' 声明了不止一个默认的 AppConfig: 'AdminConfig', 'MyAdminConfig'。

可以通过导入 django.contrib.admin.apps 而非 django.contrib.admin.apps.AdminConfig 来解决该问题:

from django.contrib.admin import apps

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

接着异常会继续产生:

django.core.exceptions.ImproperlyConfigured: 应用标签不唯一,有重复的:admin

这是由settings.py配置引起的:

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

可以通过从设置中删除'MyAdminConfig'来解决此问题:

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

7
引用自https://docs.djangoproject.com/en/1.10/ref/contrib/admin/#customizing-the-adminsite-class 如果您想设置具有自定义行为的管理站点,您可以自由地子类化AdminSite并覆盖或添加任何内容。然后,只需创建AdminSite子类的实例(与实例化任何其他Python类的方式相同),并使用它注册您的模型和ModelAdmin子类,而不是使用默认值。
我认为这是最明确的方法,但也意味着您需要更改应用程序admin.py文件中的注册代码。
当使用自己的AdminSite实例时,真的没有必要使用autodiscover,因为您可能会在myproject.admin模块中导入所有每个应用程序的admin.py模块。
假设是,一旦开始编写自定义管理站点,它就变得非常特定于项目,并且您预先知道要包含哪些应用程序。
因此,如果您不想使用上面的方法,请使用以下两个选项之一。将所有注册调用替换为您的自定义管理站点或在adminsite模块中显式注册模型。

谢谢,我已经阅读了文档的这一部分,它非常与项目相关。我将明确注册我需要的所有模型。 - svenwltr
我通过以下方式做了类似的事情:
  1. 创建一个名为 admin_site.py 的模块,用于我的 AdminSite 子类和其实例,例如 custom_site = MyAdminSite()
  2. admin.site = custom_site 替换;
  3. admin_site 添加为 INSTALLED_APPS 的第一项。
- boechat107

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接