------------恢复内容开始------------
从现在开始我们要着手进行查询效果的实现
直接显示QuerySet对象
数据的展示是最最基础的功能,第一步我们就要实现如何获取到要显示的数据
1 class ConfXadmin(object): 2 3 list_display = ["__str__",] #这里一定要是个元组或列表,如果少了逗号的时候会在后面的extend方法中把字符串打散 4 5 #获取需要显示的字段 6 def get_display_field(self): 7 temp = [] 8 temp.extend(self.list_display) 9 return temp 10 11 def list_view(self,request): 12 all_data = self.model.objects.all() 13 14 show_data_list = [] 15 16 for model_obj in all_data: 17 temp = [] 18 19 for field in self.get_display_field(): 20 21 val = getattr(model_obj,field) 22 temp.append(val) 23 24 show_data_list.append(temp) 25 26 27 return render(request,'list.html',locals())
上面两个方法就是获取到所需数据的视图。来逐步分析一下
第2行我们创建了一个list_display变量,放了一个字符'__str__’,这个对应的是我们在models.py里给每个ORM类定义的一个方法(像下面这段一样)。
class Publisher(models.Model): id = models.AutoField(primary_key=True) name = models.CharField(null = False,max_length=16,verbose_name='名称') def __str__(self): return self.name
具体的使用方法我们后面会讲到
但是这里要做注意的是,list_display里必须是一个元组或者列表,如果用下面的方式定义是不行的
list_display = ("__str__")
注意,是少了逗号,这里有个比较有意思的地方,我们可以在terminal里试一试
>>> a=[] >>> b=['abc'] >>> c=('abc') >>> d=('abc',) >>> a.extend(b) >>> a ['abc'] >>> a.extend(c) >>> a ['abc', 'a', 'b', 'c'] >>> a.extend(d) >>> a ['abc', 'a', 'b', 'c', 'abc']
c的数据类型是字符串,而不是tuple,所以会被整个打散添加到列表里。而b的数据类型就是个list,不存在这种问题。
经过get_display_field()返回的值和我们在类里一开始定义的list_display是一样的,是因为有些功能我们后期要用到,所以先把函数放在这里。
下面主要看一看list_view这个视图,
获取数据是比较简单的,但是第12行的all_data是个QuerySet对象, QuerySet在视图中可以用for循环的方式来显示出来
<div> {%for data in all_data%} <div> {{data}} </div> {%endfor%} </div>
在上面一段html代码中,可以直接用循环的方式显示出我们获得的QuerySet里的每个object对象
但是如果想要获得每个object里的字段,就不能简简单单的这样做了。
默认的字段显示
上面的代码从第19行开始是用一个嵌套的for循环来生成一个二维的列表
model_obj是QuerySet里的每一个object对象,第二个for循环是把我们需要的字段内容从object里拿出来
但是我们在display_list里只定义了一个__str__,所以只会显示一个书名。
第二层的for循环就是把我们在display_list里制定的字段拿出来,放在列表里。主要是因为第一层的object是不可迭代的对象。在模板中就不能用for循环显示出来了。所以要用这种二维的列表才可以。
假设我们需要显示出书籍的价格,只要修改一下前面定义的disp_list就可以
list_display = ["__str__",'price']
然后把模板里再加一层for循环
<div> {%for data in show_data_list%} <div> {%for field in data%} {{field}} {%endfor%} </div> {%endfor%} </div>
这样就能显示出来书籍的价格了
但是这就有一个问题出来,在访问其他的数据库对象后,就会报错
因为并不是所有的model里都有price这个字段的,所以这样做是不行的。需要我们队每一个要显示的数据库配一个独立的配置类。
带有自定义配置的Model类注册
还记得我们前面怎么完成的model类的注册么?在app下的Xadmin.py文件下
site.register(models.Books)
而我们在写site对应的XadminSite类的时候,指定了在没有自定义的配置类传入的时候是用默认的配置类的,也就是ConfXadmin直接生成的对象在site里的那个self._registry字典中。
现在我们需要指定一些需要的字段显示在list视图里,
1 from Xadmin.service.Xadmin import site 2 from Xadmin.service.Xadmin import ConfXadmin 3 from . import models 4 from copy import deepcopy 5 6 7 class BookConf(ConfXadmin): 8 list_display = deepcopy(ConfXadmin.list_display) 9 list_display.append('price') 10 11 12 site.register(models.Books,BookConf) 13 14 site.register(models.Publisher
在Xadmin.py中我们重新定义了一个BookConf类,这个类还继承了ConfXadmin,那么就可以对原有的list_display变量重新追加一些需要显示的字段名。但是要注意的是获取原类的时候要用deepcopy,否则会影响到其他实例化的对象
看一看下面的例子
1 from copy import deepcopy 2 class A(): 3 A_data = ['a'] 4 def a(self): 5 print(self.A_data) 6 7 8 class B(A): 9 # A_data = deepcopy(A.A_data) 10 A_data = A.A_data 11 def b(self): 12 self.A_data.append('in B') 13 14 a1 = A() 15 b = B() 16 a2 = A() 17 print('**********') 18 a1.a() 19 a2.a() 20 print("**********") 21 ##########输出########## 22 ********** 23 ['a'] 24 ['a'] 25 ********** 26 ['a', 'in B'] 27 ['a', 'in B'] 28 b.b() 29 a1.a() 30 a2.a()
所以一定要用深拷贝!!!
这样就好咯!忽略前端的简陋效果,只为了完成功能
美化一下显示效果,加上bootstrap的资源,用表格的方式吧内容显示出来
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <meta name="viewport" content="width=device-width, initial-scale=1.0"> 6 <title>Document</title> 7 <link rel="stylesheet" href="/static/bootstrap/css/bootstrap.min.css"> 8 <script src="/static/jquery-3.2.1.min.js"></script> 9 10 </head> 11 <body> 12 <h3>数据列表</h3> 13 14 <div class="container"> 15 <div class="row"> 16 <div class="col-md-9"> 17 <table class="table table-bordered table-striped"> 18 <thead> 19 <tr> 20 {%for item in head_list%} 21 <th> 22 {{item}} 23 </th> 24 {%endfor%} 25 </tr> 26 </thead> 27 <tbody> 28 {%for data in show_data_list%} 29 <tr> 30 {%for item in data%} 31 <td>{{item}}</td> 32 {%endfor%} 33 </tr> 34 {%endfor%} 35 </tbody> 36 </table> 37 </div> 38 </div> 39 </div> 40 </body> 41 </html>
有些时候我们需要添加一些新的功能,比方选择框CheckBox、修改、删除的a标签什么的,比方在每条记录后面加一个编辑的按钮可以进入编辑页面
选择框
选择框的实现比较简单
1 class ConfXadmin(object): 2 3 modelform_class = None 4 5 list_display = ["__str__"] #这里一定要是个元组,如果少了逗号的时候会在后面的extend方法中把字符串打散 6 7 8 def __init__(self,model,site): 9 self.model = model 10 self.model_name = self.model._meta.model_name 11 self.app_name = self.model._meta.app_label 12 13 @property 14 def urls2(self): 15 return self.get_urls2(),None,None 16 17 def get_urls2(self): 18 temp = [] 19 #通过name设置反向解析 20 temp.append(url(r'^$',self.list_view,name='{}_{}_list'.format(self.app_name,self.model_name))) 21 temp.append(url(r'^add/$',self.add_view,name='{}_{}_add'.format(self.app_name,self.model_name))) 22 temp.append(url(r'^(d+)/change/$',self.change_view,name='{}_{}_change'.format(self.app_name,self.model_name))) 23 temp.append(url(r'^(d+)/delete/$',self.delete_view,name='{}_{}_delete'.format(self.app_name,self.model_name))) 24 return temp 25 26 27 def get_list_url(self): 28 model_name = self.model._meta.model_name 29 app_label = self.model._meta.app_label 30 _url = reverse("%s_%s_list"%(app_label, model_name)) 31 return _url 32 33 def get_modelform_class(self): 34 if not self.modelform_class: 35 from django.forms import ModelForm 36 37 class ModelFormDemo(ModelForm): 38 class Meta: 39 model = self.model 40 fields = '__all__' 41 42 return ModelFormDemo 43 else: 44 return self.modelform_class 45 46 #选择按钮 47 def select(self,obj): 48 return mark_safe("<input type='checkbox'>") 49 50 #获取需要显示的字段 51 def get_display_field(self): 52 temp = [] 53 temp.append(ConfXadmin.select) 54 temp.extend(self.list_display) 55 return temp 56 57 58 def list_view(self,request): 59 all_data = self.model.objects.all() 60 61 show_data_list = [] 62 63 for model_obj in all_data: 64 temp = [] 65 66 for field in self.get_display_field(): 67 if callable(field): 68 print(field) 69 val = field(self,model_obj) 70 else: 71 val = getattr(model_obj,field) 72 temp.append(val) 73 74 show_data_list.append(temp) 75 76 77 return render(request,'list.html',locals())
我们先定义一个函数,让他生成一个html标签的字符串,函数的返回值要用safe_mark的形式返回,也就是第46行的select函数。
函数的传参不光给了个self,还有个obj,在这个函数中是用不到的,是因为在后面其他的一些按钮,比方删除和修改,是需要被编辑对象的id的,那就需要从当前循环的model_obj里的id的,所以在for循环里需要传model_obj给field。
然后把get_display_field函数里面加上新的函数(第53行),在append的时候注意顺序,因为返回的temp是按照顺序渲染在页面上,所以必须按照需求顺序append。
视图里的for循环改动的思路比较有意思,我们对field进行一下callabled判断,条件判断值为TRUE,说明当前的这个字段是个函数,否则就是在model的ORM类里定义的字段。这样出来的效果就是这样的
编辑、删除按钮
对每条记录进行编辑、删除是需要有个按钮的,可以按照下面的思路添加进去
1 #删除按钮 2 def edit(self,obj): 3 _url = reverse("{}_{}_change".format(self.app_name,self.model_name),args=(obj.pk,)) 4 return mark_safe("<a href='{}' class = 'btn btn-info'>编辑</a>".format(_url)) 5 6 #删除按钮 7 def remove(self,obj): 8 _url = reverse("{}_{}_delete".format(self.app_name,self.model_name),args=(obj.pk,)) 9 return mark_safe("<a href='{}' class = 'btn btn-warning'>删除</a>".format(_url)) 10 11 #选择按钮 12 def select(self,obj): 13 return mark_safe("<input type='checkbox'>") 14 15 #获取需要显示的字段 16 def get_display_field(self): 17 temp = [] 18 temp.append(ConfXadmin.select) 19 temp.extend(self.list_display) 20 temp.append(ConfXadmin.edit) 21 temp.append(ConfXadmin.remove) 22 return temp
这里要注意的是 和选择按钮不同的是edit和remove的url反向解析涉及到了一个参数id,所以要用到args来赋值。其他的用法都差不太多了,直接看看效果
注意看上面动图中的url,点击编辑的时候跳转的URL是带有相关的ID参数的。
经过前面的一系列的操作,我们已经成功的拿到数据然后按照表格的效果显示出来,可是字段的名称并没有显示出来。我们要获取每个字段的字段名,就要用一个循环来获取一个列表,下面的代码是list_view里的一部分,专门用来获取字段的标题
1 #构建表头 2 field_title_list = [] 3 for field in self.get_display_field(): 4 if callable(field): 5 field_title = field(self,get_title=True) 6 else: 7 8 if field == '__str__': 9 field_title = self.model_name.upper() 10 else: 11 field_title = self.model._meta.get_field(field).verbose_name 12 13 field_title_list.append(field_title)
其实很简单,先对我们定义的要显示的字段列表进行for循环,拿到每个字段,再对这个字段进行判定,如果是可调用的,就从函数获取,否则就是定义的字段名,如果就是"__str__"就直接用model的名称,否则就从字段里获取名称。
这里需要对前面定义的select/edit/remove方法做一些修改,先看看是怎么修改的
1 #删除按钮 2 def edit(self,obj=None,get_title=False): 3 if not get_title: 4 _url = reverse("{}_{}_change".format(self.app_name,self.model_name),args=(obj.pk,)) 5 return mark_safe("<a href='{}' class = 'btn btn-info'>编辑</a>".format(_url)) 6 else: 7 return '编辑' 8 9 #删除按钮 10 def remove(self,obj=None,get_title=False): 11 if not get_title: 12 _url = reverse("{}_{}_delete".format(self.app_name,self.model_name),args=(obj.pk,)) 13 return mark_safe("<a href='{}' class = 'btn btn-warning'>删除</a>".format(_url)) 14 else: 15 return '删除' 16 #选择按钮 17 def select(self,obj=None,get_title=False): 18 if not get_title: 19 return mark_safe("<input type='checkbox'>") 20 else: 21 return mark_safe("<input type='checkbox' class = 'selected'>全选")
修改的思路是一样的,主要是对传参这一块进行一些修改:
先把obj给了个默认的值为空,然后定义了另一个形参:get_title,默认为FALSE,可以看出来函数有两个返回值,当get_title为真是,意思是函数的返回值为filed的字段名,否则就是返回一段html代码嵌到前端里。
给obj和get_title赋默认值主要是为了简化了后面的代码。
对了,对应的前端的模板也要加thead标签
1 <div class="container"> 2 <div class="row"> 3 <div class="col-md-9"> 4 <h3>数据列表</h3> 5 <table class="table table-bordered table-striped"> 6 <thead> 7 <tr> 8 {%for item in field_title_list%} 9 <th> 10 {{item}} 11 </th> 12 {%endfor%} 13 </tr> 14 </thead> 15 <tbody> 16 {%for data in show_data_list%} 17 <tr> 18 {%for item in data%} 19 <td>{{item}}</td> 20 {%endfor%} 21 </tr> 22 {%endfor%} 23 </tbody> 24 </table> 25 </div> 26 </div> 27 </div>
看看效果
这样就完成了最基础的展示的视图,先到这里,后面我们在看看怎么实现分页、filter等效果