django-admin源码解析,以及组件使用

 

本文转载自cnblog.com/yuanchenqi博客

 

我们之所以要在这里取了解admin里面的源码以及各种功能的实现机制,是因为我们后期会需要自己去写一些组件,就是类似于我们的admin里面的各种功能,所以我们首先需要了解我们的admin里面都有哪些功能,先知道需求是什么,然后再去根据需求去倒推,倒推这些都是怎么实现的,使用了哪些知识点,整个的流程以及思路是什么,针对每个功能都去按照这个流程倒推,然后我们就可以慢慢理清思路,有大概的方向,从哪里开始着手,由浅入深,逐步全面化,细节化,这是一整套的流程跟逻辑,里面的很多的功能都是我们之前做练习的时候遇到的,都解决过的,只不过我们在了解需求的时候,把这些东西都捡起来然后整合到一起而已,还有一点就是我们的admin里面的源码,我们要洞悉里面的逻辑流程,从而更加深刻地理解每个功能的实现机制,为后期我们自己写组件打好基础

 

笔记整理:

 1 仿照admin组件开发独立组件
 2 
 3 admin:组件应用:后台数据表进行增删改查
 4 
 5 
 6      为每一个app下的每一个model表进行增删改查
 7      
 8      
 9     class UserInfoConfig(admin.ModelAdmin):
10     
11         list_display = ["id", "name", "email"]
12         list_display_links = ('name',"email")
13         ordering=["id",]
14         search_fields=["name",]    # userinfo.objects.filter(name__contains=keyword)
15 
16 
17 
18     action="foo"
19     selected_id=[1,2,3]
20     
21     queryset=UserInfo.objects.filter(pk__in=selected_id)
22     foo(request,queryset)
23     
24 
25 
26 
27 
28 
29 admin实现流程:
30      
31      
32     一 注册 
33     
34      1 当django项目启动时,admin应用首先扫描(加载)每一个app下面的admin.py文件
35      
36      2 (1)from django.contrib import admin 
37      
38           加载admin -->sites---->site = AdminSite()    (单例对象)
39        
40        (2)
41             admin.site.register(Book)
42           
43           
44             class AdminSite(): 
45             
46                 def __init__(self, name='admin'):
47                     self._registry = {} 
48             
49                 def register(self, model, admin_class=None,):
50                     if not admin_class:
51                         admin_class = ModelAdmin
52                         
53                     
54                      self._registry[model] = admin_class(model, self)
55                      
56                      
57             self._registry:   {Book:ModelAdmin(Book),}
58                 {
59 
60                 <class 'django.contrib.auth.models.Group'>: <django.contrib.auth.admin.GroupAdmin object at 0x0000000003E56AC8>, 
61                 <class 'django.contrib.auth.models.User'>: <django.contrib.auth.admin.UserAdmin object at 0x0000000003E88828>, 
62                 <class 'app01.models.Book'>: <django.contrib.admin.options.ModelAdmin object at 0x0000000003E88898>, 
63                 <class 'rbac.models.UserInfo'>: <rbac.admin.UserInfoConfig object at 0x0000000003E9E550>, 
64                 <class 'rbac.models.Role'>: <rbac.admin.RoleConfig object at 0x0000000003E9E588>, 
65                 <class 'rbac.models.PermissionGroup'>: <rbac.admin.PermissionGroupConfig object at 0x0000000003E9E630>,
66                 <class 'rbac.models.Menu'>: <rbac.admin.Menuconfig object at 0x0000000003E9E6D8>,  <class 'rbac.models.Permission'>: <rbac.admin.PermissionConfig object at 0x0000000003E9E780>
67                 }
68                     
69      
70     二 url配置
71         
72         为每个app下的model创建对应的增删改查的url
73          
74 
75 
76 
77 知识点:
78     1 同一个模块重复调用时,文件只加载一次。
79 
80     2 基于模块的单例模式
81     
82     
83        
84 
85 
86      
87     3 url(r'^admin/', admin.site.urls),    
课堂笔记概要

 

我们先来了解一下这个admin,它是django自带的组件,我们只要创建一个项目,就会在里面自动带上这个admin组件,我们在项目的setting文件里面可以看到它,在apps里面:

在url里面:

 

我们的admin组件,它的每一个页面都是有规律的,都是一套模板,格式和主体样式基本一致,对于数据的增删改查操作是不同的页面,也就意味着配有不同的url,我们的models里面有n个表格,那么就会有n*4个url,这都是固定的,我们先来看各个页面的所实现的功能,后期会有对于url的具体剖析

我们先从基本的简单的功能开始入手,

我们在app里面的admin文件里面把我们的所有models里面的表格给注册上,在这个admin里面注册表格的时候,有很多的可扩展功能,下面来看一下:

 1 from django.contrib import admin
 2 
 3 # Register your models here.
 4 print("rbac.............")
 5 
 6 from .models import *
 7 
 8 
 9 class UserInfoConfig(admin.ModelAdmin):
10     list_display = ["id", "name", "email"]
11     # list_display_links = ('name',"email")  # 在浏览器页面上所显示的字段名
12     # ordering=["id",]  # 把表格里面的字段id按照顺序排序,
13     # search_fields=["name","email"]  # 在页面上加一个查询框,按照name和email字段查询,两者皆可
14     # list_filter=["roles"]  # 按照roles字段进行过滤,过滤的菜单栏显示在页面上
15     # list_editable=['name']  # 在浏览器上面把这个字段显示的时候是停留在当前的显示页面就可以编辑该字段
16     # fields=["name"]  # 在新增数据页面只显示一个name字段
17     # exclude=["name"]  # 在新增数据页面显示除了name以外的所有字段
18     # readonly_fields=["name"]  # 这个字段以只读形式显示
19 
20     # fieldsets = (
21     #     ('AAAA', {
22     #         'fields': ('name', 'email')
23     #     }),
24     #
25     #     ('其他', {
26     #         'classes': ('collapse', 'wide', 'extrapretty'),  # 'collapse','wide', 'extrapretty'
27     #         'fields': ('roles',),
28     #     }),
29     # )
30 
31     def foo(self, request, queryset):  # 我们在这里定义一个函数,是为了在action里面把我们自定义的东西加上去,这个action就是我们在批量操作数据的时候,有一个下拉框,下拉框里面默认只有一个删除操作,我们可以自定义其他的操作,就是在这里使用这个自定义函数的方法,按照这个格式去写即可,
32         print(queryset)  # 我们这个queryset是一个键值对,是queryset集合的键值对,如下是打印效果:<QuerySet [<UserInfo: egon>, <UserInfo: banana>]>,userinfo是表名,后面是我们使用models建表的时候定义的一个def__str__(self)方法,它反射的是name字段,所以它显示的是name值,我们在这个函数里面是queryset进行处理的,所以逻辑要符合queryset的用法,否则会报错的,
33         queryset.update(email="yuan@qq.com")  # 这里的update是queryset的用法,email是该表格的字段,所以我们在这里就是把批量操作设定为了所有选中的项都要把email字段更新为我们这里所设定的"yuan@qq.com",
34 
35     foo.short_description = "中文显示自定义Actions"  # 这里就是字面意思,把这个函数的具体操作做一个简述,让用户知道如果点击它会发生什么,
36     actions = [foo,]  # 这里是把我们自定义的函数名放到这个列表里面,列表的名字是固定的,admin内置的
37 admin.site.register(UserInfo,UserInfoConfig)  # 最后我们把自己定义的这个类名放到register里面用这种固定格式,
38 
39 
40 
41 class RoleConfig(admin.ModelAdmin):
42     list_display = ["id","title"]
43 admin.site.register(Role,RoleConfig)
44 
45 
46 
47 class PermissionGroupConfig(admin.ModelAdmin):
48     list_display = ["caption","menu"]
49 admin.site.register(PermissionGroup,PermissionGroupConfig)
50 
51 
52 class Menuconfig(admin.ModelAdmin):
53     list_display = ["id","caption"]
54 admin.site.register(Menu,Menuconfig)
55 
56 
57 
58 class PermissionConfig(admin.ModelAdmin):
59     list_display = ["id","title","url","permission_group","code","parent"]
60     ordering = ["id"]
61 admin.site.register(Permission,PermissionConfig)
62 
63 
64 
65 
66 print(admin.site._registry)
View Code

 

还有一个点是,我们不能在admin里面既把一个字段设定为a标签,同时又把它设为input输入框,此时系统无法识别操作,就会抛错:

 1 class UserInfoConfig(admin.ModelAdmin):
 2     list_display = ['id', 'name', 'email']
 3     # list_display_links = ('email',)
 4     # The value of 'list_display_links' must be a list, a tuple or None
 5     #  也就是说我们这里可以是元祖或者是列表,如果是元祖的话,必须要有,逗号,
 6     ordering = ('-id',)  # 这里是倒序
 7     list_editable = ('email',)
 8     """
 9     <class 'rbac.admin.UserInfoConfig'>: (admin.E123) 
10     The value of 'email' cannot be in both 'list_editable' and 'list_display_links'.
11     我们不能把一个字段同时设为超链接的a标签,同时又把它设为可修改的input框,它不能既是input输入框同时又是a标签,
12     这本身也不符合逻辑,所以我们的系统无法识别,就会抛错
13     """
14 admin.site.register(UserInfo, UserInfoConfig)
admin文件注册model表

 

下面是我们的admin文件里面类下面的其中一个自定义方法,

 1     # 这个功能主要实现的是我们的数据增删改查里面的增加功能,我们的表格里面如果数据过多,不想一次显示过多的字段输入框,
 2     # 我们可以使用下面的自定义方法,把一部分字段的输入框给折叠起来,
 3     fieldsets = (
 4         ('fred', {  # 这里是在上面显示的单独的输入框
 5                 'fields': ('roles', 'email')
 6             }),
 7         ('something_else', {  # 这里是折叠的输入框
 8             'classes': ('collapse', 'wide', 'extrapretty',),  # 这里是我们内置的参数,是固定的,不可改动,它是源码里面需要的参数,不可动
 9             'fields': ('name',),
10         }),
11     )

 

单例模式:

我所理解的就是我们的单例模式下的类实例化出来的对象都是指向同一个内存地址,同一个值,我们的这个类实例化出来的两个对象,他们指向同一个内存空间,他们的值也一样,这就是单例模式

 

举例说明:

 1 # class Bird(object):
 2 #     def __init__(self,name,age):
 3 #         self.name=name
 4 #         self.age=age
 5 #
 6 # a=Bird("小鸟",2)
 7 # b=Bird("企鹅",23)
 8 # c=Bird("鸵鸟",2)
 9 
10 # class Config(object):
11 #     def __init__(self):
12 #         pass
13 #
14 # c1=Config()
15 # c2=Config()
16 
17 # 基于__new__实现的单例模式
18 class Singleton(object):
19     _instance = None
20     def __new__(cls, *args, **kw):
21         if not cls._instance:
22             cls._instance = super(Singleton, cls).__new__(cls, *args, **kw)
23         return cls._instance
24 
25 class MyClass(Singleton):
26     a = 1
27 
28 
29 c1=MyClass()
30 c2=MyClass()
31 
32 c1.a=11
33 print(c2.a)

 

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

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

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

使用模块,

使用__new__

使用装饰器(decorator)

使用元类(metaclass)

 

(1)使用__new__

为了使类只能出现一个实例,我们可以使用__new__来控制实例的创建过程,代码如下:

class Singleton(object):
    _instance = None
    def __new__(cls, *args, **kw):
        if not cls._instance:
            cls._instance = super(Singleton, cls).__new__(cls, *args, **kw)  
        return cls._instance  

class MyClass(Singleton):  
    a = 1

# 在上面的代码中,我们将类的实例和一个类变量_instance关联起来,如果cls._instance为None则创建实例,
# 否则直接返回cls._instance.
# 具体执行情况如下:
# one = MyClass()
# tow = MyClass()
# print(one == tow) # True
# print(one is tow) # True
# one.a = 11
# print(tow.a) # 11
# print(id(one), id(tow)) # 42703784 42703784

 

(2)使用模块

其实,python的模块就是天然的单例模式,因为模块在第一次导入时,会生成.pyc文件,当第二次导入时,就会直接加载.pyc文件,而不会再次执行模块代码,因此,我们只需要把相关的函数和数据定义在一个模块中,就可以获得一个单例对象了,如果我们真的想要一个单例类可以考虑这样做:

# mysingleton.py
class My_singleton(object):
    def foo(self):
        pass

just=My_singleton()

我们把上面的代码保存在文件mysingleton中,像这样用:
from mysingleton import My_singleton,just  
# 我们可以引用文件的时候把文件名写在import前面然后把该文件里面的变量名都写入到import后面,前提是我们要用到它,否则没有必要浪费内存
just.foo()

 

 

 

admin执行流程,源码时间:

<1>循环加载执行所有已经注册的App中的admin.py文件:

我们在admin.py文件里面有一句话在最上面,系统自动加载的

from django.contrib import admin 

我们把admin点进去,就会进入一个init文件,在这个init文件里面有一个函数:

def autodiscover():
    autodiscover_modules('admin',register_to=site)

它里面有一个autodiscover_modules,可以点进去,有一个函数,专门实现了循环加载的功能:

 1 def autodiscover_modules(*args, **kwargs):
 2     """
 3     Auto-discover INSTALLED_APPS modules and fail silently when
 4     not present. This forces an import on them to register any admin bits they
 5     may want.
 6 
 7     You may provide a register_to keyword parameter as a way to access a
 8     registry. This register_to object must have a _registry instance variable
 9     to access it.
10     """
11     from django.apps import apps
12 
13     register_to = kwargs.get('register_to')
14     for app_config in apps.get_app_configs():
15         for module_to_search in args:
16             # Attempt to import the app's module.
17             try:
18                 if register_to:
19                     before_import_registry = copy.copy(register_to._registry)
20 
21                 import_module('%s.%s' % (app_config.name, module_to_search))
22             except Exception:
23                 # Reset the registry to the state before the last import
24                 # as this import will have to reoccur on the next request and
25                 # this could raise NotRegistered and AlreadyRegistered
26                 # exceptions (see #8245).
27                 if register_to:
28                     register_to._registry = before_import_registry
29 
30                 # Decide whether to bubble up this error. If the app just
31                 # doesn't have the module in question, we can ignore the error
32                 # attempting to import it, otherwise we want it to bubble up.
33                 if module_has_submodule(app_config.module, module_to_search):
34                     raise
admin循环加载程序代码

 

<2>执行代码

# admin.py

# admin.py
class PermissionGroupConfig(admin.ModelAdmin):
    list_display = ['caption', 'menu']  # 我们这里的 list_display里面
admin.site.register(PermissionGroup, PermissionGroupConfig)

 

<3>admin.site

 

 

 

 

这里应用的是一个单例模式,对于AdminSite类的一个单例模式,执行的每一个App中的每一个admin.site都是一个对象

 

<4>执行register方法

admin.site.register(Book,BookAdmin)

admin.site.register(Pulish)

class ModelAdmin(BaseModelAdmin): pass

class AdminSite(object):
    def register(self,model_or_iterable,admin_class=None,**options):
    
    if not admin_class:
        admin_class = ModelAdmin
        # Instantiate the admin class to save in the registry
        sefl._registry[model]=admin_class(model,self)

在每一个App的admin.py中加上一句话

print(admin.site._registry) 

打印结果如下所示:

{<class 'django.contrib.auth.models.Group'>: <django.contrib.auth.admin.GroupAdmin object at 0x0000000004533B00>,
 <class 'django.contrib.auth.models.User'>: <django.contrib.auth.admin.UserAdmin object at 0x0000000004560DD8>, 
 <class 'rbac.models.UserInfo'>: <rbac.admin.UserInfoConfig object at 0x0000000004574710>,
 <class 'rbac.models.Role'>: <django.contrib.admin.options.ModelAdmin object at 0x0000000004574748>,
 <class 'rbac.models.Menu'>: <django.contrib.admin.options.ModelAdmin object at 0x0000000004574780>,
 <class 'rbac.models.PermissionGroup'>: <rbac.admin.PermissionGroupConfig object at 0x00000000045747F0>, 
 <class 'rbac.models.Permission'>: <rbac.admin.PermissionConfig object at 0x0000000004574898>}
{<class 'django.contrib.auth.models.Group'>: <django.contrib.auth.admin.GroupAdmin object at 0x0000000004633B38>,
 <class 'django.contrib.auth.models.User'>: <django.contrib.auth.admin.UserAdmin object at 0x0000000004660E10>, 
 <class 'rbac.models.UserInfo'>: <rbac.admin.UserInfoConfig object at 0x0000000004674748>,
 <class 'rbac.models.Role'>: <django.contrib.admin.options.ModelAdmin object at 0x0000000004674780>, 
 <class 'rbac.models.Menu'>: <django.contrib.admin.options.ModelAdmin object at 0x00000000046747B8>, 
 <class 'rbac.models.PermissionGroup'>: <rbac.admin.PermissionGroupConfig object at 0x0000000004674828>, 
 <class 'rbac.models.Permission'>: <rbac.admin.PermissionConfig object at 0x00000000046748D0>}

是我们所有的在models里面注册的表格,其中Group表格和User表格是我们的django自带的表格,由于我们的程序是debug模式,所以它会打印两遍,如果不是error模式就不会打印两遍了

 

posted @ 2018-03-09 17:29  dream-子皿  阅读(205)  评论(0)    收藏  举报