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.ModelAdminPermission模型类,定制样式,这样在我们访问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模型类,另外默认还有GroupUser

<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)的代码执行流程 

posted on 2018-04-08 21:23  Py行僧  阅读(704)  评论(0编辑  收藏  举报