Django 非常好用的一个功能就是后台界面的自动管理。
from django.contrib import admin
from django.utils.safestring import mark_safe
class xxxConfig(admin.ModelAdmin):
def deletes(self):
return mark_safe('<a href=''>删除</a>')
list_display = [, deletes] # 里面的字段不能是 ManyToMany 字段
list_filter = ()
list_display_links = ()
search_fields = ()
def patch_init(self, request, queryset):
queryset.update(xxx=xxx)
patch_init.short_description = "批量处理"
actions = [patch_init, ]
所有字段都在 admin.ModelAdmin 中可见
admin.site.register(模型, '你自定义的类')
Admin 的实现流程:启动,注册,设计url
- 启动
在INSTALLED_APPS
中的注册 app 中,扫描admin
文件,并执行
def autodiscover():
autodiscover_modules('admin', register_to=site)
- 注册,在每个安装的 app 下面的
admin
文件中注册site
,即admin.site.register(模型, '你自定义的类')
- 设计 url
在项目的根目录下url
文件中会有path('admin/', admin.site.urls),
这么一行注册 url 的函数
综上,其实这是很明显的一个单例模式,因为后两步中都用到了一个特殊的对象admin.site
,admin
是从django.contrib
中导入的,而admin
中又导入了site
:from django.contrib.admin.sites import AdminSite, site
,之后进入到site
中,发现了也就这么一个东西:
class AdminSite:
pass
site = AdminSite()
这其实用到了 Python 中对于单例模式的一个经典的用法,模块就是一个特殊的单例,因为模块无论声明多少次,最终导入到 Python 解释器中只会有一次,就是刚开始那次。
接下来我们看一看源码做了什么?
# admin 的 __init__.py
from django.contrib.admin.sites import AdminSite, site
def autodiscover():
autodiscover_modules('admin', register_to=site)
这里随即进入 AdminSite
:
# sites.py
pass
class AdminSite:
def __init__(self, name='admin'):
self._registry = {} # model_class class -> admin_class instance
self.name = name
pass
def register(self, model_or_iterable, admin_class=None, **options):
if not admin_class:
admin_class = ModelAdmin
pass
site = AdminSite()
注意这里的 site = AdminSite()
,在 Django 的其它地方导入它以后,它就是一个单例了,它的 id 就不变了。在 AdminSite
中定义了一个字典 _registry
,里面存放的是注册的 model
和 其配置类,配置类默认是 ModelAdmin
,也就是 Django 自己的那个后台。这里完成了 url
的注册。
接下来看一看它里面是如何构造 url
的。还记得在项目根目录下的 url(r'admin', admin.site.urls),
吗,进入它:
# 跟上面的一样是 sites.py
def get_urls(self):
pass
for model, model_admin in self._registry.items():
urlpatterns += [
path('%s/%s/' % (model._meta.app_label, model._meta.model_name), include(model_admin.urls)),
]
if model._meta.app_label not in valid_app_labels:
valid_app_labels.append(model._meta.app_label)
return urlpatterns
@property
def urls(self):
return self.get_urls(), 'admin', self.name
这里面 include(model_admin.urls)
及其重要,Django 默认将自己的配置类注册到 _register
了,它是 ModelAdmin
的 urls
:
# 注:这是 Django 2 的有些部分可能不一样,不过不影响
def get_urls(self):
pass
info = self.model._meta.app_label, self.model._meta.model_name
urlpatterns = [
path('', wrap(self.changelist_view), name='%s_%s_changelist' % info),
path('add/', wrap(self.add_view), name='%s_%s_add' % info),
path('autocomplete/', wrap(self.autocomplete_view), name='%s_%s_autocomplete' % info),
path('<path:object_id>/history/', wrap(self.history_view), name='%s_%s_history' % info),
path('<path:object_id>/delete/', wrap(self.delete_view), name='%s_%s_delete' % info),
path('<path:object_id>/change/', wrap(self.change_view), name='%s_%s_change' % info),
# For backwards compatibility (was the change url before 1.9)
path('<path:object_id>/', wrap(RedirectView.as_view(
pattern_name='%s:%s_%s_change' % ((self.admin_site.name,) + info)
))),
]
return urlpatterns
@property
def urls(self):
return self.get_urls()
这里重要的是 Django 对 url
做了两级分发,而且这两级 url
不在同一个类中,这样做的目的就是为了方便构造模板。最后那一级 url 放在了 ModelAdmin
中,就是增删改查那四个功能。
说明:这里二级分发 url 主要是为了方便后面模板构造数据,因为构造数据要访问 model,如果两级 url 全部放在一个类中,那么需要找到 model 便需要根据 url 来匹配,但是将二级 url 放在默认的类中,那么它便可以找到对应的 model 了,因为 _register 中存放的是 model 和 配置类形成的字典。而默认配置类里面会注册进来 对应的 model,所以在配置类中可以找到 model。
先暂时到这,可能有诸多不清楚的,后面再补充。
顺便说一句,AdminSite
的源码连注释,加空行总共 500 行代码,有空了解一下。😋
Django 是如何找到每个 app 下的
admin.py
文件呢,在每个 app 下都有一个配置类,如 admin 的AdminConfig
,它里面会定义一个ready
方法,django 在加载INSTALLED_APPS
时,会调用里面的方法。
class AdminConfig(SimpleAdminConfig):
def ready(self):
super().ready()
self.module.autodiscover()
在我们自己构建组件时,可以这样写:
from django.apps improt AppConfig
from django.uitls.moudle_loading improt autodiscover_moudles
class xxxConfig(AppConfig):
name = xxx
def ready(self):
autodiscover_moudles('里面放需要加载文件的名称')
设计 url
首先来了解一下 url 注册的另一种方式:
def test01(request):
return HttpResponse('test01')
def test02(request):
pass
def test03(request):
pass
urlpatterns = [
url(r'^test/', ([
url(r't1/', test01),
url(r't2/', test02),
url(r't3/', test03),
], None, None))
]
这种相当于是将 include
中的 url 放到当前 url 中了。
构造 url:
get_urls2():
temp = []
temp.append(url(r'^$'), view_name)
temp.append(url(r'^add$'), view_name)
temp.append(url(r'^(\d+)/change/$'), view_name)
temp.append(url(r'^(\d+)/delete/$'), view_name)
return temp
def get_urls():
res = []
for model, admin_class_obj in admin.site_register.items():
app_name = model._meta.app_label
model_name = model._meta.model_name
temp.append(url(r'{0}/{1}'.format(app_name, model_name), (get_urls2(), None, None)), )
return res
urlpatterns = [
url(r'^xadmin/', (get_urls(), None, None)),
]
以上有几个函数很有趣:
model._meta.app_label --> 获取模型所属 app 的名称字符串
model._meta.model_name --> 获取模型名称字符串
obj = model._meta.get_field("field") --> 获取模型内某个属性,别忘记,其实模型内的属性也是一个类
所以:obj.verbose_name 即可取到其对应的属性。