Xadmin控件的实现:〇三查询视图一——基础功能

------------恢复内容开始------------

从现在开始我们要着手进行查询效果的实现

数据展示

直接显示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>
表格显示的list.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>
list的表格部分html代码

看看效果

 这样就完成了最基础的展示的视图,先到这里,后面我们在看看怎么实现分页、filter等效果

原文地址:https://www.cnblogs.com/yinsedeyinse/p/13492640.html