django
有一套强大的admin
后台数据库管理工具,通过url(r'^admin/', admin.site.urls)
完成对已注册model
的增删改成,注册方法是admin.site.register(Publish)
我们创建一个app
,然后创建一个model
对象,然后迁移数据库
class Publish(models.Model):
title = models.CharField(max_length=32)
def __str__(self):
return self.title
在admin
后台可以通过如下的url
分发,实现数据库表的增删改查
http://127.0.0.1:8000/admin/app01/publish/
http://127.0.0.1:8000/admin/app01/publish/1/change/
http://127.0.0.1:8000/admin/app01/publish/1/delete/
http://127.0.0.1:8000/admin/app01/publish/add/
在url.py
中我们通过url(r'^admin/', admin.site.urls)
一条配置,竟然可以实现对一个model
模型类对应的数据库表,进行增删改查操作,那么django到底是如何,实现一条url配置实现众多次分发的,今天我们就了解下admin
源码
我们在urlpatterns
配置列表处经常看到如下两行模块导入代码
from django.conf.urls import url
from django.contrib import admin
url(r'^admin/', admin.site.urls),
是调用了urls
模块下的url
方法
我们先从admin.site.urls
参数入手,看看这个参数做了什么操作处理 admin
是隶属于from django.contrib import admin
该模块,我们先看下admin
模块代码
from django.contrib.admin.decorators import register
from django.contrib.admin.filters import (
AllValuesFieldListFilter, BooleanFieldListFilter, ChoicesFieldListFilter,
DateFieldListFilter, FieldListFilter, ListFilter, RelatedFieldListFilter,
RelatedOnlyFieldListFilter, SimpleListFilter,
)
from django.contrib.admin.helpers import ACTION_CHECKBOX_NAME
from django.contrib.admin.options import (
HORIZONTAL, VERTICAL, ModelAdmin, StackedInline, TabularInline,
)
from django.contrib.admin.sites import AdminSite, site
from django.utils.module_loading import autodiscover_modules
__all__ = [
"register", "ACTION_CHECKBOX_NAME", "ModelAdmin", "HORIZONTAL", "VERTICAL",
"StackedInline", "TabularInline", "AdminSite", "site", "ListFilter",
"SimpleListFilter", "FieldListFilter", "BooleanFieldListFilter",
"RelatedFieldListFilter", "ChoicesFieldListFilter", "DateFieldListFilter",
"AllValuesFieldListFilter", "RelatedOnlyFieldListFilter", "autodiscover",
]
def autodiscover():
autodiscover_modules('admin', register_to=site)
default_app_config = 'django.contrib.admin.apps.AdminConfig'
这个模块__init__.py
中只有autodiscover
方法,和default_app_config
变量,另外还有头部引入的其他的模块,我们从代码上就能猜到autodiscover
意思大概就是自动扫描,也就是加载、导入的意思,那么到底是哪里执行了autodiscover
方法呢?
django调用python manage.py runserver启动项目时候,利用CommandParser构造并且解析数据,然后执行django.setup方法,调用django.apps模块来读取项目文件中的settings.py拿到这面这几个app,然后交给django.apps的registry.py和config.py来进行统一的配置加载,然后最后self.fetch_command(subcommand).run_from_argv(self.argv)加载
那我们就从那里入手去找找代码,看看能不能发现思路线索,结果在如下的代码中 from django.apps import apps
类下的populate
方法中找到了答案
for app_config in self.get_app_configs():
app_config.ready()
找到对应的AdminConfig
,在from django.contrib.admin.apps import AdminConfig
下
class AdminConfig(SimpleAdminConfig):
"""The default AppConfig for admin which does autodiscovery."""
def ready(self):
super(AdminConfig, self).ready()
self.module.autodiscover()
那么我就接着上篇的博客,来继续跟代码,去研究admin
代码是怎么样的一个执行流程吧
那我们就把目光回到from django.contrib import admin
模块,看如下的扫描代码
def autodiscover():
autodiscover_modules('admin', register_to=site)
继续看autodiscover_modules
方法具体代码,贴出如下
def autodiscover_modules(*args, **kwargs):
from django.apps import apps
register_to = kwargs.get('register_to')
for app_config in apps.get_app_configs():
for module_to_search in args:
# Attempt to import the app's module.
try:
if register_to:
before_import_registry = copy.copy(register_to._registry)
import_module('%s.%s' % (app_config.name, module_to_search))
except Exception:
if register_to:
register_to._registry = before_import_registry
if module_has_submodule(app_config.module, module_to_search):
raise
在上篇博客我们提到过最后封装了一个结果集(大概类似如下封装)
app_configs odict_values([<AdminConfig: admin>, <AuthConfig: auth>, <ContentTypesConfig: contenttypes>, <SessionsConfig: sessions>, <MessagesConfig: messages>, <StaticFilesConfig: staticfiles>, <App01Config: app01>, <RbacConfig: rbac>])
循环遍历结果集,在autodiscover_modules
方法中传递的2个参数'admin', register_to=site
,通过参数for module_to_search in args:
和if register_to
为True的使用,最后执行如下代码,从而导入系列模块
import_module('%s.%s' % (app_config.name, module_to_search))
导入的格式如下,这里我列出格式如下:
app_config.name---- django.contrib.admin admin
app_config.name---- django.contrib.auth admin
app_config.name---- django.contrib.contenttypes admin
app_config.name---- django.contrib.sessions admin
app_config.name---- django.contrib.messages admin
app_config.name---- django.contrib.staticfiles admin
app_config.name---- app01 admin
app_config.name---- app02 admin
app_config.name---- _stark admin
我们回过头来看最开始的配置代码url(r'^admin/', admin.site.urls),
以上分析的只是admin模块autodiscover
的方法,我们继续看下from django.contrib.admin import site
做了哪些操作吧,该模块代码如下:
all_sites = WeakSet()
class AlreadyRegistered(Exception):
pass
class NotRegistered(Exception):
pass
class AdminSite(object):
省略n行代码
。。。。。。
site = AdminSite()
site = AdminSite()
是获取一个AdminSite类对象,然后通过该对象去执行admin.site.urls
方法,我们看看这个对象初始化做了什么,以及有哪些关键方法做了什么?
def __init__(self, name='admin'):
self._registry = {} # model_class class -> admin_class instance
self.name = name
self._actions = {'delete_selected': actions.delete_selected}
self._global_actions = self._actions.copy()
all_sites.add(self)
初始化方法我们要留意下self._registry = {}
这个字典,后续我们在分析它
到此为止,我们还去看哪块的代码呢?我们都知道当我们在admin
后台,去操作一个model类映射的数据库表时,我们都是需要在admin.py中进行如下配置
admin.site.register(Publish)
所以我们就去看看register
方法中做了哪些操作吧,我先将全部代码贴出如下:
def register(self, model_or_iterable, admin_class=None, **options):
if not admin_class:
admin_class = ModelAdmin
if isinstance(model_or_iterable, ModelBase):
model_or_iterable = [model_or_iterable]
for model in model_or_iterable:
if model._meta.abstract:
raise ImproperlyConfigured(
'The model %s is abstract, so it cannot be registered with admin.' % model.__name__
)
if model in self._registry:
raise AlreadyRegistered('The model %s is already registered' % model.__name__)
if not model._meta.swapped:
# If we got **options then dynamically construct a subclass of
# admin_class with those **options.
if options:
options['__module__'] = __name__
admin_class = type("%sAdmin" % model.__name__, (admin_class,), options)
# Instantiate the admin class to save in the registry
self._registry[model] = admin_class(model, self)
接下来我们逐行分析上述代码,我们先看如下代码
if not admin_class:
admin_class = ModelAdmin
这个ModelAdmin
是什么东西呢?我们看如下代码,我们可以通过PermissionConf
继承admin.ModelAdmin
为Permission
模型类,定制样式,这样在我们访问admin
后台时,会展现我们的样式,每个模型类默认样式就是ModelAdmin
class PermissionConf(admin.ModelAdmin):
list_display = ["title", "url", "permission_group", "code"]
ordering = ["id"]
admin.site.register(Permission,PermissionConf)
所以admin_class = ModelAdmin
代码意思就是,如果没有为model
模型类定制样式,那么就使用默认的ModelAdmin
样式,继续执行如下代码
if isinstance(model_or_iterable, ModelBase):
model_or_iterable = [model_or_iterable]
在admin
模块中,是依次扫描,然后去执行每个admin.py
下的register
方法
最后执行self._registry[model] = admin_class(model, self)
_registry
就是AdminSite
初始化构造的一个空字典{}
,具体是什么类型的字典呢?
这个字典的每个键值对中的键就是对应的model
对象模型,值就是admin_class(model, self)
值是调用了类ModelAdmin
,继而构造对象构成model
对象模型对应的值
这里我们看下封装的数据结构吧:
<class 'django.contrib.auth.models.Group'> <django.contrib.admin.sites.AdminSite object at 0x06036DD0>
<class 'django.contrib.auth.models.User'> <django.contrib.admin.sites.AdminSite object at 0x06036DD0>
<class 'app01.models.Publish'> <django.contrib.admin.sites.AdminSite object at 0x06036DD0>
<class 'django.contrib.auth.models.Group'> <django.contrib.admin.sites.AdminSite object at 0x060C6DF0>
<class 'django.contrib.auth.models.User'> <django.contrib.admin.sites.AdminSite object at 0x060C6DF0>
<class 'app01.models.Publish'> <django.contrib.admin.sites.AdminSite object at 0x060C6DF0>
至此site = AdminSite()
对象的register
方法就分析完毕
我们接下里继续看下如下代码,我再次贴出来
url(r'^admin/', admin.site.urls),
admin
模块、AdminSite
对象register
方法、均分析完毕,接下来就看看admin.site.urls
这个函数,property
特性是方法转属性的用法,最后返回一个<class 'tuple'>
元组类型
@property
def urls(self):
return self.get_urls(), 'admin', self.name
最后就看下方法get_urls()
,我将代码贴出如下:
def get_urls(self):
from django.conf.urls import url, include
from django.contrib.contenttypes import views as contenttype_views
def wrap(view, cacheable=False):
def wrapper(*args, **kwargs):
return self.admin_view(view, cacheable)(*args, **kwargs)
wrapper.admin_site = self
return update_wrapper(wrapper, view)
# Admin-site-wide views.
urlpatterns = [
url(r'^$', wrap(self.index), name='index'),
url(r'^login/$', self.login, name='login'),
url(r'^logout/$', wrap(self.logout), name='logout'),
url(r'^password_change/$', wrap(self.password_change, cacheable=True), name='password_change'),
url(r'^password_change/done/$', wrap(self.password_change_done, cacheable=True),
name='password_change_done'),
url(r'^jsi18n/$', wrap(self.i18n_javascript, cacheable=True), name='jsi18n'),
url(r'^r/(?P<content_type_id>\d+)/(?P<object_id>.+)/$', wrap(contenttype_views.shortcut),
name='view_on_site'),
]
valid_app_labels = []
for model, model_admin in self._registry.items():
urlpatterns += [
url(r'^%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)
if valid_app_labels:
regex = r'^(?P<app_label>' + '|'.join(valid_app_labels) + ')/$'
urlpatterns += [
url(regex, wrap(self.app_index), name='app_list'),
]
return urlpatterns
去循环遍历for model, model_admin in self._registry.items():
获取对应的键和值
如下是打印输出,我只创建了一个Publish
模型类,另外默认还有Group
和User
<class 'django.contrib.auth.models.Group'> auth.GroupAdmin
<class 'django.contrib.auth.models.User'> auth.UserAdmin
<class 'app01.models.Publish'> app01.ModelAdmin
url(r'^admin/', admin.site.urls)
,分发url
,如果没有在urlpatterns
中,那么就拼接填入valid_app_labels.append(model._meta.app_label)
以下是get_urls
的结果集
[<RegexURLPattern index ^$>,
<RegexURLPattern login ^login/$>,
<RegexURLPattern logout ^logout/$>,
<RegexURLPattern password_change_done ^password_change/done/$>,
<RegexURLPattern view_on_site ^r/(?P<content_type_id>\d+)/(?P<object_id>.+)/$>, <RegexURLResolver <RegexURLPattern list> (None:None) ^auth/group/>, <RegexURLResolver <RegexURLPattern list> (None:None) ^auth/user/>, <RegexURLResolver <RegexURLPattern list> (None:None) ^app01/publish/>, <RegexURLPattern app_list ^(?P<app_label>auth|app01)/$>]
以上就是`url(r’^admin/’, admin.site.urls)的代码执行流程