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>
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>
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>
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})
静态文件配置:
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 }
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}
与之匹配的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>

浙公网安备 33010602011771号