django-admin 仿写stark组件,pop_up最终极版本

 

我们的最终极版在下面,把复用的HTML页面做成自定义标签,供其他的页面调用,解耦效果更好 

我们的pop_up里面最核心的就是HTML部分,涉及到有js语法

我们的pop_up是在添加页面里面做出来的,

add_HTML页面:

 1 <!DOCTYPE html>
 2 <html lang="en">
 3 <head>
 4     <meta charset="UTF-8">
 5     <meta http-equiv="X-UA-Compatible" content="IE=edge">
 6     <meta name="viewport" content="width=device-width, initial-scale=1">
 7     <link rel="stylesheet" href="https://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.min.css">
 8     <link rel="stylesheet" href="/static/css/base.input.css">
 9     <script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.js"></script>
10     <script src="https://cdn.bootcss.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>
11     <title>Title</title>
12 </head>
13 <body>
14 <h3>添加数据</h3>
15 
16 {% include 'file/base_form.html' %}
17 
18 <script>
19     function hi(ret){
20         console.log(ret);
21         var res=JSON.parse(ret);
22         var ele_option=document.createElement("option");
23         ele_option.value=res.pk;
24         ele_option.innerHTML=res.text;
25         ele_option.selected="selected";
26         console.log(ele_option);
27 
28         document.getElementById(res.pop_id).appendChild(ele_option)
29     }
30 </script>
31 </body>
32 </html>
add_HTML页面

 

base_HTML页面

 1 <div class="container">
 2     <div class="row">
 3         <div class="col-md-8">
 4 
 5             <form action="" method="post" novalidate>
 6                 {% csrf_token %}
 7                 {% for i in form %}
 8                     <div class="form-group put_in" >
 9                         <label for="">{{ i.label }}</label>
10                         <div class="input_style">{{ i }}
11                             <span class="error">{{ i.errors.0 }}</span>
12                         </div>
13                     {% if i.is_pop %}
14                         <a href="" onclick="pop('{{ i.url }}')" class="pop_btn"><span class="pull-right sign">+</span></a>
15                     {% endif %}
16                     </div>
17                 {% endfor %}
18 
19                 <div class="form-group">
20                     <p><input type="submit" class="btn btn-primary"></p>
21                 </div>
22 
23             </form>
24         </div>
25     </div>
26 </div>
27 
28 <script>
29     function pop(url){
30         window.open(url,"","width=400,height=300,top=200,left=500")
31     }
32 </script>
add调用的组件HTML页面

 

pop_HTML页面

 1 <!DOCTYPE html>
 2 <html lang="en">
 3 <head>
 4     <meta charset="UTF-8">
 5     <meta http-equiv="X-UA-Compatible" content="IE=edge">
 6     <meta name="viewport" content="width=device-width, initial-scale=1">
 7     <title>Title</title>
 8 </head>
 9 <body>
10 
11 <script>
12     opener.hi('{{ ret|safe }}');
13     window.close();
14 </script>
15 </body>
16 </html>
相应add主页面的副页面

 

 1   # ModelForm校验数据添加页面
 2     def add_view(self, request):
 3         FormClass = self.get_modelform_class()
 4         if request.method == 'GET':
 5             form = FormClass()
 6 
 7             from django.forms.boundfield import BoundField
 8             from django.forms.models import ModelChoiceField
 9 
10             for bound_field in form:
11                 if isinstance(bound_field.field, ModelChoiceField):
12                     bound_field.is_pop = True
13                     print(bound_field.field.queryset.model)
14 
15                     app_label = bound_field.field.queryset.model._meta.app_label
16                     model_name = bound_field.field.queryset.model._meta.model_name
17                     _url = "%s_%s_add" % (app_label, model_name)
18                     bound_field.url = reverse(_url)+"?pop_id=id_%s" % bound_field.name
19                 else:
20                     bound_field.is_pop = False
21                     bound_field.url = None
22 
23             return render(request, 'file/add.html', {'form': form})
24         else:
25             data_list = FormClass(data=request.POST)
26             if data_list.is_valid():
27                 ret = data_list.save()
28                 pop_id = request.GET.get('pop_id')
29                 if pop_id:
30                     res = {'pk': ret.pk, 'text': str(ret), 'pop_id': pop_id}
31                     import json
32                     return render(request, 'file/pop_demo.html', {'ret': json.dumps(res)})
33 
34                 return redirect(self.get_stand_url())
35             else:
36                 return render(request, 'file/add.html', {'form': data_list})
add-view函数

 

静态文件配置:

 1 .form-group .input_style input, select{
 2     display:block;
 3     width:100%;
 4     height:34px;
 5     padding:6px 12px;
 6     font-size:14px;
 7     line-height: 1.42857143;
 8     color:#555;
 9     background-color:#fff;
10     background-image:none;
11     border:1px solid #ccc;
12     border-radius:4px;
13     -webkit-box-shadow:inset 0 1px 1px rgba(0,0,0, .075);
14     box-shadow:inset 0 1px rgba(0,0,0, .075);
15     -webkit-transition:border-color
16     ease-in-out .15s, -webkit-box-shadow ease-in-out .15s;
17     -o-transition:border-color ease-in-out .15s, box-shadow ease-in-out .15s;
18     transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s;
19 }
20 
21 .error{
22     color:red;
23 }
24 
25 .put_in{
26     position:relative
27 }
28 .pop_btn{
29     position:absolute;
30     top:45%;right:-24px;
31     /*vertical-align:2px;*/
32 }
33 .sign{
34     font-size:27px;
35 }
js静态文件

 

 

tag.py

  1 from django.conf.urls import url
  2 from django.shortcuts import render, redirect, reverse
  3 from django.utils.safestring import mark_safe
  4 from django.forms import ModelForm  # 这个ModelForm里面封装了很强大的功能,要把源码过一遍
  5 from django.db.models import ForeignKey, ManyToManyField
  6 from django.utils.http import urlencode
  7 import copy
  8 from types import FunctionType
  9 
 10 
 11 # 这个类主要帮我们处理多级过滤的a标签,我们之前是把多级过滤的a标签给写到get_filter_link_tags这个函数里面,
 12 # 后来为了实现功能解耦,避免单个函数代码量过大,就把这个功能封装成了一个类,以便于阅读,以及功能扩展
 13 class LinkTagsGen(object):
 14     def __init__(self, data, filter_field, request):
 15         self.data = data
 16         self.filter_field = filter_field
 17         self.request = request
 18 
 19     def __iter__(self):
 20         """
 21         所有的可迭代对象内部都是实现了__iter__方法,我们把数据写到这里就是实现的数据的可迭代
 22         :return: yield 多级过滤的A标签
 23         """
 24         current_id = self.request.GET.get(self.filter_field.filter_name, 0)
 25         params = copy.deepcopy(self.request.GET)
 26         params._mutable = True
 27         if params.get(self.filter_field.filter_name):
 28             del params[self.filter_field.filter_name]
 29             _url = "%s?%s" % (self.request.path_info, params.urlencode())
 30             yield mark_safe("<a href='%s'>All</a>" % _url)
 31         else:
 32             yield mark_safe("<a href='#' class='active'>All</a>")
 33 
 34         for item in self.data:  # self.data是一个个的queryset集合以及元祖,((1,'已出版'),(2,'未出版')),
 35             # <QuerySet[<Publish:人民出版社>,<Publish:北京出版社>]>
 36             # print(self.data)
 37             pk, text = None, None
 38             if self.filter_field.filter_obj.choices:
 39                 pk, text = str(item[0]), item[1]
 40             elif isinstance(self.filter_field.filter_obj, ForeignKey) or isinstance(self.filter_field.filter_obj, ManyToManyField):
 41                 pk, text = str(item.pk), item
 42             else:
 43                 pk, text = item[1], item[1]
 44 
 45             params[self.filter_field.filter_name] = pk
 46             _url = "%s?%s" % (self.request.path_info, params.urlencode())
 47             if current_id == pk:
 48                 link_tag = "<a class='active' href='%s'>%s</a>" %(_url, text)
 49             else:
 50                 link_tag = "<a href='%s'>%s</a>" % (_url, text)
 51             yield mark_safe(link_tag)
 52 
 53 
 54 # 为每一个过滤的字段封装成整体类
 55 class FilterField(object):
 56     def __init__(self, filter_name, filter_obj, config):
 57         self.filter_name = filter_name
 58         self.filter_obj = filter_obj
 59         self.config = config
 60 
 61     def get_data(self):
 62         if isinstance(self.filter_obj, ForeignKey) or isinstance(self.filter_obj, ManyToManyField):
 63             return self.filter_obj.rel.to.objects.all()
 64         elif self.filter_obj.choices:
 65             return self.filter_obj.choices
 66         else:  # 这里是取到我们的当前表格的所有数据,把pk值和字段名显示出来
 67             return self.config.model.objects.values_list('pk', self.filter_name)
 68 
 69 
 70 # 服务于ModelSubject下面的stand_li,我们把stand_li里面的很多方法给封装到这个类里面实现功能解耦,
 71 # 主要是为了减轻我们的StandLi里面的代码量
 72 class StandLi(object):
 73     def __init__(self, config, request, queryset):
 74         """
 75         :param config: 它就是我们下面的类ModelSubject所传过来的它的self实例对象,
 76         我们在这里要使用那些方法和变量就需要把它的这个实例对象拿过来,否则如下搬过来的代码块都会失效
 77         :param request: 我们这个类是在下面的ModelSubject里面调用然后在那里实例化出来的对象,所以这个request是它传过来的
 78         :param queryset: 同上,这个queryset也是ModelSubject所传过来的参数,供下面的代码调用
 79         """
 80         self.config = config
 81         self.request = request
 82         self.queryset = queryset
 83 
 84         # 生成分页器
 85         path = self.request.path_info
 86         params = self.request.GET
 87         page_num = request.GET.get('page', 1)  # 如果没有找到page,就返回1,也就是第一页
 88         from file.utensil.page import MyPage
 89         count = queryset.count()
 90         page = MyPage(page_num, count, path, params)
 91         self.pagination = page
 92         data_list = self.queryset[page.start:page.end]
 93         self.data_list = data_list
 94         # page_html = page.page_html()  # 这里我们可以把page_html方法直接在前端HTML模板里面引用
 95 
 96         # actions 实现批量操作功能
 97         self.actions = self.config.get_actions()  # [patch_init, patch_delete]
 98         # print('actions', self.actions)
 99 
100         # filter 实现筛选功能
101         self.list_filter = self.config.list_filter
102 
103     # 实现多级过滤的类里面封装的一个函数
104     def get_filter_link_tags(self):
105         for filter_name in self.list_filter:
106             filter_obj = self.config.model._meta.get_field(filter_name)
107             filter_field = FilterField(filter_name, filter_obj, self.config)
108             # 这里是按照位置传参,不能随便写的,这边传过去的是什么顺序,那边接收的就是什么顺序
109 
110 
111             # print("filter_field", filter_field.get_data())
112             """
113             filter_field <QuerySet [<Publish: 长春出版社>, <Publish: 香港中文大学出版社>, <Publish: 中信出版社>]>
114             filter_field ((0, '已出版'), (1, '未出版'))
115             filter_field <QuerySet [<Writer: White>, <Writer: Black>, <Writer: Miss Lin>]>
116             """
117             val = LinkTagsGen(filter_field.get_data(), filter_field, self.request)
118             yield val
119 
120     # 这里是使用两个yield去实现的多级过滤
121     #  展示筛选条件
122     # def get_filter_link_tags(self):  # list_filter=['state','publish','authors']
123     #
124     #     for filter_name in self.list_filter:
125     #         current_id = int(self.request.GET.get(filter_name, 0))  # 这里加上int之后我们点击超链接标签的时候就会有字体颜色的变化
126     #         # current_id = self.request.GET.get(filter_name, 0)  # 这里没有加上int点击超链接的时候不会有变化
127     #         # print('current_id', current_id)
128     #         filter_obj = self.config.model._meta.get_field(filter_name)
129     #         # print('filter_obj', filter_obj)
130     #         filter_field = FilterField(filter_name, filter_obj)
131     #         # print("filter_field",filter_field)
132     #         def inner(filter_field, current_id):
133     #             # print(filter_field.get_data())
134     #
135     #             # 这里得出的结果是我们的多对多字段和一对多字段的所有关联数据
136     #             # <QuerySet [<Publish: 长春出版社>, <Publish: 香港中文大学出版社>, <Publish: 中信出版社>]>
137     #             # <QuerySet [<Writer: White>, <Writer: Black>, <Writer: Miss Lin>]>
138     #
139     #             for obj in filter_field.get_data():
140     #                 if isinstance(filter_field.filter_obj, ForeignKey) or \
141     #                         isinstance(filter_field.filter_obj, ManyToManyField):
142     #                     # link_tags=[]
143     #                     params = copy.deepcopy(self.request.GET)
144     #                     params._mutable = True
145     #                     params[filter_field.filter_name] = obj.pk
146     #
147     #                     if current_id == obj.pk:
148     #                         yield mark_safe("<a class='active' href='?%s'>%s</a>" % (params.urlencode(), obj))
149     #                     else:
150     #                         yield mark_safe("<a href='?%s'>%s</a>" % (params.urlencode(), obj))
151     #                 elif filter_field.filter_obj.choices:
152     #                     params = copy.deepcopy(self.request.GET)
153     #                     params._mutable = True
154     #                     params[filter_field.filter_name] = obj[0]
155     #                     if current_id == obj[0]:
156     #                         yield mark_safe("<a class='active' href='?%s'>%s</a>" % (params.urlencode(), obj[1]))
157     #                     else:
158     #                         yield mark_safe("<a href='?%s'>%s</a>" % (params.urlencode(), obj[1]))
159     #                 else:
160     #                     # params = copy.deepcopy(self.request.GET)
161     #                     # params._mutable = True
162     #                     # params[filter_field.filter_name] = obj
163     #                     # yield mark_safe("<a href='?%s'>%s</a>" % (params.urlencode(), obj))
164     #                     ...
165     #         yield inner(filter_field, current_id)
166 
167     # 把自定义的action内容放到一个列表里面,以键值对的数据类型
168     def handle_actions(self):
169         temp = []
170         for action_func in self.actions:
171             temp.append({'name': action_func.__name__, "desc": action_func.desc})
172         return temp
173 
174     # 获取表头数据
175     def get_header(self):
176         # 生成表头数据
177         # ['id','title','price',edit]
178         header_list = []
179         for field in self.config.get_list_display():
180             if callable(field):
181                 ret = field(self, is_header=True)
182                 header_list.append(ret)
183             else:
184                 if field == '__str__':  # 这里是判断我们的list_display列表里面是否有我们自定义传入的值,如果没有的话,
185                     # 就是直接等于我们在静态属性里面设定的那个默认的'__str__',也就是说如果这一步判断成立,
186                     # 那么就证明我们的用户没有自定义展示的字段,我们就需要自己给浏览器一个字段去展示,那个字段就是我们这里所设定的那个大写的表名
187                     header_list.append(self.config.model._meta.model_name.upper())  # 我们这里的操作是
188                 else:
189                     obj = self.config.model._meta.get_field(field)  # 我们的list_display里面是一个个的字符串,
190                     # 把字符串放到get_field里面来可以把我们的字符串转换成类对象,
191                     header_list.append(obj.verbose_name)  # 我们这里的verbose_name在model里面是内置方法,
192                     # 我们的verbose_name本质上是对我们的字段进行描述的,比如我们的book里面的title可以在字段约束里面设
193                     # verbose_name='书名',类似于这样,把它变成中文,然后我们在前端HTML模板里面渲染的时候就可以渲染出来中文了,
194                     # 而不是使用默认的英文,当然了我们如果不设置verbose_name的值那么就使用默认的title作为字段名传到浏览器
195 
196         return header_list
197 
198     # 获取表单数据
199     def get_body(self):
200         # 生成表单数据列表(我们把这个函数挪到上面这个类里面来之后,把循环遍历的数据改动了,之前是把当前表格的数据取出来之后就直接遍历它,
201         # 后来我们有了搜索功能,那么就不能遍历表格里面的所有数据了,需要把我们过滤查询出来的数据给遍历出来
202         # ret = self.config.model.objects.all().count()
203         # print('self.list_display', self.list_display)
204         data_temp = []
205         for obj in self.data_list:  # 我们遍历这个queryset集合得到的obj是它的每一个对象
206             # print(self.data_list)
207             """
208             <QuerySet [<Writer: White>, <Writer: Black>, <Writer: Miss Lin>]>
209             """
210             temp = []
211             for field in self.config.get_list_display():  # 我们遍历list_display得到每一个字符串
212 
213                 if callable(field):
214                     # res = field(obj)  # @@@更上面的特殊标识的代码块相呼应
215                     res = field(self.config, obj)  # &&& 这里跟上面同样标识的代码块相呼应的,上面我们使用的类名去调用函数名,
216                     # 得到的是一个函数,这里就是给所调用的函数传参的,self,和obj都是我们传给函数的参数;
217                     # 如果我们使用self这个对象去调用函数名的方法的话,就不需要再传一个self作为参数进去了,我们两种方法都可以,需要对应上即可
218                 # if isinstance(field, FunctionType):
219                 #     res = field(self.config, obj=obj)
220                 elif getattr(obj, field):
221                     res = getattr(obj, field)
222                     if field in self.config.list_display_links:  # 我们这里是判断表单里面的字段是否在links表格里面
223                         # 被自定义作为可跳转标签,
224                         # 如果答案是肯定的,那么我们就需要把a标签给拼出来
225                         res = self.config.get_link_tag(obj, res)
226                 else:
227                     field_obj = self.config.model._meta.get_field(field)
228 
229                     if isinstance(field_obj, ManyToManyField):  # 这里是判断该字段是否是多对多字段,
230                         ret = getattr(obj, field).all()  # 使用getattr方法去判断该对象是否具有,field属性方法,
231                         # getattr里面需要两个参数(类对象,字符串属性方法).另外一点是,使用getattr(obj, field).all()就相当于
232                         # 是obj.fields.all取出该字段的所有数据
233                         list = []
234                         for obj in ret:
235                             list.append(str(obj))
236                         res = ' / '.join(list)
237 
238                 temp.append(res)
239             data_temp.append(temp)
240 
241         # print('data_list', data_list)
242 
243         """
244         我们最终得到的数据类型是如下格式:列表套着列表
245         [
246         使用orm语句得到的每一个类对象,有几个表格就有几个对象
247         ]
248         list_display=['id','title',]
249         [
250         [1,'python',<a>编辑</a>],
251         [2,'java',<a>编辑</a>],
252         ]
253         """
254         return data_temp
255 
256 
257 class ModelSubject(object):
258     """
259     我们在这里模拟admin源码里面的ModelAdmin,
260     """
261     list_display = ["__str__", ]  # 我们在这里给空列表里面加上"__str__",它就相当于是一个默认值,
262     model_form_class = None  # 为下面我们判断用户是否有自定义ModelForm校验方式而做铺垫
263     search_fields = []
264     list_display_links = []  # 为我们后面用户是否有自定义可跳转字段做铺垫
265     # 就像我们的函数里面有默认值的参数一样,如果有传参就使用我们的自定义传参,如果没有传参就使用我们默认的参数也就是这个字符串,
266     # 这里是为了给我们后面的代码做铺垫,我们的目的是在我们的数据展示页面里面默认就会把复选框和编辑还有删除按钮都加上,
267     # 在这里把空列表里面添加上一个默认的字符串,是为了我们后面往该列表里面添加默认固定数据也就是复选框和编辑删除按钮做铺垫
268     actions = []
269     list_filter = []  # 多级过滤
270 
271     # 静态内置方法
272     def __init__(self, model, site):
273         self.model = model  # 当我们生成一个实例化对象的时候需要把model这个参数传进来,
274         # 必须要传,它是位置参数,然后我们所传入的那个model就是我们在models.py里面定义的每一个表名也就是类名
275         self.site = site
276         self.namespace = '{}_{}'.format(self.model._meta.app_label, self.model._meta.model_name)
277         # self.app_model_name = (self.model._meta.app_label, self.model._meta.model_name)  # 这里写得跟上面一句是一样的效果,
278         # 这里调用的时候需要有两个%s,因为这里是一个元祖,而我们上面的namespace是一个字符串,不是一个元祖,所以只需要一个%s即可,调用的时候就这点区别
279     # 我们这里的namespace是因为会频繁使用到所以就把它作为一个内置静态属性写入到这里,其他地方如果要调用它就直接使用self.namespace即可
280     # .format的方法:'{}_{}'.format(a,b)
281 
282     # 默认actions批量删除
283     def patch_delete(self, queryset):
284         queryset.delete()
285         return None
286     patch_delete.desc = '批量删除'
287 
288     # 获取真正展示的actions
289     def get_actions(self):
290         temp = []
291         temp.extend(self.actions)  # [patch_init,patch_delete]
292         temp.append(ModelSubject.patch_delete)
293         return temp
294 
295     # 获取展示页面的url
296     def get_stand_url(self):
297         stand_url = reverse('%s_standlist' % self.namespace)
298         return stand_url
299 
300     # 获取编辑页面的url
301     def get_edit_url(self, obj):
302         edit_url = reverse('%s_edit' % self.namespace, args=(obj.pk,))
303         return edit_url
304 
305     # 获取删除页面的url
306     def get_dele_url(self, obj):
307         dele_url = reverse('%s_dele' % self.namespace, args=(obj.pk,))
308         return dele_url
309 
310     # 获取增加页面的url
311     def get_add_url(self):
312         add_url = reverse('%s_add' % self.namespace)
313         return add_url
314 
315     # 展示页面默认附带的编辑按钮
316     def edit(self, obj=None, is_header=False):
317         if is_header:
318             return '操作'
319         return mark_safe('<a href="%s">编辑</a>' % reverse('%s_edit' % self.namespace, args=(obj.pk,)))
320 
321     # 展示页面默认附带的删除按钮
322     def dele(self, obj=None, is_header=False):
323         if is_header:
324             return '删除'
325         return mark_safe("<a href='%s'>删除</a>" % reverse('%s_dele' % self.namespace, args=(obj.pk,)))
326 
327     # 展示页面附带的默认复选框
328     def checkbox(self, obj=None, is_header=False):
329         if is_header:
330             return mark_safe("<input id='action-toggle' type='checkbox'>")
331         return mark_safe("<input type='checkbox' value='%s' name='_selected_action'>" % obj.pk)
332 
333     # 展示页面默认显示按钮被存放的列表
334     def get_list_display(self):
335         new_li = []
336         new_li.extend(self.list_display)
337         if not self.list_display_links:
338             new_li.append(ModelSubject.edit)  # &&& 跟如下同样特征的代码块相呼应我们在这里使用类名去调用函数名,得到的是一个函数的方法,
339         # 函数如果有参数是需要我们传参数的;但是我们如果使用self去调用的话,self就是实例化出来的对象,
340         # 而我们的对象去调用函数方法的时候就不需要去传自己了也就是self,
341         new_li.append(ModelSubject.dele)
342         new_li.insert(0, ModelSubject.checkbox)  # 把checkbox放到第一个位置,使用insert插入到索引为0
343         # else:
344         #     new_li.extend(self.list_display)  # 我们这里的extend是把它后面的列表里面的数据都取出来放到我们自己的这个列表里面来
345         #     new_li.append(ModelSubject.dele)
346         #     new_li.insert(0, ModelSubject.checkbox)
347         """
348         # @@@ 跟下面特殊标识的代码块相呼应
349         new_li.append(self.edit)
350         new_li.append(self.dele)
351         new_li.insert(0,self.checkbox)
352         """
353         return new_li
354 
355     # 模糊查询
356     def get_search_condition(self):
357         from django.db.models import Q
358         search_condition = Q()
359         search_condition.connector = 'or'
360         if self.search_fields:  # 如果用户有自定义的查询字段,我们就走这个if下面的代码,
361             key_word = self.request.GET.get('q')  # 取出用户输入的input里面的值,
362             if key_word:
363                 for search_field in self.search_fields:  # 遍历用户自定义的查询字段列表,
364                     search_condition.children.append((search_field + "__contains", key_word))
365         return search_condition
366 
367     # 处理用户自定义的link超链接字段标签,然后让超链接携带url键值对参数方法,供get_body使用
368     def get_link_tag(self, obj, val):
369         params = self.request.GET
370         params = copy.deepcopy(params)
371         params._mutable = True
372         # from django.http import QueryDict
373         # qqx = QueryDict(mutable=True)
374         qqx = {}  # 这里只写一行,等效于上面的两行,当然了前提是我们要引入urlencoded
375         qqx['list_filter'] = params.urlencode()
376         whh = mark_safe("<a href='{}?{}'>{}</a>".format(self.get_edit_url(obj), urlencode(qqx), val))
377         return whh
378 
379     # 获取多级过滤的数据
380     def get_filter_condition(self):
381         from django.db.models import Q
382         fiter_condition = Q()
383         for field, val in self.request.GET.items():
384             if field in self.list_filter:
385                 fiter_condition.children.append((field, val))
386         return fiter_condition
387 
388     # 展示页面
389     def stand_li(self, request):
390         # print(self.model)
391         # 所以我们在这里可以获取到当前的url里面的表名,然后直接使用orm语句即可得到当前表格的所有信息
392         if request.method == 'POST':
393             # print('request.POST.get', request.POST.get('action'))
394             # print('request.POST.getlist', request.POST.getlist("_selected_action"))
395             # 打印出来的结果是request.POST.getlist ['on', 'on'] 这个on是从何而来的,我的input里面的所有value值都设定的是obj.pk,
396             # 这个obj.pk是有值的,
397             # 为什么这里打印出来的getlist是两个on?我们的input标签上面有name值,
398             # 然后我们使用request.POST.get后面的括号里面放的是name属性的值,然后它的返回值是我们的input标签里面的value值,
399             # 都说了是value值,怎么还能加上s呢?简直愚蠢,
400 
401             pk_list = request.POST.getlist("_selected_action")
402             queryset = self.model.objects.filter(pk__in=pk_list)
403             func_name = request.POST.get("action")
404             func = getattr(self, func_name)
405             ret = func(queryset)
406 
407         self.request = request
408 
409         # 关于search的模糊查询
410         search_condition = self.get_search_condition()
411 
412         # action
413         # a=self.model.objects.all().count()  # 这样就是可以获取我们的queryset集合的总数据长度,
414         # 然后就可以用它去传给我们的分页组件,用它也可以,直接用count就能获取数据长度,或者是用len也行,我之前都是用len获取的
415 
416         # filter多级过滤,
417         get_filter_condition = self.get_filter_condition()
418         queryset = self.model.objects.filter(search_condition).filter(get_filter_condition)
419         add_url = self.get_add_url()
420         sl = StandLi(self, request, queryset)  # 这里是把我们的StandLi这个类所需要的参数都传给它,然后通过StandLi实例化出来的一个对象
421         # 然后在这里实例化出来一个对象我在这里调用那个对象就能够使用那个类里面的封装的方法了
422 
423         return render(request, 'file/hello.html', locals())
424 
425     # ModelForm校验添加和编辑页面
426     def get_modelform_class(self):
427         from django.forms import widgets
428 
429         class AllModelForm(ModelForm):
430             class Meta:
431                 model = self.model
432                 fields = '__all__'
433         if not self.model_form_class:  # 这里的model_form_class在上面被定义了默认是None,
434             # 我们的分发下去的App里面自定义的file文件里面注册model类的时候实例化出来的对象,在注册的时候传过来的这个变量
435             return AllModelForm
436         else:
437             return self.model_form_class
438 
439     # ModelForm校验数据添加页面
440     def add_view(self, request):
441         FormClass = self.get_modelform_class()
442         if request.method == 'GET':
443             form = FormClass()
444 
445             from django.forms.boundfield import BoundField
446             from django.forms.models import ModelChoiceField
447 
448             for bound_field in form:
449                 if isinstance(bound_field.field, ModelChoiceField):
450                     bound_field.is_pop = True
451                     print(bound_field.field.queryset.model)
452 
453                     app_label = bound_field.field.queryset.model._meta.app_label
454                     model_name = bound_field.field.queryset.model._meta.model_name
455                     _url = "%s_%s_add" % (app_label, model_name)
456                     bound_field.url = reverse(_url)+"?pop_id=id_%s" % bound_field.name
457                 else:
458                     bound_field.is_pop = False
459                     bound_field.url = None
460 
461             return render(request, 'file/add.html', {'form': form})
462         else:
463             data_list = FormClass(data=request.POST)
464             if data_list.is_valid():
465                 ret = data_list.save()
466                 pop_id = request.GET.get('pop_id')
467                 if pop_id:
468                     res = {'pk': ret.pk, 'text': str(ret), 'pop_id': pop_id}
469                     import json
470                     return render(request, 'file/pop_demo.html', {'ret': json.dumps(res)})
471 
472                 return redirect(self.get_stand_url())
473             else:
474                 return render(request, 'file/add.html', {'form': data_list})
475 
476     # ModelForm校验数据编辑页面
477     def edit_view(self, request, id):
478         edit_list = self.model.objects.filter(pk=id).first()
479         FormClass = self.get_modelform_class()
480         if request.method == 'GET':
481             data_list = FormClass(instance=edit_list)
482             return render(request, 'file/edit.html', {'form': data_list})
483         else:
484             data_list = FormClass(data=request.POST, instance=edit_list)
485         if data_list.is_valid():
486             data_list.save()
487             return redirect(self.get_stand_url())
488         else:
489             return render(request, 'file/edit.html', {'form': data_list})
490 
491     # 数据删除页面
492     def dele_view(self, request, id):
493         del_obj = self.model.objects.filter(pk=id).first()
494         if request.method == 'GET':
495             stand_url = self.get_stand_url()
496             return render(request, 'file/dele.html', {'del_obj': del_obj, 'list_url': stand_url})
497         else:
498             del_obj.delete()
499 
500             return redirect(self.get_stand_url())
501 
502     # 获取url,此为第二次分发
503     def get_urls(self):
504         temp = []
505         temp.append(url(r'^$', self.stand_li, name='%s_standlist' % self.namespace))
506         temp.append(url(r'^(\d+)/dele/', self.dele_view, name='%s_dele' % self.namespace))
507         temp.append(url(r'^(\d+)/edit/', self.edit_view, name='%s_edit' % self.namespace))
508         temp.append(url(r'^add/', self.add_view, name='%s_add' % self.namespace))
509         return temp
510 
511     @property
512     def urls(self):
513         return self.get_urls()
514 
515 
516 class Stark(object):
517     """
518     我们这里面的功能是可以跟上面的类写到一起去的,但是我们为了功能解耦,所以就分开写了,这里的主要功能就是
519     生成registry的字典,把键值对生成,然后我们最终的结果是要得到一个实例化对象,供我们后面的程序调用,这里的类才是主要的,核心的代码块
520     而我们上面的那个ModelSubject是辅助我们这里的功能,它之所以分发出去是为了便于扩展其他的功能,我们的自定义样式,
521     还有很多的方法和参数,就像我们的admin里面的ModelAdmin一样,长达1400多行代码,单独把它分离出去便于功能的扩展
522     """
523 
524     def __init__(self):
525         self._registry = {}  # 这里是定义一个私有属性,就是为了避免被子类修改
526 
527     # 注册model表
528     def register(self, model, model_config=None):  # 我们是仿照着admin的源码写的组件,这里的model_config默认值是None,
529         # 我们在传参的时候,如果给它传值,那么就使用我们传入的值替换掉这个None
530         # 它的源码里面有这几个参数,我们也要按照顺序把这几个参数加进来
531         if not model_config:
532             model_config = ModelSubject  # 我们这里的model_config我们上面的类ModelSubject实例化出来的对象,
533             # 它是上面的类所实例化出来的对象,这一句写得明明白白的,这大白话再看不懂就真是白学了,
534         self._registry[model] = model_config(model, self)
535 
536     # 获取url,第一次分发
537     def get_urls(self):
538         li = []
539         for model, model_config in self._registry.items():  # 我们在这里所循环的model_config就是
540             # 我们往上数第四行所实例化出来的那个model_config,它是上面的ModelSubject这个类所实例化出来对象,
541             model_name = model._meta.model_name  # 这里的._meta.model_name是获取字符串格式的类名,
542             app_label = model._meta.app_label  # 这里的._meta.app_labe获取的是字符串格式的App名,都是为了跟url做匹配,
543             sli = url(r'%s%s/' % (app_label, model_name), (model_config.urls, None, None))  # 我们这里的model_config,
544             # 它后面的.urls是在调用一个私有方法,我们的私有方法就是使用.urls来调用,不用加上括号,
545             # 因为有@property这个装饰器在里面起到的作用,然后我们需要找到model_config这个实例对象是哪个类生成的,
546             # 然后找到该类所拥有的方法,从里面找到urls,届时,那个urls就是我们在这里调用的那个urls了,
547             # 所以关键的点就是我们的model_config,老师讲课的时候一再地强调过这个model_config从何而来,这个是关键,
548             li.append(sli)
549         return li
550 
551     # 我们最终的数据结构就是这样的,嵌套多层
552     # [
553     # url(
554     # r'',(
555     # [
556     # (url(r'',views.add)),
557     # (url(r'',views.edit)),
558     # ],
559     # none,none)
560     # )
561     # ]
562     @property
563     def urls(self):
564         return self.get_urls(), None, None
565 
566 
567 site = Stark()
所有的代码

我们把多对多字段也展示出来了,还有choice字段类型的数据调整展示,

 

 

这里是终级版本:

  1 from django.conf.urls import url
  2 from django.shortcuts import render, redirect, reverse
  3 from django.utils.safestring import mark_safe
  4 from django.forms import ModelForm  # 这个ModelForm里面封装了很强大的功能,要把源码过一遍
  5 from django.db.models import ForeignKey, ManyToManyField
  6 from django.utils.http import urlencode
  7 import copy
  8 from types import FunctionType
  9 
 10 
 11 # 这个类主要帮我们处理多级过滤的a标签,我们之前是把多级过滤的a标签给写到get_filter_link_tags这个函数里面,
 12 # 后来为了实现功能解耦,避免单个函数代码量过大,就把这个功能封装成了一个类,以便于阅读,以及功能扩展
 13 class LinkTagsGen(object):
 14     def __init__(self, data, filter_field, request):
 15         self.data = data
 16         self.filter_field = filter_field
 17         self.request = request
 18 
 19     def __iter__(self):
 20         """
 21         所有的可迭代对象内部都是实现了__iter__方法,我们把数据写到这里就是实现的数据的可迭代
 22         :return: yield 多级过滤的A标签
 23         """
 24         current_id = self.request.GET.get(self.filter_field.filter_name, 0)
 25         params = copy.deepcopy(self.request.GET)
 26         params._mutable = True
 27         if params.get(self.filter_field.filter_name):
 28             del params[self.filter_field.filter_name]
 29             _url = "%s?%s" % (self.request.path_info, params.urlencode())
 30             yield mark_safe("<a href='%s'>All</a>" % _url)
 31         else:
 32             yield mark_safe("<a href='#' class='active'>All</a>")
 33 
 34         for item in self.data:  # self.data是一个个的queryset集合以及元祖,((1,'已出版'),(2,'未出版')),
 35             # <QuerySet[<Publish:人民出版社>,<Publish:北京出版社>]>
 36             # print(self.data)
 37             pk, text = None, None
 38             if self.filter_field.filter_obj.choices:
 39                 pk, text = str(item[0]), item[1]
 40             elif isinstance(self.filter_field.filter_obj, ForeignKey) or isinstance(self.filter_field.filter_obj, ManyToManyField):
 41                 pk, text = str(item.pk), item
 42             else:
 43                 pk, text = item[1], item[1]
 44 
 45             params[self.filter_field.filter_name] = pk
 46             _url = "%s?%s" % (self.request.path_info, params.urlencode())
 47             if current_id == pk:
 48                 link_tag = "<a class='active' href='%s'>%s</a>" %(_url, text)
 49             else:
 50                 link_tag = "<a href='%s'>%s</a>" % (_url, text)
 51             yield mark_safe(link_tag)
 52 
 53 
 54 # 为每一个过滤的字段封装成整体类
 55 class FilterField(object):
 56     def __init__(self, filter_name, filter_obj, config):
 57         self.filter_name = filter_name
 58         self.filter_obj = filter_obj
 59         self.config = config
 60 
 61     def get_data(self):
 62         if isinstance(self.filter_obj, ForeignKey) or isinstance(self.filter_obj, ManyToManyField):
 63             return self.filter_obj.rel.to.objects.all()
 64         elif self.filter_obj.choices:
 65             return self.filter_obj.choices
 66         else:  # 这里是取到我们的当前表格的所有数据,把pk值和字段名显示出来
 67             return self.config.model.objects.values_list('pk', self.filter_name)
 68 
 69 
 70 # 服务于ModelSubject下面的stand_li,我们把stand_li里面的很多方法给封装到这个类里面实现功能解耦,
 71 # 主要是为了减轻我们的StandLi里面的代码量
 72 class StandLi(object):
 73     def __init__(self, config, request, queryset):
 74         """
 75         :param config: 它就是我们下面的类ModelSubject所传过来的它的self实例对象,
 76         我们在这里要使用那些方法和变量就需要把它的这个实例对象拿过来,否则如下搬过来的代码块都会失效
 77         :param request: 我们这个类是在下面的ModelSubject里面调用然后在那里实例化出来的对象,所以这个request是它传过来的
 78         :param queryset: 同上,这个queryset也是ModelSubject所传过来的参数,供下面的代码调用
 79         """
 80         self.config = config
 81         self.request = request
 82         self.queryset = queryset
 83 
 84         # 生成分页器
 85         path = self.request.path_info
 86         params = self.request.GET
 87         page_num = request.GET.get('page', 1)  # 如果没有找到page,就返回1,也就是第一页
 88         from file.utensil.page import MyPage
 89         count = queryset.count()
 90         page = MyPage(page_num, count, path, params)
 91         self.pagination = page
 92         data_list = self.queryset[page.start:page.end]
 93         self.data_list = data_list
 94         # page_html = page.page_html()  # 这里我们可以把page_html方法直接在前端HTML模板里面引用
 95 
 96         # actions 实现批量操作功能
 97         self.actions = self.config.get_actions()  # [patch_init, patch_delete]
 98         # print('actions', self.actions)
 99 
100         # filter 实现筛选功能
101         self.list_filter = self.config.list_filter
102 
103     # 实现多级过滤的类里面封装的一个函数
104     def get_filter_link_tags(self):
105         for filter_name in self.list_filter:
106             filter_obj = self.config.model._meta.get_field(filter_name)
107             filter_field = FilterField(filter_name, filter_obj, self.config)
108             # 这里是按照位置传参,不能随便写的,这边传过去的是什么顺序,那边接收的就是什么顺序
109 
110 
111             # print("filter_field", filter_field.get_data())
112             """
113             filter_field <QuerySet [<Publish: 长春出版社>, <Publish: 香港中文大学出版社>, <Publish: 中信出版社>]>
114             filter_field ((0, '已出版'), (1, '未出版'))
115             filter_field <QuerySet [<Writer: White>, <Writer: Black>, <Writer: Miss Lin>]>
116             """
117             val = LinkTagsGen(filter_field.get_data(), filter_field, self.request)
118             yield val
119 
120     # 这里是使用两个yield去实现的多级过滤
121     #  展示筛选条件
122     # def get_filter_link_tags(self):  # list_filter=['state','publish','authors']
123     #
124     #     for filter_name in self.list_filter:
125     #         current_id = int(self.request.GET.get(filter_name, 0))  # 这里加上int之后我们点击超链接标签的时候就会有字体颜色的变化
126     #         # current_id = self.request.GET.get(filter_name, 0)  # 这里没有加上int点击超链接的时候不会有变化
127     #         # print('current_id', current_id)
128     #         filter_obj = self.config.model._meta.get_field(filter_name)
129     #         # print('filter_obj', filter_obj)
130     #         filter_field = FilterField(filter_name, filter_obj)
131     #         # print("filter_field",filter_field)
132     #         def inner(filter_field, current_id):
133     #             # print(filter_field.get_data())
134     #
135     #             # 这里得出的结果是我们的多对多字段和一对多字段的所有关联数据
136     #             # <QuerySet [<Publish: 长春出版社>, <Publish: 香港中文大学出版社>, <Publish: 中信出版社>]>
137     #             # <QuerySet [<Writer: White>, <Writer: Black>, <Writer: Miss Lin>]>
138     #
139     #             for obj in filter_field.get_data():
140     #                 if isinstance(filter_field.filter_obj, ForeignKey) or \
141     #                         isinstance(filter_field.filter_obj, ManyToManyField):
142     #                     # link_tags=[]
143     #                     params = copy.deepcopy(self.request.GET)
144     #                     params._mutable = True
145     #                     params[filter_field.filter_name] = obj.pk
146     #
147     #                     if current_id == obj.pk:
148     #                         yield mark_safe("<a class='active' href='?%s'>%s</a>" % (params.urlencode(), obj))
149     #                     else:
150     #                         yield mark_safe("<a href='?%s'>%s</a>" % (params.urlencode(), obj))
151     #                 elif filter_field.filter_obj.choices:
152     #                     params = copy.deepcopy(self.request.GET)
153     #                     params._mutable = True
154     #                     params[filter_field.filter_name] = obj[0]
155     #                     if current_id == obj[0]:
156     #                         yield mark_safe("<a class='active' href='?%s'>%s</a>" % (params.urlencode(), obj[1]))
157     #                     else:
158     #                         yield mark_safe("<a href='?%s'>%s</a>" % (params.urlencode(), obj[1]))
159     #                 else:
160     #                     # params = copy.deepcopy(self.request.GET)
161     #                     # params._mutable = True
162     #                     # params[filter_field.filter_name] = obj
163     #                     # yield mark_safe("<a href='?%s'>%s</a>" % (params.urlencode(), obj))
164     #                     ...
165     #         yield inner(filter_field, current_id)
166 
167     # 把自定义的action内容放到一个列表里面,以键值对的数据类型
168     def handle_actions(self):
169         temp = []
170         for action_func in self.actions:
171             temp.append({'name': action_func.__name__, "desc": action_func.desc})
172         return temp
173 
174     # 获取表头数据
175     def get_header(self):
176         # 生成表头数据
177         # ['id','title','price',edit]
178         header_list = []
179         for field in self.config.get_list_display():
180             if callable(field):
181                 ret = field(self, is_header=True)
182                 header_list.append(ret)
183             else:
184                 if field == '__str__':  # 这里是判断我们的list_display列表里面是否有我们自定义传入的值,如果没有的话,
185                     # 就是直接等于我们在静态属性里面设定的那个默认的'__str__',也就是说如果这一步判断成立,
186                     # 那么就证明我们的用户没有自定义展示的字段,我们就需要自己给浏览器一个字段去展示,那个字段就是我们这里所设定的那个大写的表名
187                     header_list.append(self.config.model._meta.model_name.upper())  # 我们这里的操作是
188                 else:
189                     obj = self.config.model._meta.get_field(field)  # 我们的list_display里面是一个个的字符串,
190                     # 把字符串放到get_field里面来可以把我们的字符串转换成类对象,
191                     header_list.append(obj.verbose_name)  # 我们这里的verbose_name在model里面是内置方法,
192                     # 我们的verbose_name本质上是对我们的字段进行描述的,比如我们的book里面的title可以在字段约束里面设
193                     # verbose_name='书名',类似于这样,把它变成中文,然后我们在前端HTML模板里面渲染的时候就可以渲染出来中文了,
194                     # 而不是使用默认的英文,当然了我们如果不设置verbose_name的值那么就使用默认的title作为字段名传到浏览器
195 
196         return header_list
197 
198     # 获取表单数据
199     def get_body(self):
200         # 生成表单数据列表(我们把这个函数挪到上面这个类里面来之后,把循环遍历的数据改动了,之前是把当前表格的数据取出来之后就直接遍历它,
201         # 后来我们有了搜索功能,那么就不能遍历表格里面的所有数据了,需要把我们过滤查询出来的数据给遍历出来
202         # ret = self.config.model.objects.all().count()
203         # print('self.list_display', self.list_display)
204         data_temp = []
205         for obj in self.data_list:  # 我们遍历这个queryset集合得到的obj是它的每一个对象
206             # print(self.data_list)
207             """
208             <QuerySet [<Writer: White>, <Writer: Black>, <Writer: Miss Lin>]>
209             """
210             temp = []
211             for field in self.config.get_list_display():  # 我们遍历list_display得到每一个字符串
212 
213                 if callable(field):
214                     # res = field(obj)  # @@@更上面的特殊标识的代码块相呼应
215                     res = field(self.config, obj)  # &&& 这里跟上面同样标识的代码块相呼应的,上面我们使用的类名去调用函数名,
216                     # 得到的是一个函数,这里就是给所调用的函数传参的,self,和obj都是我们传给函数的参数;
217                     # 如果我们使用self这个对象去调用函数名的方法的话,就不需要再传一个self作为参数进去了,我们两种方法都可以,需要对应上即可
218                 # if isinstance(field, FunctionType):
219                 #     res = field(self.config, obj=obj)
220                 else:
221                     try:
222                         field_obj = self.config.model._meta.get_field(field)
223                         if isinstance(field_obj, ManyToManyField):  # 这里是判断该字段是否是多对多字段,
224                             ret = getattr(obj, field).all()  # 使用getattr方法去判断该对象是否具有,field属性方法,
225                             # getattr里面需要两个参数(类对象,字符串属性方法).另外一点是,使用getattr(obj, field).all()就相当于
226                             # 是obj.fields.all取出该字段的所有数据
227                             list = []
228                             for j in ret:
229                                 list.append(str(j))
230                             res = ' / '.join(list)
231                         else:
232                             res = getattr(obj, field)
233                             if field in self.config.list_display_links:  # 我们这里是判断表单里面的字段是否在links表格里面
234                                 # 被自定义作为可跳转标签,
235                                 # 如果答案是肯定的,那么我们就需要把a标签给拼出来
236                                 res = self.config.get_link_tag(obj, res)
237                     except Exception as e:
238                         res = str(obj)
239                 temp.append(res)
240             data_temp.append(temp)
241         # print('data_list', data_list)
242 
243         """
244         我们最终得到的数据类型是如下格式:列表套着列表
245         [
246         使用orm语句得到的每一个类对象,有几个表格就有几个对象
247         ]
248         list_display=['id','title',]
249         [
250         [1,'python',<a>编辑</a>],
251         [2,'java',<a>编辑</a>],
252         ]
253         """
254         return data_temp
255 
256 
257 class ModelSubject(object):
258     """
259     我们在这里模拟admin源码里面的ModelAdmin,
260     """
261     list_display = ["__str__", ]  # 我们在这里给空列表里面加上"__str__",它就相当于是一个默认值,
262     model_form_class = None  # 为下面我们判断用户是否有自定义ModelForm校验方式而做铺垫
263     search_fields = []
264     list_display_links = []  # 为我们后面用户是否有自定义可跳转字段做铺垫
265     # 就像我们的函数里面有默认值的参数一样,如果有传参就使用我们的自定义传参,如果没有传参就使用我们默认的参数也就是这个字符串,
266     # 这里是为了给我们后面的代码做铺垫,我们的目的是在我们的数据展示页面里面默认就会把复选框和编辑还有删除按钮都加上,
267     # 在这里把空列表里面添加上一个默认的字符串,是为了我们后面往该列表里面添加默认固定数据也就是复选框和编辑删除按钮做铺垫
268     actions = []
269     list_filter = []  # 多级过滤
270 
271     # 静态内置方法
272     def __init__(self, model, site):
273         self.model = model  # 当我们生成一个实例化对象的时候需要把model这个参数传进来,
274         # 必须要传,它是位置参数,然后我们所传入的那个model就是我们在models.py里面定义的每一个表名也就是类名
275         self.site = site
276         self.namespace = '{}_{}'.format(self.model._meta.app_label, self.model._meta.model_name)
277         # self.app_model_name = (self.model._meta.app_label, self.model._meta.model_name)  # 这里写得跟上面一句是一样的效果,
278         # 这里调用的时候需要有两个%s,因为这里是一个元祖,而我们上面的namespace是一个字符串,不是一个元祖,所以只需要一个%s即可,调用的时候就这点区别
279     # 我们这里的namespace是因为会频繁使用到所以就把它作为一个内置静态属性写入到这里,其他地方如果要调用它就直接使用self.namespace即可
280     # .format的方法:'{}_{}'.format(a,b)
281 
282     # 默认actions批量删除
283     def patch_delete(self, queryset):
284         queryset.delete()
285         return None
286     patch_delete.desc = '批量删除'
287 
288     # 获取真正展示的actions
289     def get_actions(self):
290         temp = []
291         temp.extend(self.actions)  # [patch_init,patch_delete]
292         temp.append(ModelSubject.patch_delete)
293         return temp
294 
295     # 获取展示页面的url
296     def get_stand_url(self):
297         stand_url = reverse('%s_standlist' % self.namespace)
298         return stand_url
299 
300     # 获取编辑页面的url
301     def get_edit_url(self, obj):
302         edit_url = reverse('%s_edit' % self.namespace, args=(obj.pk,))
303         return edit_url
304 
305     # 获取删除页面的url
306     def get_dele_url(self, obj):
307         dele_url = reverse('%s_dele' % self.namespace, args=(obj.pk,))
308         return dele_url
309 
310     # 获取增加页面的url
311     def get_add_url(self):
312         add_url = reverse('%s_add' % self.namespace)
313         return add_url
314 
315     # 展示页面默认附带的编辑按钮
316     def edit(self, obj=None, is_header=False):
317         if is_header:
318             return '操作'
319         return mark_safe('<a href="%s">编辑</a>' % reverse('%s_edit' % self.namespace, args=(obj.pk,)))
320 
321     # 展示页面默认附带的删除按钮
322     def dele(self, obj=None, is_header=False):
323         if is_header:
324             return '删除'
325         return mark_safe("<a href='%s'>删除</a>" % reverse('%s_dele' % self.namespace, args=(obj.pk,)))
326 
327     # 展示页面附带的默认复选框
328     def checkbox(self, obj=None, is_header=False):
329         if is_header:
330             return mark_safe("<input id='action-toggle' type='checkbox'>")
331         return mark_safe("<input type='checkbox' value='%s' name='_selected_action'>" % obj.pk)
332 
333     # 展示页面默认显示按钮被存放的列表
334     def get_list_display(self):
335         new_li = []
336         new_li.extend(self.list_display)
337         if not self.list_display_links:
338             new_li.append(ModelSubject.edit)  # &&& 跟如下同样特征的代码块相呼应我们在这里使用类名去调用函数名,得到的是一个函数的方法,
339         # 函数如果有参数是需要我们传参数的;但是我们如果使用self去调用的话,self就是实例化出来的对象,
340         # 而我们的对象去调用函数方法的时候就不需要去传自己了也就是self,
341         new_li.append(ModelSubject.dele)
342         new_li.insert(0, ModelSubject.checkbox)  # 把checkbox放到第一个位置,使用insert插入到索引为0
343         # else:
344         #     new_li.extend(self.list_display)  # 我们这里的extend是把它后面的列表里面的数据都取出来放到我们自己的这个列表里面来
345         #     new_li.append(ModelSubject.dele)
346         #     new_li.insert(0, ModelSubject.checkbox)
347         """
348         # @@@ 跟下面特殊标识的代码块相呼应
349         new_li.append(self.edit)
350         new_li.append(self.dele)
351         new_li.insert(0,self.checkbox)
352         """
353         return new_li
354 
355     # 模糊查询
356     def get_search_condition(self):
357         from django.db.models import Q
358         search_condition = Q()
359         search_condition.connector = 'or'
360         if self.search_fields:  # 如果用户有自定义的查询字段,我们就走这个if下面的代码,
361             key_word = self.request.GET.get('q')  # 取出用户输入的input里面的值,
362             if key_word:
363                 for search_field in self.search_fields:  # 遍历用户自定义的查询字段列表,
364                     search_condition.children.append((search_field + "__contains", key_word))
365         return search_condition
366 
367     # 处理用户自定义的link超链接字段标签,然后让超链接携带url键值对参数方法,供get_body使用
368     def get_link_tag(self, obj, val):
369         params = self.request.GET
370         params = copy.deepcopy(params)
371         params._mutable = True
372         # from django.http import QueryDict
373         # qqx = QueryDict(mutable=True)
374         qqx = {}  # 这里只写一行,等效于上面的两行,当然了前提是我们要引入urlencoded
375         qqx['list_filter'] = params.urlencode()
376         whh = mark_safe("<a href='{}?{}'>{}</a>".format(self.get_edit_url(obj), urlencode(qqx), val))
377         return whh
378 
379     # 获取多级过滤的数据
380     def get_filter_condition(self):
381         from django.db.models import Q
382         fiter_condition = Q()
383         for field, val in self.request.GET.items():
384             if field in self.list_filter:
385                 fiter_condition.children.append((field, val))
386         return fiter_condition
387 
388     # 展示页面
389     def stand_li(self, request):
390         # print(self.model)
391         # 所以我们在这里可以获取到当前的url里面的表名,然后直接使用orm语句即可得到当前表格的所有信息
392         if request.method == 'POST':
393             # print('request.POST.get', request.POST.get('action'))
394             # print('request.POST.getlist', request.POST.getlist("_selected_action"))
395             # 打印出来的结果是request.POST.getlist ['on', 'on'] 这个on是从何而来的,我的input里面的所有value值都设定的是obj.pk,
396             # 这个obj.pk是有值的,
397             # 为什么这里打印出来的getlist是两个on?我们的input标签上面有name值,
398             # 然后我们使用request.POST.get后面的括号里面放的是name属性的值,然后它的返回值是我们的input标签里面的value值,
399             # 都说了是value值,怎么还能加上s呢?简直愚蠢,
400 
401             pk_list = request.POST.getlist("_selected_action")
402             queryset = self.model.objects.filter(pk__in=pk_list)
403             func_name = request.POST.get("action")
404             func = getattr(self, func_name)
405             ret = func(queryset)
406 
407         self.request = request
408 
409         # 关于search的模糊查询
410         search_condition = self.get_search_condition()
411 
412         # action
413         # a=self.model.objects.all().count()  # 这样就是可以获取我们的queryset集合的总数据长度,
414         # 然后就可以用它去传给我们的分页组件,用它也可以,直接用count就能获取数据长度,或者是用len也行,我之前都是用len获取的
415 
416         # filter多级过滤,
417         get_filter_condition = self.get_filter_condition()
418         queryset = self.model.objects.filter(search_condition).filter(get_filter_condition)
419         add_url = self.get_add_url()
420         sl = StandLi(self, request, queryset)  # 这里是把我们的StandLi这个类所需要的参数都传给它,然后通过StandLi实例化出来的一个对象
421         # 然后在这里实例化出来一个对象我在这里调用那个对象就能够使用那个类里面的封装的方法了
422 
423         return render(request, 'file/hello.html', locals())
424 
425     # ModelForm校验添加和编辑页面
426     def get_modelform_class(self):
427         from django.forms import widgets
428 
429         class AllModelForm(ModelForm):
430             class Meta:
431                 model = self.model
432                 fields = '__all__'
433         if not self.model_form_class:  # 这里的model_form_class在上面被定义了默认是None,
434             # 我们的分发下去的App里面自定义的file文件里面注册model类的时候实例化出来的对象,在注册的时候传过来的这个变量
435             return AllModelForm
436         else:
437             return self.model_form_class
438 
439     # ModelForm校验数据添加页面
440     def add_view(self, request):
441         FormClass = self.get_modelform_class()
442         if request.method == 'GET':
443             form = FormClass()
444 
445             from django.forms.boundfield import BoundField  # 这个BoundField我们点进去,源码里面有一些属性用法,
446             return render(request, 'file/add.html', {'form': form})
447         else:
448             data_list = FormClass(data=request.POST)
449             if data_list.is_valid():
450                 ret = data_list.save()
451                 pop_id = request.GET.get('pop_id')
452                 if pop_id:
453                     res = {'pk': ret.pk, 'text': str(ret), 'pop_id': pop_id}
454                     import json
455                     return render(request, 'file/pop_demo.html', {'ret': json.dumps(res)})
456 
457                 return redirect(self.get_stand_url())
458             else:
459                 return render(request, 'file/add.html', {'form': data_list})
460 
461     # ModelForm校验数据编辑页面
462     def edit_view(self, request, id):
463         edit_list = self.model.objects.filter(pk=id).first()
464         FormClass = self.get_modelform_class()
465         if request.method == 'GET':
466             data_list = FormClass(instance=edit_list)
467             return render(request, 'file/edit.html', {'form': data_list})
468         else:
469             data_list = FormClass(data=request.POST, instance=edit_list)
470             if data_list.is_valid():
471                 data_list.save()
472                 params = request.GET.get('list_filter')
473                 url = "%s?%s" % (self.get_stand_url(), params)
474                 return redirect(url)
475             else:
476                 return render(request, 'file/edit.html', {'form': data_list})
477 
478     # 数据删除页面
479     def dele_view(self, request, id):
480         del_obj = self.model.objects.filter(pk=id).first()
481         if request.method == 'GET':
482             stand_url = self.get_stand_url()
483             return render(request, 'file/dele.html', {'del_obj': del_obj, 'list_url': stand_url})
484         else:
485             del_obj.delete()
486 
487             return redirect(self.get_stand_url())
488 
489     # 获取url,此为第二次分发
490     def get_urls(self):
491         temp = []
492         temp.append(url(r'^$', self.stand_li, name='%s_standlist' % self.namespace))
493         temp.append(url(r'^(\d+)/dele/', self.dele_view, name='%s_dele' % self.namespace))
494         temp.append(url(r'^(\d+)/edit/', self.edit_view, name='%s_edit' % self.namespace))
495         temp.append(url(r'^add/', self.add_view, name='%s_add' % self.namespace))
496         return temp
497 
498     @property
499     def urls(self):
500         return self.get_urls()
501 
502 
503 class Stark(object):
504     """
505     我们这里面的功能是可以跟上面的类写到一起去的,但是我们为了功能解耦,所以就分开写了,这里的主要功能就是
506     生成registry的字典,把键值对生成,然后我们最终的结果是要得到一个实例化对象,供我们后面的程序调用,这里的类才是主要的,核心的代码块
507     而我们上面的那个ModelSubject是辅助我们这里的功能,它之所以分发出去是为了便于扩展其他的功能,我们的自定义样式,
508     还有很多的方法和参数,就像我们的admin里面的ModelAdmin一样,长达1400多行代码,单独把它分离出去便于功能的扩展
509     """
510 
511     def __init__(self):
512         self._registry = {}  # 这里是定义一个私有属性,就是为了避免被子类修改
513 
514     # 注册model表
515     def register(self, model, model_config=None):  # 我们是仿照着admin的源码写的组件,这里的model_config默认值是None,
516         # 我们在传参的时候,如果给它传值,那么就使用我们传入的值替换掉这个None
517         # 它的源码里面有这几个参数,我们也要按照顺序把这几个参数加进来
518         if not model_config:
519             model_config = ModelSubject  # 我们这里的model_config我们上面的类ModelSubject实例化出来的对象,
520             # 它是上面的类所实例化出来的对象,这一句写得明明白白的,这大白话再看不懂就真是白学了,
521         self._registry[model] = model_config(model, self)
522 
523     # 获取url,第一次分发
524     def get_urls(self):
525         li = []
526         for model, model_config in self._registry.items():  # 我们在这里所循环的model_config就是
527             # 我们往上数第四行所实例化出来的那个model_config,它是上面的ModelSubject这个类所实例化出来对象,
528             model_name = model._meta.model_name  # 这里的._meta.model_name是获取字符串格式的类名,
529             app_label = model._meta.app_label  # 这里的._meta.app_labe获取的是字符串格式的App名,都是为了跟url做匹配,
530             sli = url(r'%s%s/' % (app_label, model_name), (model_config.urls, None, None))  # 我们这里的model_config,
531             # 它后面的.urls是在调用一个私有方法,我们的私有方法就是使用.urls来调用,不用加上括号,
532             # 因为有@property这个装饰器在里面起到的作用,然后我们需要找到model_config这个实例对象是哪个类生成的,
533             # 然后找到该类所拥有的方法,从里面找到urls,届时,那个urls就是我们在这里调用的那个urls了,
534             # 所以关键的点就是我们的model_config,老师讲课的时候一再地强调过这个model_config从何而来,这个是关键,
535             li.append(sli)
536         return li
537 
538     # 我们最终的数据结构就是这样的,嵌套多层
539     # [
540     # url(
541     # r'',(
542     # [
543     # (url(r'',views.add)),
544     # (url(r'',views.edit)),
545     # ],
546     # none,none)
547     # )
548     # ]
549     @property
550     def urls(self):
551         return self.get_urls(), None, None
552 
553 
554 site = Stark()
核心源码组件

 

自定义标签:

 1 from django import template
 2 from django.urls import reverse
 3 
 4 register = template.Library()
 5 from django.forms.models import ModelChoiceField
 6 
 7 
 8 @register.inclusion_tag("file/base_form.html")
 9 def get_simple_tag(form):
10     for bound_field in form:
11         if isinstance(bound_field.field, ModelChoiceField):
12             bound_field.is_pop = True
13             # print(bound_field.field.queryset.model)
14 
15             app_label = bound_field.field.queryset.model._meta.app_label
16             model_name = bound_field.field.queryset.model._meta.model_name
17             _url = "%s_%s_add" % (app_label, model_name)
18             bound_field.url = reverse(_url) + "?pop_id=id_%s" % bound_field.name
19         else:
20             bound_field.is_pop = False
21             bound_field.url = None
22     return {"form": form}
simple_tag(自定义标签)

与之匹配的HTML模板:

 1 <div class="container">
 2     <div class="row">
 3         <div class="col-md-8">
 4 
 5             <form action="" method="post" novalidate>
 6                 {% csrf_token %}
 7                 {% for i in form %}
 8                     <div class="form-group put_in">
 9                         <label for="">{{ i.label }}</label>
10                         <div class="input_style">{{ i }}
11                             <span class="error">{{ i.errors.0 }}</span>
12                         </div>
13                         {% if i.is_pop %}
14                             <a href="" onclick="pop('{{ i.url }}')" class="pop_btn"><span
15                                     class="pull-right sign">+</span></a>
16                         {% endif %}
17                     </div>
18                 {% endfor %}
19 
20                 <div class="form-group">
21                     <p><input type="submit" class="btn btn-primary"></p>
22                 </div>
23 
24             </form>
25         </div>
26     </div>
27 </div>
28 
29 <script src="/static/js/base.js"></script>
自定义标签页面

 

打开窗口页面:

 1 {% load my_simple %}
 2 <!DOCTYPE html>
 3 <html lang="en">
 4 <head>
 5     <meta charset="UTF-8">
 6     <meta http-equiv="X-UA-Compatible" content="IE=edge">
 7     <meta name="viewport" content="width=device-width, initial-scale=1">
 8     <link rel="stylesheet" href="https://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.min.css">
 9     <link rel="stylesheet" href="/static/css/base.input.css">
10     <script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.js"></script>
11     <script src="https://cdn.bootcss.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>
12     <title>Title</title>
13 </head>
14 <body>
15 <h3>添加数据</h3>
16 
17 {#{% include 'file/base_form.html' %}#}
18 {% get_simple_tag form %}
19 
20 <script src="/static/js/add.js"></script>
21 </body>
22 </html>
打开窗口页面

 

 

关闭窗口页面:

 1 <!DOCTYPE html>
 2 <html lang="en">
 3 <head>
 4     <meta charset="UTF-8">
 5     <meta http-equiv="X-UA-Compatible" content="IE=edge">
 6     <meta name="viewport" content="width=device-width, initial-scale=1">
 7     <link rel="stylesheet" href="https://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.min.css">
 8     <script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.js"></script>
 9     <title>Title</title>
10 </head>
11 <body>
12 
13 {#<script src="/static/js/po.js"></script>#}
14 {#    这里文件引入失败,为什么?这里是有一个参数需要我们的后端传入的,这个参数是在前端模板里面要进行渲染的,不能拿到静态文件里面去 #}
15 <script>
16     opener.hi('{{ ret|safe }}');
17     window.close();
18 </script>
19 
20 </body>
21 </html>
关闭窗口模板

 

posted @ 2018-03-21 20:49  dream-子皿  阅读(82)  评论(0)    收藏  举报