内容回顾:
一 admin的使用
app01的admin.py文件:
class BookConfig(admin.ModelAdmin):
list_display=[]
list_display_links=[]
list_filter=[]
search_fields=[]
def patch(self,request,queryset):
pass
patch.short_desc=""
actions=[patch,]
admin.site.register(Book,BookConfig)
二 admin的源码解析
1 启动
加载settings中install_app
from django.contrib import admin
autodiscover_modules('admin')
2 注册
源码:django.contrib.admin.sites模块
class AdminSite(object):
def __init__(self):
self._registry = {}
def register(self,model,admin_class=None):
# 设置配置类
if not admin_class:
admin_class = ModelAdmin
self._registry[model] = admin_class(model, self)
site = AdminSite()
每一个app下的admin.py文件:
from django.contrib import admin
admin.site.register(Book,BookConfig)
admin.site.register(Publish)
我们先来说一下admin组件中的第三步,他是如何设计url的
我们之所以能进去admin后台管理界面,是因为有这个url:
我们之前配url时,是这样配置的 url(r"index/",views.index),但是对于admin中,一张表就会对应有多个url,就拿Food表来说:
针对Food表,url:
http://127.0.0.1:8000/admin/app02/food/
http://127.0.0.1:8000/admin/app02/food/add/
http://127.0.0.1:8000/admin/app02/food/1/change/
http://127.0.0.1:8000/admin/app02/food/2/delete/
那么admin是怎么实现只配那么一条url呢?其实我们能够想到二级路由,但这里不是二级路由实现的。
我们先来学习一个知识点:
我们这样配url:
‘test/’url后面不写视图函数,后面写一个元祖,元祖里面有三个参数,第一个元祖是一个列表,第二个参数,和第三个参数我们暂时用不到,所以写None。而这个列表中写第二个url,然后我们在app01下的视图函数中写函数aaa。我们可以写的更复杂一点,可以仿照admin中的url写。
url(r'^stark/', ([ url(r'app01/', ([ url(r'food/', ([ url(r'^$', listview), url(r'add/$', addview), url(r'(\d+)/change/$', changeview), url(r'(\d+)/delete/$', delview), ], None, None))], None, None)) ], None, None))
其实admin后面的admin.site.urls就是实现了这样的url的配置。
这样写肯定也是不对的,也因为我们有很多表,会在不同的app下,当我们点击相应的表会显示相应的app和相应的表的内容,实现一个动态的效果。那么我们怎么来获取相应的app名和表的名呢?
我们在注册的时候不就是往admin.site._registry中添加键值对吗,那个键值对里面刚好有我们需要的。我们可以打印出来看看。
先在modes中写几张表,然后再admin中注册。
def get_urls(): temp=[] print(admin.site._registry) return temp urlpatterns = [ # url(r'^admin/', admin.site.urls), url(r'^admin/',(get_urls(),None,None)) # 这里把元祖中的第一个列表参数写成了一个函数,函数仍然返回的是一个列表,在函数中我们打印admin.site._registry键值对。 ]
这是打印的一条结果,他的健是models中的Publish表对象,他的值为admin中自定义写的PublishConfig类对象,那我们怎么取我们想要的东西呢?这里介绍两个方法:
model_name = model._meta.model_name # 获取当前model表名 app_label = model._meta.app_label # 获取所在app的名字 print(model_name) print(app_label)
既然能够拿到model表的名字和app名,我们就能够实现表的一个动态的增删改查:
接下来我们仿照admin实现一个自定义的增删改查的组件
我们新建一个项目,然后再项目里新建另一个app,起名为stark,作为我们的组件。我们自己写组件的时候,要先理解admin组件是怎么工作的,要清楚admin组件中的流程。不然会很难理解。(别忘了创建stark的app后,将stark添加进settings中的INSTALLED_APPS)。
我们就是要做类似于admin源码里面的那三步,将那三步写进我们自己的stark组件中去。
我们先按照admin的流程来想,我们每个app下面都会有admin.py文件,当我们启动django时,会执行app下的admin.py文件, 这时候是因为执行了源码中的:
def autodiscover():
autodiscover_modules('admin', register_to=site)
那么我们就想要让他启动时先执行我们自己创建的stark.py文件,也应该执行这句代码。我们先在app01下创建stark.py文件。那这句代码应该放在哪呢?
我们将stark添加进settings中的INSTALLED_APPS,django启动时,会逐个扫描里面的每一个,当他扫描到stark.apps.StarkConfig的时候,会执行StarkConfig,点进去,就会到stark app下的apps.py文件,意思就是说当他扫描到stark.apps.StarkConfig的时候,会首先执行apps.py文件。所以我们要把这句话放在apps.py文件里。
这样写完之后,启动后就可以先执行app01下的stark.py文件中的内容了。
启动完了之后,就是注册了。
admin在注册的时候其实就是执行了AdminSite的一个单例模式:就是执行了这样一段代码:
class AdminSite(object): def __init__(self): self._registry = {} def register(self,model,admin_class=None): # 设置配置类 if not admin_class: admin_class = ModelAdmin self._registry[model] = admin_class(model, self) site = AdminSite()
我们要把这段代码单独放在一个模块里,我们也把这个模块的名字叫做sites:
# 默认的配置类
class ModelStark(object): def __init__(self,model): self.model = model # stark组件的全局类 class AdminSite(object): def __init__(self): self._registry = {} def register(self, model, admin_class=None): # 设置配置类 if not admin_class: admin_class = ModelStark self._registry[model] = admin_class(model) site = AdminSite()
这样就可以了。
接下来 ,我们在model里面放几张表。我们之前在admin中注册的时候是这样写的:
from django.contrib import admin # Register your models here. from app01.models import Book,Publish,Author,AuthorDetail # 定义自己的类 class BookConfig(admin.ModelStark): # list_display' must not be a ManyToManyField. list_display=["title","price","publishDate","publish"] list_display_links = ["price","title"] list_filter = ["title","publish","authors"] search_fields = ["title","price"] # 批量操作 def patch_init(self,request,queryset): queryset.update(price=0) patch_init.short_description = "价格初始化" actions =[patch_init] admin.site.register(Book,BookConfig) class PublishConfig(admin.ModelAdmin): list_display = ["name","email"] admin.site.register(Publish,PublishConfig) admin.site.register(Author) admin.site.register(AuthorDetail)
也就是说要在我们自己app01下的stark.py下进行注册。就应该这样写:
from stark import sites from app01 import models sites.site.register(models.Book) sites.site.register(models.Publish) sites.site.register(models.AuthorDetail) sites.site.register(models.Author)
当然我们也可以添加自己的类:
from stark import sites from app01 import models # 定义自己的类 class Bookconfig(sites.ModelStark): # list_display' must not be a ManyToManyField. list_display = ["title", "price", "publishDate", "publish"] list_display_links = ["price", "title"] list_filter = ["title", "publish", "authors"] search_fields = ["title", "price"] # 批量操作 def patch_init(self, request, queryset): queryset.update(price=0) patch_init.short_description = "价格初始化" actions = [patch_init] sites.site.register(models.Book,Bookconfig) sites.site.register(models.Publish) sites.site.register(models.AuthorDetail) sites.site.register(models.Author)
这样就注册好了。
这时候我们也可以print(sites.site._registry)看看里面有几个键值对。
接下来就是第三部分,设计url了:
之前我们已经写好了url,但是:
urlpatterns = [ # url(r'^admin/', admin.site.urls), url(r'^admin/',(get_urls(),None,None)) ]
我们写的这个url和admin中的不一样,我们也希望将自己写的url放进我们写的stark组件中。我们应该这样写:
from stark import sites
urlpatterns = [ url(r'^stark/',sites.site.urls) ]
这样写那说明,site的那个实例化类中要有urls的这个方法,只要这个urls方法返回的是([],None,None),
接下来我们就要在那个类中添加这个方法:
def get_urls2(self): temp = [ url(r'^$', listview), url(r'add/$', addview), url(r'(\d+)/change/$', changeview), url(r'(\d+)/delete/$', delview), ] return temp def get_urls(self): temp = [] for model, config_obj in self._registry.items(): # 循环分别打印健和值。 model_name = model._meta.model_name # 获取当前model的表名 app_label = model._meta.app_label # 获取所在的app名字 print(model_name) print(app_label) temp.append(url(r'%s/%s/' % (app_label, model_name), (self.get_urls2(), None, None))) return temp @property def urls(self): return self.get_urls(),None,None
在AdminSite()类中添加这些东西,我们觉得这样就成功了,但是一级url的分发(get_urls())和二级url的分发( get_urls2()),是不能放在一个类中的,因为我们的二级url分发是需要访问视图函数的,那么我们怎么能够保证当我们点击书籍的时候就访问书籍的列表
访问publish就展示publish的列表,如果我们这样写,就只能进入一个视图函数了。
所以二级url的分发不能放在这个类里,那我们把这个二级url的分发放在哪里呢?放在那个配置类里面去
将二级url的分发放在配置类中有什么好处?config_obj是我们的配置类, 可以是默认的配置类也可以是自定义的配置类。在这里我们要明白这个self指的是谁,他指的是当前这个配置类,而这个配置类中有就有参数model。虽然访问的时候都要进相同的视图函数,但是每次访问不同的表时的配置类不一样,所以self就不一样,进入的视图函数也就不一样。
这样了解之后,就可以完善视图函数了:
def listview(self,request): print(self)# 当前访问摩星表的配置类对象 print(self.model)# 当前访问的模型表 data_list = self.model.object.all() return render(request,'list.html',locals())
重点要知道:
我们在二级url分发的时候要弄清楚这个self指的是谁,通过这个图要了解一下这个这个self顺序是怎么查询的?
当我们查询book表的时候,二级url要走视图函数,那么这个视图函数中的self是怎么找到这个视图函数的?
首先要明白是谁调用的这个self,Bookconfig(book),他先在自己的实例空间中找视图函数,如果没有,就从自己对应的类空间里找视图函数,如果还没有,就从继承的父类中找视图函数,一步一步往上找。