美文网首页
Django之admin源码解析

Django之admin源码解析

作者: SlashBoyMr_wang | 来源:发表于2018-08-27 16:46 被阅读0次

    一、单例模式的介绍

    单例模式(Singleton Pattern)是一种常用的软件设计模式,该模式的主要目的是确保某一个类只有一个实例存在。当你希望在整个系统中,某个类只能出现一个实例时,单例对象就能派上用场。

    比如,我们用的 Django程序中的settings配置文件,客户端通过一个 AppConfig 的类来读取配置文件的信息。如果在程序运行期间,有很多地方都需要使用配置文件的内容,也就是说,很多地方都需要创建 AppConfig 对象的实例,这就导致系统中存在多个 AppConfig 的实例对象,而这样会严重浪费内存资源,尤其是在配置文件内容很多的情况下。事实上,类似 AppConfig 这样的类,我们希望在程序运行期间只存在一个实例对象。

    在 Python 中,我们可以用多种方法来实现单例模式:

    • 使用模块
    • 使用 __ new __
    • 使用装饰器(decorator)
    • 使用元类(metaclass)

    基于new方法实现:

    class Settings(object):
        _instance = None
        def __new__(cls, *args, **kw):
            if not cls._instance:
                cls._instance = super(Settings, cls).__new__(cls, *args, **kw)
            return cls._instance
    
    s1=Settings()
    s2=Settings()
    #Settings类实例化出来的所有对象都是相同的对象
    

    基于模块导入的方式实现

    其实,Python 的模块就是天然的单例模式,因为模块在第一次导入时,模块中的代码会执行,同时会生成一个 .pyc 文件,当第二次导入时,就会直接加载 .pyc 文件,而不会再次执行模块代码。因此,我们只需把相关的函数和数据定义在一个模块中,就可以获得一个单例对象了。
    要想弄清楚模块导入实现单例模式,想搞清楚一下三个例子

    • 例一:
    //mymodel.py文件
    class Person:
       def walk(self,x):
           print(f'向前走{x}米')
    
    wangfei = Person()
    
    //text.py文件
    from mymodel import wangfei
    print(id(wangfei.walk(100)))
    
    from mymodel import wangfei
    print(id(wangfei.walk(1000)))
    

    输出:

    向前走100米
    1721431248
    向前走1000米
    1721431248
    

    验证了这句:模块中的代码会执行,同时会生成一个 .pyc 文件,当第二次导入时,就会直接加载 .pyc 文件,而不会再次执行模块代码。

    • 例二:
    //mymodel.py文件
    class Person:
        def walk(self,x):
            print(f'向前走{x}米')
    
    wangfei = Person()
    
    //text.py
    from mymodel import wangfei,Person
    print(id(wangfei))
    print(id(Person()))
    print(id(Person()))
    

    输出:

    1880091761016
    1880091620408
    1880089942224
    

    导入模块中实例化的对象和导入模块中的类,再通过类实例化得到的对象都是不同的对象。

    • 例三:
    //mymodel.py文件
    class Person:
        def walk(self,x):
            print(f'向前走{x}米')
    
    wangfei = Person()
    
    //text2.py
    from mymodel import wangfei
    def func():
        print(id(wangfei))
    
    //text.py
    from mymodel import wangfei
    from text2 import func
    print(id(wangfei))
    func()
    

    结果:

    2829704809720
    2829704809720
    

    在一个程序中,即便是在不同的py文件中导入同一个对象,它们其实也是同一个对象,第二次导入同样是直接加载第一次导入生成的 .pyc 文件。

    二、admin的执行流程

    1. Django启动时,自动加载settings配置文件中的installed_apps,然后执行如下源码函数按照顺序依次加载apps对应的admin.py文件:
    def autodiscover():
        autodiscover_modules('admin', register_to=site)
    
    2、执行admin.py文件下的代码
    from django.contrib import admin
    from blog import models
    
    class UserInfo(admin.ModelAdmin):
        list_display = ("username", "phone", "email", 'blog')
    
    admin.site.register(models.UserInfo, UserInfo)
    
    class ArticleAction(admin.ModelAdmin):
        list_display = ['title', 'user']
    
    admin.site.register(models.Article, ArticleAction)
    admin.site.register(models.Article2Tag)
    admin.site.register(models.ArticleDetail)
    admin.site.register(models.ArticleUpDown)
    admin.site.register(models.Blog)
    admin.site.register(models.Category)
    admin.site.register(models.Comment)
    admin.site.register(models.Tag)
    
    3. 执行Django中admin包下的init中的AdminSite(object)类
    class AdminSite(object):
           .........
    site = AdminSite()
    

    注意:这里应用的是一个单例模式(通过python模块导入的方式实现的),对于AdminSite类的一个单例模式,执行的每一个app中的每一个admin.site都是一个对象

    4. 通过AdminSite类的对象执行AdminSite类中的register方法
    admin.site.register(models.UserInfo, UserInfo)
    admin.site.register(models.Article, ArticleAction)
    admin.site.register(models.Article2Tag)
    admin.site.register(models.ArticleDetail)
    admin.site.register(models.ArticleUpDown)
    admin.site.register(models.Blog)
    admin.site.register(models.Category)
    admin.site.register(models.Comment)
    admin.site.register(models.Tag)
    
        def register(self, model_or_iterable, admin_class=None, **options):
            """
            Registers the given model(s) with the given admin class.
            """
            if not admin_class:
                admin_class = ModelAdmin
    

    这段源码的逻辑是判断register中是否传入了admin_class参数,如果没有传,就是用默认的参数ModelAdmin。

    延伸出来上面在自定义admin_class时为什么必须继承admin.ModelAdmin?
    这是因为admin.ModelAdmin中的参数有很多,我们在自定义时不可能将所有参数都修改,或者重写,那样就太麻烦了。主动继承admin.ModelAdmin,我们只需要修改我们想要设置的参数,其他的就可以使用原来的参数了。

       # Instantiate the admin class to save in the registry
       self._registry[model] = admin_class(model, self)
    

    register这个方法的最后这段源码是将admin_class类传入model来实例化对象完成注册!!!

    5.amdin中url的设计

    首先应该知道Django中的路由分发的设置方式现在了解两种:

    • 第一种:include('py文件')
      urls.py↓
    from django.conf.urls import url, include
    
    url(r'^blogcenter/', include(blogcenter_urls)),
    

    blogcenter_urls.py↓

    from django.conf.urls import url
    from blog import views
    
    urlpatterns = [
        url(r'^backend/$',views.Backend.as_view()),
    ]
    
    • 第二种:url('正则表达式',([ url列表 ],None,None)) 规则。其中第一个None代表 app名,第二个代表模型类名,但是我们基本用不到,所以就先不关注它们。
    url(r"blogcenter/", ([
                       url(r"article/", ([
                                          url(r"del_article/", del_article),
                                          url(r"edit_article/", edit_article),
                                          url(r"add_article/", add_article),
                                      ], None, None)),
    
                       url(r"backend/", views.backend),
                   ], None, None))
    

    接下来看一下Django初始配置的url:

    url(r'^admin/', admin.site.urls),
    

    通过源码可以看出urls是site对象可以调用的静态方法,而site对象是由AdminSite类实例化出来的,从而就可以找到urls的归属地了。
    一下为源码:

        @property
        def urls(self):
            return self.get_urls(), 'admin', self.name
    

    可以看出urls是AdminSite类的静态方法,它的返回值是一个元组,再看self.get_urls()是什么?

    def __init__(self, name='admin'):
            self._registry = {}  # model_class class -> admin_class instance
          .......
    
    def register(self, model_or_iterable, admin_class=None, **options):
         """
         Registers the given model(s) with the given admin class.
         """
         if not admin_class:
             admin_class = ModelAdmin
             ........
    
          #将每个admin_class传入对应model实例化从而注册,以model模型类 为键,
          #对应的model配置类对象为值得形式添加到self._registry中。
          self._registry[model] = admin_class(model, self)
    
    def get_urls(self):
        # Add in each model's views, and create a list of valid URLS for the
        # app_index
        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)
          .......
          return urlpatterns
    

    配置类中的get_url(),和urls方法

    class ModelAdmin(BaseModelAdmin):
        "Encapsulates all admin options and functionality for a given model."
    
        list_display = ('__str__',)
        list_display_links = ()
        list_filter = ()
        list_select_related = False
        list_per_page = 100
        list_max_show_all = 200
        list_editable = ()
        ......
    
        def __init__(self, model, admin_site):
            self.model = model
            self.opts = model._meta
            self.admin_site = admin_site
            super(ModelAdmin, self).__init__()
    
        def get_urls(self):
            from django.conf.urls import url
    
            def wrap(view):
                def wrapper(*args, **kwargs):
                    return self.admin_site.admin_view(view)(*args, **kwargs)
                wrapper.model_admin = self
                return update_wrapper(wrapper, view)
    
            info = self.model._meta.app_label, self.model._meta.model_name
    
            urlpatterns = [
                url(r'^$', wrap(self.changelist_view), name='%s_%s_changelist' % info),
                url(r'^add/$', wrap(self.add_view), name='%s_%s_add' % info),
                url(r'^(.+)/history/$', wrap(self.history_view), name='%s_%s_history' % info),
                url(r'^(.+)/delete/$', wrap(self.delete_view), name='%s_%s_delete' % info),
                url(r'^(.+)/change/$', wrap(self.change_view), name='%s_%s_change' % info),
                # For backwards compatibility (was the change url before 1.9)
                url(r'^(.+)/$', 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()
    

    通过以上简略源码可以了解到几点:

    • self._registry 是以model模型表为键,对应的model配置类对象为值
    • for model, model_admin in self._registry.items():这句源码中,model是个张模型表,model_admin是对应的model配置类对象
    • model._meta.model_name :模型表的名称
    • model._meta.app_label :模型表所在app的名称
    • model_admin.urls:model_admin所代表的是对应model的配置类,通过调用配置类的urls方法,得到相应的URL和视图函数的对应关系,进而返回前端所需的渲染数据。
    • 列表urlpatterns中最后得到是注册model表对应的所有urls,其实就是按照这种规则的路由分发来设计的--->url('正则表达式',([ url列表 ],None,None))

    相关文章

      网友评论

          本文标题:Django之admin源码解析

          本文链接:https://www.haomeiwen.com/subject/yvttwftx.html