主要内容:
- 1. 动态生成增删改查路由
- 2. 查 -- changelist_view
1. 动态生成增删改查路由:
#在StarkConfig类中 def wrapper(self, func): ''' 一个装饰器 ''' @functools.wraps(func) def inner(request,*args, **kwargs): #每次请求进来都获取一份 self.request = request return func(request,*args, **kwargs) return inner def get_urls(self): info = self.model_class._meta.app_label, self.model_class._meta.model_name urlpatterns = [ # 使用装饰器的另一种写法,同时使用命名路由 url(r'list/$', self.wrapper(self.changelist_view),name='%s_%s_changelist' % info), url(r'add/$', self.wrapper(self.add_view), name='%s_%s_add' % info), url(r'change/(?P<pk>d+)', self.wrapper(self.change_view),name='%s_%s_change' % info), url(r'del/(?P<pk>d+)', self.wrapper(self.delete_view),name='%s_%s_delete' % info), ] extra = self.extra_url() if extra: urlpatterns.extend(extra) return urlpatterns def extra_url(self): ''' 为单独的某张表添加额外的url ''' pass @property def urls(self): #再上一级的路由分发是StarkConfig对象 或者 它的派生类调用改静态方法 return self.get_urls()
2. 查 -- changelist_view
#List的视图函数函数 def changelist_view(self,request): ''' 所有url的查看列表页面 ''' if request.method == 'POST': action_name = request.POST.get('action') action_dict = self.get_action_dict() if action_name not in action_dict: return HttpResponse('非法请求') response = getattr(self,action_name)(request) if response: return response ## 处理搜索 ## search_list, q, con = self.get_search_condition(request) # 通过model_class(app01.models.UserInfo) #filter 中可以 加以逗号分隔的字段 ,也可以放 **{} , 或者放q对象 # ##### 处理分页 ##### from stark.utils.pagination import Pagination total_count = self.model_class.objects.filter(con).count() query_params = request.GET.copy() query_params._mutable = True page = Pagination(request.GET.get('page'), total_count, request.path_info, query_params, per_page=3) queryset = self.model_class.objects.filter(con).order_by(*self.get_order_by())[page.start:page.end] cl = ChangeList(self, queryset, q, search_list, page) context = { 'cl': cl } return render(request, 'stark/changelist.html', context)C
ChangeList,封装列表页面需要的所有数据
class ChangeList(object): ''' 封装列表页面需要的所有数据(功能) ''' def __init__(self,config,queryset,q,search_list,page): self.q = q self.search_list = search_list self.page = page self.config = config self.action_list = [{'name': func.__name__, 'text': func.text} for func in config.get_action_list()] self.add_btn = config.get_add_btn() self.queryset = queryset self.list_display = config.get_list_display()
2.1 定制表格显示列 -- (使用)
- 视图中代码片段
#代码片段 cl = ChangeList(self, queryset, q, search_list, page) context = { 'cl': cl } #在ChangeList 类中 self.list_display = config.get_list_display() #在StarkConfig 类中 def get_list_display(self): ''' 相比于直接引用self.list_display 可以自定制显示内容 ''' return self.list_displaytemplatetags -- stark.py
- templatetags -- stark.py
from django.template import Library from types import FunctionType register = Library() def header_list(cl): """ 表头 :param cl: :return: """ if cl.list_display: for name_or_func in cl.list_display: if isinstance(name_or_func, FunctionType): verbose_name = name_or_func(cl.config, header=True) else: verbose_name = cl.config.model_class._meta.get_field(name_or_func).verbose_name yield verbose_name else: yield cl.config.model_class._meta.model_name def body_list(cl): """ 表格内容 :param cl: :return: """ for row in cl.queryset: row_list = [] if not cl.list_display: row_list.append(row) yield row_list continue for name_or_func in cl.list_display: if isinstance(name_or_func, FunctionType): val = name_or_func(cl.config, row=row) else: val = getattr(row, name_or_func) row_list.append(val) yield row_list @register.inclusion_tag('stark/table.html') def table(cl): return {'header_list':header_list(cl),'body_list':body_list(cl)}
- 返回的html代码
<table class="table table-bordered"> <thead> <tr> {% for item in header_list %} <th>{{ item }}</th> {% endfor %} </tr> </thead> <tbody> {% for row_list in body_list %} <tr> {% for col in row_list %} <td>{{ col }}</td> {% endfor %} </tr> {% endfor %} </tbody> </table>
2.2 List 页面中的功能 - 提交的action 功能 (批量初始化,批量删除等)
- ListView中通过 action_dict = self.get_action_dict() 获取到action_dic
# action_list中存放的是执行action行为的方法名 action_list = [multi_init, StarkConfig.multi_delete] def get_action_list(self): val = [] val.extend(self.action_list) return val def get_action_dict(self): val = {} for item in self.action_list: #将函数名str => k ,函数本身做value val[item.__name__] = item return val
- ListView 中 通过反射获取到具体的方法名 gettattr 并执行
# 以批量删除为例 def multi_delete(self, request): """ 批量删除的action :param request: :return: """ pk_list = request.POST.getlist('pk') self.model_class.objects.filter(pk__in=pk_list).delete() # return HttpResponse('删除成功')
- template的html页面中
<form class="form-inline" method="post"> {% csrf_token %} {% if cl.action_list %} <div class="form-group"> <select name="action" class="form-control" style="min- 200px;"> <option>请选择功能</option> {% for item in cl.action_list %} <option value="{{ item.name }}">{{ item.text }}</option> {% endfor %} </select> <input class="btn btn-primary" type="submit" value="执行"> </div> {% endif %} {% table cl %} <nav aria-label="Page navigation"> <ul class="pagination"> {{ cl.page.page_html|safe }} </ul> </nav> </form>
注:在 StarkConfig类中或在为每张表写的 StarkConfig的派生类中的 静态字段action_list中存放的是对应具体操作的函数名
2.3 在List页面中,表格中数据前checkbox的定制
- 添加checkbox功能的方法:
def display_checkbok(self,row=None,header = False): ''' 添加checkbox的功能 ''' if header: return '选择' return mark_safe("<input type='checkbox' name='pk' value='%s'/>" % row.pk)
注: 从后端返回标签或者html代码,需要使用 mark_safe
2.4 LIst页面编辑 & 删除按钮
- 添加edit 和编辑按钮的方法
def display_edit(self,row=None,header = False): ''' 添加编辑功能 ''' if header: return '编辑' return mark_safe('<a href="%s"><i class="fa fa-edit" aria-hidden="true"></i></a></a>'% self.reverse_edit_url(row)) def display_del(self,row=None,header = False): ''' 添加删除功能 ''' if header: return '删除' return mark_safe('<a href="%s"><i class="fa fa-trash-o" aria-hidden="true"></i></a>'% self.reverse_del_url(row) ) def display_edit_del(self, row=None, header=False): if header: return "操作" tpl = """<a href="%s"><i class="fa fa-edit" aria-hidden="true"></i></a></a> | <a href="%s"><i class="fa fa-trash-o" aria-hidden="true"></i></a> """ % (self.reverse_edit_url(row),self.reverse_del_url(row)) return mark_safe(tpl)
- 关于删除和编辑的a标签路由 在后边编辑和删除操作返回List页面描述
2.5 List页面的添加按钮
- add按钮的类方法
def get_add_btn(self): return mark_safe('<a href="%s" class="btn btn-info">添加</a>'% self.reverse_add_url())
- a标签跳转链接
def reverse_list_url(self): app_label = self.model_class._meta.app_label model_name = self.model_class._meta.model_name namespace = self.site.namespace name = '%s:%s_%s_changelist'%(namespace,app_label,model_name) list_url = reverse(name) #跳转到list页面前的url origin_condition = self.request.GET.get(self.back_condition_key) if not origin_condition: return list_url list_url = "%s?%s" %(list_url,origin_condition) return list_url
2.6 关键字搜索
- 搜索执行的方法,以及search_list定制的字段
# search_list为字段列表 def get_search_list(self): val = [] val.extend(self.search_list) return val def get_search_condition(self,request): search_list = self.get_search_list() q = request.GET.get('q',"") con = Q() con.connector = "OR" if q: for field in search_list: con.children.append(('%s__contains' % field,q)) return search_list,q,con
- 在changelist视图函数中
## 处理搜索 ## search_list, q, con = self.get_search_condition(request)
- 使用搜索条件后从数据库中查到的queryset的需要加入 Q()作为filter的条件
注: 技术点 : q 对象的使用
2.7 编辑&删除, 或者再使用分页,模糊查询后,在进行编辑和删除后, 保留搜索条件
- 编辑和删除按钮中a标签的url
def reverse_edit_url(self, row): app_label = self.model_class._meta.app_label model_name = self.model_class._meta.model_name namespace = self.site.namespace name = '%s:%s_%s_change'%(namespace,app_label,model_name) edit_url = reverse(name,kwargs={'pk':row.pk}) if not self.request.GET: return edit_url param_str = self.request.GET.urlencode() new_query_dict = QueryDict(mutable=True) new_query_dict[self.back_condition_key] = param_str edit_url = "%s?%s" %(edit_url,new_query_dict.urlencode(),) return edit_url def reverse_del_url(self, row): app_label = self.model_class._meta.app_label model_name = self.model_class._meta.model_name namespace = self.site.namespace name = '%s:%s_%s_delete' % (namespace, app_label,model_name) del_url = reverse(name, kwargs={'pk': row.pk}) if not self.request.GET: return del_url param_str = self.request.GET.urlencode() new_query_dict = QueryDict(mutable=True) new_query_dict[self.back_condition_key] = param_str del_url = "%s?%s" %(del_url,new_query_dict.urlencode(),) return del_url
- 在此时的reverse_url中跳转到List页面的 reverse_list_url
def reverse_list_url(self): app_label = self.model_class._meta.app_label model_name = self.model_class._meta.model_name namespace = self.site.namespace name = '%s:%s_%s_changelist'%(namespace,app_label,model_name) list_url = reverse(name) #跳转到list页面前的url origin_condition = self.request.GET.get(self.back_condition_key) if not origin_condition: return list_url list_url = "%s?%s" %(list_url,origin_condition) return list_url
注: 技术点:QueryDict的使用
2.8 分页组件
- 项目中创建utils文件夹,创建一个 pagination.py
""" 分页组件 """ from urllib.parse import urlencode class Pagination(object): def __init__(self, current_page, all_count, base_url,query_params, per_page=10, pager_page_count=11): """ 分页初始化 :param current_page: 当前页码 :param per_page: 每页显示数据条数 :param all_count: 数据库中总条数 :param base_url: 基础URL :param query_params: QueryDict对象,内部含所有当前URL的原条件 :param pager_page_count: 页面上最多显示的页码数量 """ self.base_url = base_url try: self.current_page = int(current_page) if self.current_page <= 0: raise Exception() except Exception as e: self.current_page = 1 self.query_params = query_params self.per_page = per_page self.all_count = all_count self.pager_page_count = pager_page_count pager_count, b = divmod(all_count, per_page) if b != 0: pager_count += 1 self.pager_count = pager_count half_pager_page_count = int(pager_page_count / 2) self.half_pager_page_count = half_pager_page_count @property def start(self): """ 数据获取值起始索引 :return: """ return (self.current_page - 1) * self.per_page @property def end(self): """ 数据获取值结束索引 :return: """ return self.current_page * self.per_page def page_html(self): """ 生成HTML页码 :return: """ # 如果数据总页码pager_count<11 pager_page_count if self.pager_count < self.pager_page_count: pager_start = 1 pager_end = self.pager_count else: # 数据页码已经超过11 # 判断: 如果当前页 <= 5 half_pager_page_count if self.current_page <= self.half_pager_page_count: pager_start = 1 pager_end = self.pager_page_count else: # 如果: 当前页+5 > 总页码 if (self.current_page + self.half_pager_page_count) > self.pager_count: pager_end = self.pager_count pager_start = self.pager_count - self.pager_page_count + 1 else: pager_start = self.current_page - self.half_pager_page_count pager_end = self.current_page + self.half_pager_page_count page_list = [] if self.current_page <= 1: prev = '<li><a href="#">上一页</a></li>' else: self.query_params['page'] = self.current_page - 1 prev = '<li><a href="%s?%s">上一页</a></li>' % (self.base_url,self.query_params.urlencode()) page_list.append(prev) for i in range(pager_start, pager_end + 1): self.query_params['page'] = i if self.current_page == i: tpl = '<li class="active"><a href="%s?%s">%s</a></li>' % ( self.base_url, self.query_params.urlencode(), i,) else: tpl = '<li><a href="%s?%s">%s</a></li>' % (self.base_url, self.query_params.urlencode(), i,) page_list.append(tpl) if self.current_page >= self.pager_count: nex = '<li><a href="#">下一页</a></li>' else: self.query_params['page'] = self.current_page + 1 nex = '<li><a href="%s?%s">下一页</a></li>' % (self.base_url, self.query_params.urlencode(),) page_list.append(nex) page_str = "".join(page_list) return page_str
- changelist view中处理分页部分的代码
# ##### 处理分页 ##### from stark.utils.pagination import Pagination total_count = self.model_class.objects.filter(con).count() query_params = request.GET.copy() query_params._mutable = True page = Pagination(request.GET.get('page'), total_count, request.path_info, query_params, per_page=3) queryset = self.model_class.objects.filter(con).order_by(*self.get_order_by())[page.start:page.end]
3. 增 -- add_view
- add_view的视图函数
def add_view(self,request): ''' 所有的添加页面都在此函数进行处理 ''' AddModelForm = self.get_model_form_class() if request.method == "GET": form = AddModelForm() return render(request,'stark/change.html',{'form':form}) form = AddModelForm(request.POST) if form.is_valid(): form.save() return redirect(self.reverse_list_url()) return render(request,'stark/change.html',{'from':form})
- 获取form
def get_model_form_class(self): if self.model_form_class: return self.model_form_class class AddModelForm(forms.ModelForm): class Meta: model = self.model_class fields = "__all__" return AddModelForm
注: form的获取是通过 类方法 get_model_form_class,对于不同情况可以自己在StarkConfig的派生类中自定制
4. 改-- edit_view
- edit_view的视图函数
def change_view(self,request,pk): ''' 所有的编辑页面 ''' obj = self.model_class.objects.filter(pk=pk).first() if not obj: return HttpResponse('数据不存在') ModelFormClass = self.get_model_form_class() if request.method == "GET": form = ModelFormClass() return render(request,'stark/change.html',{'form':form}) form = ModelFormClass(data=request.POST,instance=obj) if form.is_valid(): form.save() return redirect(self.reverse_list_url()) return render(request, 'stark/change.html', {'form': form})
- 关于instance
5. 删-- delete_view
- del_view的视图函数
def delete_view(self,request,pk): ''' 所有删除页面 ''' if request.method =='GET': return render(request,'stark/delete.html',{'cancel_url':self.reverse_list_url()}) self.model_class.objects.filter(pk=pk).delete() return redirect(self.reverse_list_url())