curd 插件
1. Django项目启动 自动加载文件 制作启动文件
1. 注册strak 在apps.py 类里面增加如下 def ready(self): from django.utils.module_loading import autodiscover_modules autodiscover_modules("stark") 2. 在已经注册的app中创建stark.py文件 加载
2. 在stark中模仿AdminSite ModelAdmin类写代码 注册自己的类
class StarkConfig(object): def __init__(self,model_class,site): self.model_class = model_class self.site = site class StarkSite(object): def __init__(self): self._registey = {} def register(self,model_class,stark_config_class=None): if not stark_config_class: stark_config_class = StarkConfig self._registey[model_class] = stark_config_class(model_class,self) site = StarkSite()
3. 将注册的类自动生成url
- urls.py from stark.service import v1 urlpatterns = [ url(r'^stark/', v1.site.urls), ] - 为每个类生成4个url v1.py class StarkConfig(object): def __init__(self,model_class,site): self.model_class = model_class self.site = site def get_urls(self): app_model_name = (self.model_class._meta.app_label,self.model_class._meta.model_name,) url_patterns = [ url(r'^$',self.changelist_view,name="%s_%s_changlist" %app_model_name), url(r'^add/$',self.add_view,name="%s_%s_add" %app_model_name), url(r'^(\d+)/delete/$',self.delete_view,name="%s_%s_delete" %app_model_name), url(r'^(\d+)/change/$',self.change_view,name="%s_%s_chang" %app_model_name), ] return url_patterns @property def urls(self): return self.get_urls() def changelist_view(self,request,*args,**kwargs): return HttpResponse('列表') def add_view(self,request,*args,**kwargs): return HttpResponse('添加') def delete_view(self,request,nid,*args,**kwargs): return HttpResponse('删除') def change_view(self,request,nid,*args,**kwargs): return HttpResponse('修改') class StarkSite(object): def __init__(self): self._registey = {} def register(self,model_class,stark_config_class=None): if not stark_config_class: stark_config_class = StarkConfig self._registey[model_class] = stark_config_class(model_class,self) def get_urls(self): url_pattern = [] for model_class,stark_config_obj in self._registry.items(): app_name = model_class._meta.app_label model_name = model_class._meta.model_name curd_url = url(r'^%s/%s/' %(app_name,model_name,) , (stark_config_obj.urls,None,None)) url_pattern.append(curd_url) return url_pattern @property def urls(self): return (self.get_urls(),None,'stark') site = StarkSite()
4. 列表页面展示
- v1.py def changelist_view(self,request,*args,**kwargs): # 处理表头 head_list = [] for field_name in self.list_display: if isinstance(field_name,str): # 根据类和字段名称,获取字段对象的verbose_name verbose_name = self.model_class._meta.get_field(field_name).verbose_name else: verbose_name = field_name(self,is_header=True) head_list.append(verbose_name) # 处理表中的数据 # [ UserInfoObj,UserInfoObj,UserInfoObj,UserInfoObj,] # [ UserInfo(id=1,name='alex',age=18),UserInfo(id=2,name='alex2',age=181),] data_list = self.model_class.objects.all() new_data_list = [] for row in data_list: # row是 UserInfo(id=2,name='alex2',age=181) # row.id,row.name,row.age temp = [] for field_name in self.list_display: if isinstance(field_name,str): val = getattr(row,field_name) # # 2 alex2 else: val = field_name(self,row) temp.append(val) new_data_list.append(temp) return render(request,'stark/changelist.html',{'data_list':new_data_list,'head_list':head_list}) - shark.py class UserInfoConfig(v1.StarkConfig): def checkbox(self,obj=None,is_header=False): if is_header: return '选择' return mark_safe('<input type="checkbox" name="pk" value="%s" />' %(obj.id,)) def edit(self,obj=None,is_header=False): if is_header: return '编辑' return mark_safe('<a href="/edit/%s">编辑</a>' %(obj.id,)) list_display = [checkbox,'id','name',edit]
5. 显示增加按钮
- 先判断是否显示,再通过反向解析生成增加链接 - 后端 # 是否显示增加按钮 show_add_btn = True def get_show_btn(self): return self.show_add_btn return render(request, 'stark/changelist.html', {'data_list': new_data_list, 'head_list': head_list,"add_url":self.get_add_url(),"show_add_btn":self.get_show_bt - 前端 {% if show_add_btn %} <a class="btn btn-primary" href="{{ add_url }}">增加 {% endif %}
6. 增加页面
增加内容页面 1. 通过ModelForm创建公共类 显示和提交 - 后端 def add_view(self, request, *args, **kwargs): class AddTable(ModelForm): class Meta: model = self.model_class fields = "__all__" if request.method == "GET": form = AddTable() return render(request,"stark/add_view.html",{"form":form}) else: form = AddTable(request.POST) if form.is_valid(): form.save() return redirect(self.get_list_url()) else: return render(request, "stark/add_view.html", {"form": form}) - 前端 <form method="post" novalidate> {% csrf_token %} {{ form.as_p }} <input type="submit" value="提交"> </form> 2. 升级 在子类自定义ModelForm类 子类之后可以自定义类 - v1.py model_form_class = None def get_model_form_class(self): if self.model_form_class: return self.model_form_class else: #方式一: # class AddTable(ModelForm): # class Meta: # model = self.model_class # fields = "__all__" # return AddTable #方式二: meta = type("Meta",(object,),{"model":self.model_class,"fields":"__all__"}) AddTable = type("AddTable",(ModelForm,),{"Meta":meta}) return AddTable def add_view(self, request, *args, **kwargs): model_form_class = self.get_model_form_class() if request.method == "GET": form = model_form_class() return render(request,"stark/add_view.html",{"form":form}) else: form = model_form_class(request.POST) if form.is_valid(): form.save() return redirect(self.get_list_url()) else: return render(request, "stark/add_view.html", {"form": form}) - stark.py class UserInfoModelForm(ModelForm): class Meta: model = models.UserInfo fields = ["name","password"] error_messages = { "name":{ 'required':'用户名不能为空' } } class UserInfoConfig(v1.StarkConfig): model_form_class = UserInfoModelForm
9. 修改 删除
def change_view(self, request, nid, *args, **kwargs): obj = self.model_class.objects.filter(pk=nid).first() if not obj: return redirect(self.get_list_url()) model_form_class = self.get_model_form_class() if request.method == "GET": form = model_form_class(instance=obj) return render(request,"stark/change_view.html",{"form":form}) else: form = model_form_class(instance=obj,data=request.POST) if form.is_valid: form.save() return redirect(self.get_list_url()) return render(request,"stark/change_view.html",{"form":form}) def delete_view(self, request, nid, *args, **kwargs): self.model_class.objects.filter(pk=nid).delete() return redirect(self.get_list_url())
10. 组合搜索
阶段一:
阶段一: - 派生类 # 三个条件分别为 choice M2M FK comb_filter = ["gender","depart","roles"] - 基类 comb_filter = [] def get_comb_filter(self): return self.comb_filter - ChangeList 初始化 self.comb_filter = config.get_comb_filter() from django.db.models import ForeignKey,ManyToManyField # 根据列表的字符串找到数据的字段对象,判断对象是否是FK,M2M # 如果不是 为chioce def gen_comb_filter(self): data_list = [] for item in self.comb_filter: # item gender _field = self.model_class._meta.get_field(item) # _field app04.UserInfo.gender if isinstance(_field,ForeignKey): #如果字段的类型是ForeignKey 找到他对应的类和数据 data_list.append(_field.rel.to.objects.all()) elif isinstance(_field,ManyToManyField): data_list.append(_field.rel.to.objects.all()) else: #choice data_list.append(_field.choices) return data_list - 前端 <div> {% for comb in self.gen_comb_filter %} <div> {% for item in comb %} <a href="">{{ item }}</a> {% endfor %} </div> {% endfor %} </div>
阶段二: 用户查找的数据封装到类里面 前端循环 和上面效果一样
class FilterRow(object): def __init__(self,data): self.data = data def __iter__(self): yield "全部" for val in self.data: yield val def gen_comb_filter(self): data_list = [] for item in self.comb_filter: # item gender _field = self.model_class._meta.get_field(item) # _field app04.UserInfo.gender if isinstance(_field,ForeignKey): #如果字段的类型是ForeignKey 找到他对应的类和数据 data_list.append(FilterRow(_field.rel.to.objects.all())) elif isinstance(_field,ManyToManyField): data_list.append(FilterRow(_field.rel.to.objects.all())) else: #choice data_list.append(FilterRow(_field.choices)) return data_list
阶段三: 增加搜索条件可以为多选 封装到类里面判断
comb_filter = [ v1.filterOption("gender"), v1.filterOption("depart"), v1.filterOption("roles",True) ] class filterOption(object): def __init__(self,filed_name,muti=False): self.filed_name = filed_name self.muti = muti def gen_comb_filter(self): data_list = [] for obj in self.comb_filter: # item gender _field = self.model_class._meta.get_field(obj.filed_name) # _field app04.UserInfo.gender if isinstance(_field,ForeignKey): #如果字段的类型是ForeignKey 找到他对应的类和数据 data_list.append(FilterRow(_field.rel.to.objects.all())) elif isinstance(_field,ManyToManyField): data_list.append(FilterRow(_field.rel.to.objects.all())) else: #choice data_list.append(FilterRow(_field.choices)) return data_list
阶段四: 搜索条件自定义 效果如上
好处1: 如部门表有 销售部门 运维部门 开发部门 默认只让显示 销售部门和运维部门 好处2: 可以继承filterOption类 重写filterOption类底下的两个方法
comb_filter = [ v1.filterOption("gender"), v1.filterOption("depart"), v1.filterOption("roles",True) ] class filterOption(object): def __init__(self,filed_name,muti=False,condition=False): self.filed_name = filed_name self.muti = muti self.condition = condition def get_queryset(self,_field): if self.condition: return _field.rel.to.objects.filter() return _field.rel.to.objects.all() def get_choices(self,_field): return _field.choices class FilterRow(object): def __init__(self,data): self.data = data def __iter__(self): yield "全部" for val in self.data: yield val def gen_comb_filter(self): data_list = [] for option in self.comb_filter: # item gender _field = self.model_class._meta.get_field(option.filed_name) # _field app04.UserInfo.gender if isinstance(_field,ForeignKey): #如果字段的类型是ForeignKey 找到他对应的类和数据 row = FilterRow(option.get_queryset(_field)) data_list.append(row) elif isinstance(_field,ManyToManyField): row = FilterRow(option.get_queryset(_field)) data_list.append(row) else: #choice row = FilterRow(option.get_choices(_field)) data_list.append(row) return data_list
阶段五: 返回列表变为生成器
comb_filter = [ v1.filterOption("gender"), v1.filterOption("depart"), v1.filterOption("roles",True) ] class filterOption(object): def __init__(self,filed_name,muti=False,condition=False): self.filed_name = filed_name self.muti = muti self.condition = condition def get_queryset(self,_field): if self.condition: return _field.rel.to.objects.filter() return _field.rel.to.objects.all() def get_choices(self,_field): return _field.choices class FilterRow(object): def __init__(self,data): self.data = data def __iter__(self): yield "全部" for val in self.data: yield val def gen_comb_filter(self): for option in self.comb_filter: # item gender _field = self.model_class._meta.get_field(option.filed_name) # _field app04.UserInfo.gender if isinstance(_field,ForeignKey): #如果字段的类型是ForeignKey 找到他对应的类和数据 row = FilterRow(option.get_queryset(_field)) elif isinstance(_field,ManyToManyField): row = FilterRow(option.get_queryset(_field)) else: #choice row = FilterRow(option.get_choices(_field)) yield row
阶段六: 前端循环时 如果是choice根据索引取值 如果是对象 根据对象的方法取值 所以需要做出区分 并且在后端生成url 全部的a标签还未做
comb_filter = [ v1.filterOption("gender",is_choice=True), v1.filterOption("depart"), v1.filterOption("roles",True) ] class filterOption(object): def __init__(self,filed_name,muti=False,condition=False,is_choice=False): """ :param filed_name: :param muti: :param condition: 过滤条件 """ self.filed_name = filed_name self.muti = muti self.condition = condition self.is_choice = is_choice def get_queryset(self,_field): if self.condition: return _field.rel.to.objects.filter() return _field.rel.to.objects.all() def get_choices(self,_field): return _field.choices class FilterRow(object): def __init__(self,option,data,params): self.option = option self.data = data self.params = copy.deepcopy(params) def __iter__(self): yield mark_safe("<a href={0}>全部</a>".format("111")) for val in self.data: if self.option.is_choice: pk,text = val else: pk,text = val.pk,str(val) yield mark_safe("<a href={0}>{1}</a>".format(pk,text)) def gen_comb_filter(self): for option in self.comb_filter: # item gender _field = self.model_class._meta.get_field(option.filed_name) # _field app04.UserInfo.gender if isinstance(_field,ForeignKey): #如果字段的类型是ForeignKey 找到他对应的类和数据 row = FilterRow(option,option.get_queryset(_field),self.request.GET) elif isinstance(_field,ManyToManyField): row = FilterRow(option,option.get_queryset(_field),self.request.GET) else: #choice row = FilterRow(option,option.get_choices(_field),self.request.GET) yield row
阶段七: 后端生成url 全部未做
class filterOption(object): def __init__(self,filed_name,muti=False,condition=False,is_choice=False): """ :param filed_name: :param muti: :param condition: 过滤条件 """ self.filed_name = filed_name self.muti = muti self.condition = condition self.is_choice = is_choice def get_queryset(self,_field): if self.condition: return _field.rel.to.objects.filter() return _field.rel.to.objects.all() def get_choices(self,_field): return _field.choices class FilterRow(object): def __init__(self,option,data,request): self.option = option self.data = data self.request = request def __iter__(self): params = copy.deepcopy(self.request.GET) params._mutable = True yield mark_safe("<a href={0}>全部</a>".format("111")) for val in self.data: if self.option.is_choice: pk,text = val else: pk,text = val.pk,str(val) params[self.option.filed_name] = pk url = "%s?%s"%(self.request.path_info,params.urlencode()) yield mark_safe("<a href={0}>{1}</a>".format(url,text)) def gen_comb_filter(self): for option in self.comb_filter: # item gender _field = self.model_class._meta.get_field(option.filed_name) # _field app04.UserInfo.gender if isinstance(_field,ForeignKey): #如果字段的类型是ForeignKey 找到他对应的类和数据 row = FilterRow(option,option.get_queryset(_field),self.request) elif isinstance(_field,ManyToManyField): row = FilterRow(option,option.get_queryset(_field),self.request) else: #choice row = FilterRow(option,option.get_choices(_field),self.request) yield row
阶段八: 请求的值如果和循环的值如果相同 全部和其它按钮 都判断
comb_filter = [ v1.filterOption("gender",is_choice=True), v1.filterOption("depart"), v1.filterOption("roles",True) ] class filterOption(object): def __init__(self,filed_name,muti=False,condition=False,is_choice=False): """ :param filed_name: :param muti: :param condition: 过滤条件 """ self.filed_name = filed_name self.muti = muti self.condition = condition self.is_choice = is_choice def get_queryset(self,_field): if self.condition: return _field.rel.to.objects.filter() return _field.rel.to.objects.all() def get_choices(self,_field): return _field.choices class FilterRow(object): def __init__(self,option,data,request): self.option = option self.data = data self.request = request def __iter__(self): params = copy.deepcopy(self.request.GET) params._mutable = True current_id = params.get(self.option.filed_name) # 如果循环的值 在前端传过来的里面 删除 if self.option.filed_name in params: del params[self.option.filed_name] url = "{0}{1}".format(self.request.path_info, params.urlencode()) yield mark_safe('<a href="{0}">全部</a>'.format(url)) else: url = "{0}{1}".format(self.request.path_info, params.urlencode()) yield mark_safe('<a href="{0}" class="active">全部</a>'.format(url)) for val in self.data: if self.option.is_choice: pk,text = str(val[0]),str(val[1]) else: pk,text = str(val.pk),str(val) params[self.option.filed_name] = pk url = "%s?%s"%(self.request.path_info,params.urlencode()) if current_id == pk: yield mark_safe("<a href={0} class='active'>{1}</a>".format(url, text)) else: yield mark_safe("<a href={0}>{1}</a>".format(url,text)) def gen_comb_filter(self): for option in self.comb_filter: # item gender _field = self.model_class._meta.get_field(option.filed_name) # _field app04.UserInfo.gender if isinstance(_field,ForeignKey): #如果字段的类型是ForeignKey 找到他对应的类和数据 row = FilterRow(option,option.get_queryset(_field),self.request) elif isinstance(_field,ManyToManyField): row = FilterRow(option,option.get_queryset(_field),self.request) else: #choice row = FilterRow(option,option.get_choices(_field),self.request) yield row
阶段九: 单选前后台完成
# 组合搜索过滤 comb_codition = {} option_list = self.get_comb_filter() flag = False for key in request.GET.keys(): value_list = request.GET.getlist(key) for option in option_list: if option.filed_name == key: flag = True print("111") break if flag: print("22") comb_codition["%s__in"%key] = value_list print(comb_codition) queryset = self.model_class.objects.filter(self.get_search_condition()).filter(**comb_codition)
阶段十 多选完成
class filterOption(object): def __init__(self,filed_name,muti=False,condition=False,is_choice=False): """ :param filed_name: :param muti: :param condition: 过滤条件 """ self.filed_name = filed_name self.muti = muti self.condition = condition self.is_choice = is_choice def get_queryset(self,_field): if self.condition: return _field.rel.to.objects.filter() return _field.rel.to.objects.all() def get_choices(self,_field): return _field.choices class FilterRow(object): def __init__(self,option,data,request): self.option = option self.data = data self.request = request def __iter__(self): params = copy.deepcopy(self.request.GET) params._mutable = True current_id = params.get(self.option.filed_name) current_id_list = params.getlist(self.option.filed_name) # 全部 如果循环的值 在前端传过来的里面 删除 if self.option.filed_name in params: origin_list = params.pop(self.option.filed_name) url = "{0}?{1}".format(self.request.path_info, params.urlencode()) yield mark_safe('<a href="{0}">全部</a>'.format(url)) params.setlist(self.option.filed_name,origin_list) else: url = "{0}?{1}".format(self.request.path_info, params.urlencode()) yield mark_safe('<a href="{0}" class="active">全部</a>'.format(url)) # 全部 后面的值 for val in self.data: if self.option.is_choice: pk,text = str(val[0]),str(val[1]) else: pk,text = str(val.pk),str(val) if not self.option.muti: # 单选 params[self.option.filed_name] = pk url = "%s?%s"%(self.request.path_info,params.urlencode()) if current_id == pk: yield mark_safe("<a href={0} class='active'>{1}</a>".format(url, text)) else: yield mark_safe("<a href={0}>{1}</a>".format(url,text)) else: # 多选 _params = copy.deepcopy(params) id_list = params.getlist(self.option.filed_name) if pk in current_id_list: id_list.remove(pk) _params.setlist(self.option.filed_name, id_list) url = "%s?%s" % (self.request.path_info, _params.urlencode()) yield mark_safe("<a href={0} class='active'>{1}</a>".format(url, text)) else: id_list.append(pk) _params.setlist(self.option.filed_name,id_list) url = "%s?%s" % (self.request.path_info, _params.urlencode()) yield mark_safe("<a href={0}>{1}</a>".format(url, text)) queryset = self.model_class.objects.filter(self.get_search_condition()).filter(**comb_codition).distinct()
阶段一: - 派生类 # 三个条件分别为 choice M2M FK comb_filter = ["gender","depart","roles"] - 基类 comb_filter = [] def get_comb_filter(self): return self.comb_filter - ChangeList 初始化 self.comb_filter = config.get_comb_filter() from django.db.models import ForeignKey,ManyToManyField # 根据列表的字符串找到数据的字段对象,判断对象是否是FK,M2M # 如果不是 为chioce def gen_comb_filter(self): data_list = [] [ ((1,男),(2,女)), [obj,obj,obj,obj], [obj,obj,obj,obj] ] for item in self.comb_filter: # item gender _field = self.model_class._meta.get_field(item) # _field app04.UserInfo.gender if isinstance(_field,ForeignKey): #如果字段的类型是ForeignKey 找到他对应的类和数据 data_list.append(_field.rel.to.objects.all()) elif isinstance(_field,ManyToManyField): data_list.append(_field.rel.to.objects.all()) else: #choice data_list.append(_field.choices) return data_list - 前端 <div> {% for comb in self.gen_comb_filter %} <div> {% for item in comb %} <a href="">{{ item }}</a> {% endfor %} </div> {% endfor %} </div> 阶段二: 用户查找的数据封装到类里面 前端循环 和上面效果一样 class FilterRow(object): def __init__(self,data): self.data = data def __iter__(self): yield "全部" for val in self.data: yield val def gen_comb_filter(self): data_list = [] for item in self.comb_filter: # item gender _field = self.model_class._meta.get_field(item) # _field app04.UserInfo.gender if isinstance(_field,ForeignKey): #如果字段的类型是ForeignKey 找到他对应的类和数据 data_list.append(FilterRow(_field.rel.to.objects.all())) elif isinstance(_field,ManyToManyField): data_list.append(FilterRow(_field.rel.to.objects.all())) else: #choice data_list.append(FilterRow(_field.choices)) return data_list 阶段三: 搜索条件可以为多选 封装到类里面判断 comb_filter = [ v1.filterOption("gender"), v1.filterOption("depart"), v1.filterOption("roles",True) ] class filterOption(object): def __init__(self,filed_name,muti=False): self.filed_name = filed_name self.muti = muti def gen_comb_filter(self): data_list = [] for obj in self.comb_filter: # item gender _field = self.model_class._meta.get_field(obj.filed_name) # _field app04.UserInfo.gender if isinstance(_field,ForeignKey): #如果字段的类型是ForeignKey 找到他对应的类和数据 data_list.append(FilterRow(_field.rel.to.objects.all())) elif isinstance(_field,ManyToManyField): data_list.append(FilterRow(_field.rel.to.objects.all())) else: #choice data_list.append(FilterRow(_field.choices)) return data_list 阶段四: 搜索条件自定义 好处1: 如部门表有 销售部门 运维部门 开发部门 默认只让显示 销售部门和运维部门 好处2: 可以继承filterOption类 重写filterOption类底下的两个方法 comb_filter = [ v1.filterOption("gender"), v1.filterOption("depart"), v1.filterOption("roles",True) ] class filterOption(object): def __init__(self,filed_name,muti=False,condition=False): self.filed_name = filed_name self.muti = muti self.condition = condition def get_queryset(self,_field): if self.condition: return _field.rel.to.objects.filter() return _field.rel.to.objects.all() def get_choices(self,_field): return _field.choices class FilterRow(object): def __init__(self,data): self.data = data def __iter__(self): yield "全部" for val in self.data: yield val def gen_comb_filter(self): data_list = [] for option in self.comb_filter: # item gender _field = self.model_class._meta.get_field(option.filed_name) # _field app04.UserInfo.gender if isinstance(_field,ForeignKey): #如果字段的类型是ForeignKey 找到他对应的类和数据 row = FilterRow(option.get_queryset(_field)) data_list.append(row) elif isinstance(_field,ManyToManyField): row = FilterRow(option.get_queryset(_field)) data_list.append(row) else: #choice row = FilterRow(option.get_choices(_field)) data_list.append(row) return data_list 阶段六: 前端循环时 如果是choice根据索引取值 如果是对象 根据对象的方法取值 所以需要做出区分 并且在后端生成url 全部的a标签还未做 comb_filter = [ v1.filterOption("gender",is_choice=True), v1.filterOption("depart"), v1.filterOption("roles",True) ] class filterOption(object): def __init__(self,filed_name,muti=False,condition=False,is_choice=False): """ :param filed_name: :param muti: :param condition: 过滤条件 """ self.filed_name = filed_name self.muti = muti self.condition = condition self.is_choice = is_choice def get_queryset(self,_field): if self.condition: return _field.rel.to.objects.filter() return _field.rel.to.objects.all() def get_choices(self,_field): return _field.choices class FilterRow(object): def __init__(self,option,data,params): self.option = option self.data = data self.params = copy.deepcopy(params) def __iter__(self): yield mark_safe("<a href={0}>全部</a>".format("111")) for val in self.data: if self.option.is_choice: pk,text = val else: pk,text = val.pk,str(val) yield mark_safe("<a href={0}>{1}</a>".format(pk,text)) def gen_comb_filter(self): for option in self.comb_filter: # item gender _field = self.model_class._meta.get_field(option.filed_name) # _field app04.UserInfo.gender if isinstance(_field,ForeignKey): #如果字段的类型是ForeignKey 找到他对应的类和数据 row = FilterRow(option,option.get_queryset(_field),self.request.GET) elif isinstance(_field,ManyToManyField): row = FilterRow(option,option.get_queryset(_field),self.request.GET) else: #choice row = FilterRow(option,option.get_choices(_field),self.request.GET) yield row 阶段七: 后端生成url 全部未做 class filterOption(object): def __init__(self,filed_name,muti=False,condition=False,is_choice=False): """ :param filed_name: :param muti: :param condition: 过滤条件 """ self.filed_name = filed_name self.muti = muti self.condition = condition self.is_choice = is_choice def get_queryset(self,_field): if self.condition: return _field.rel.to.objects.filter() return _field.rel.to.objects.all() def get_choices(self,_field): return _field.choices class FilterRow(object): def __init__(self,option,data,request): self.option = option self.data = data self.request = request def __iter__(self): params = copy.deepcopy(self.request.GET) params._mutable = True yield mark_safe("<a href={0}>全部</a>".format("111")) for val in self.data: if self.option.is_choice: pk,text = val else: pk,text = val.pk,str(val) params[self.option.filed_name] = pk url = "%s?%s"%(self.request.path_info,params.urlencode()) yield mark_safe("<a href={0}>{1}</a>".format(url,text)) def gen_comb_filter(self): for option in self.comb_filter: # item gender _field = self.model_class._meta.get_field(option.filed_name) # _field app04.UserInfo.gender if isinstance(_field,ForeignKey): #如果字段的类型是ForeignKey 找到他对应的类和数据 row = FilterRow(option,option.get_queryset(_field),self.request) elif isinstance(_field,ManyToManyField): row = FilterRow(option,option.get_queryset(_field),self.request) else: #choice row = FilterRow(option,option.get_choices(_field),self.request) yield row 阶段八 请求的值如果和循环的值如果相同 全部和其它按钮 都判断 comb_filter = [ v1.filterOption("gender",is_choice=True), v1.filterOption("depart"), v1.filterOption("roles",True) ] class filterOption(object): def __init__(self,filed_name,muti=False,condition=False,is_choice=False): """ :param filed_name: :param muti: :param condition: 过滤条件 """ self.filed_name = filed_name self.muti = muti self.condition = condition self.is_choice = is_choice def get_queryset(self,_field): if self.condition: return _field.rel.to.objects.filter() return _field.rel.to.objects.all() def get_choices(self,_field): return _field.choices class FilterRow(object): def __init__(self,option,data,request): self.option = option self.data = data self.request = request def __iter__(self): params = copy.deepcopy(self.request.GET) params._mutable = True current_id = params.get(self.option.filed_name) # 如果循环的值 在前端传过来的里面 删除 if self.option.filed_name in params: del params[self.option.filed_name] url = "{0}{1}".format(self.request.path_info, params.urlencode()) yield mark_safe('<a href="{0}">全部</a>'.format(url)) else: url = "{0}{1}".format(self.request.path_info, params.urlencode()) yield mark_safe('<a href="{0}" class="active">全部</a>'.format(url)) for val in self.data: if self.option.is_choice: pk,text = str(val[0]),str(val[1]) else: pk,text = str(val.pk),str(val) params[self.option.filed_name] = pk url = "%s?%s"%(self.request.path_info,params.urlencode()) if current_id == pk: yield mark_safe("<a href={0} class='active'>{1}</a>".format(url, text)) else: yield mark_safe("<a href={0}>{1}</a>".format(url,text)) def gen_comb_filter(self): for option in self.comb_filter: # item gender _field = self.model_class._meta.get_field(option.filed_name) # _field app04.UserInfo.gender if isinstance(_field,ForeignKey): #如果字段的类型是ForeignKey 找到他对应的类和数据 row = FilterRow(option,option.get_queryset(_field),self.request) elif isinstance(_field,ManyToManyField): row = FilterRow(option,option.get_queryset(_field),self.request) else: #choice row = FilterRow(option,option.get_choices(_field),self.request) yield row 阶段九 单选完成 # 组合搜索过滤 comb_codition = {} option_list = self.get_comb_filter() flag = False for key in request.GET.keys(): value_list = request.GET.getlist(key) for option in option_list: if option.filed_name == key: flag = True print("111") break if flag: print("22") comb_codition["%s__in"%key] = value_list print(comb_codition) queryset = self.model_class.objects.filter(self.get_search_condition()).filter(**comb_codition) 阶段十 多选完成 class filterOption(object): def __init__(self,filed_name,muti=False,condition=False,is_choice=False): """ :param filed_name: :param muti: :param condition: 过滤条件 """ self.filed_name = filed_name self.muti = muti self.condition = condition self.is_choice = is_choice def get_queryset(self,_field): if self.condition: return _field.rel.to.objects.filter() return _field.rel.to.objects.all() def get_choices(self,_field): return _field.choices class FilterRow(object): def __init__(self,option,data,request): self.option = option self.data = data self.request = request def __iter__(self): params = copy.deepcopy(self.request.GET) params._mutable = True current_id = params.get(self.option.filed_name) current_id_list = params.getlist(self.option.filed_name) # 全部 如果循环的值 在前端传过来的里面 删除 if self.option.filed_name in params: origin_list = params.pop(self.option.filed_name) url = "{0}?{1}".format(self.request.path_info, params.urlencode()) yield mark_safe('<a href="{0}">全部</a>'.format(url)) params.setlist(self.option.filed_name,origin_list) else: url = "{0}?{1}".format(self.request.path_info, params.urlencode()) yield mark_safe('<a href="{0}" class="active">全部</a>'.format(url)) # 全部 后面的值 for val in self.data: if self.option.is_choice: pk,text = str(val[0]),str(val[1]) else: pk,text = str(val.pk),str(val) if not self.option.muti: # 单选 params[self.option.filed_name] = pk url = "%s?%s"%(self.request.path_info,params.urlencode()) if current_id == pk: yield mark_safe("<a href={0} class='active'>{1}</a>".format(url, text)) else: yield mark_safe("<a href={0}>{1}</a>".format(url,text)) else: # 多选 _params = copy.deepcopy(params) id_list = params.getlist(self.option.filed_name) if pk in current_id_list: id_list.remove(pk) _params.setlist(self.option.filed_name, id_list) url = "%s?%s" % (self.request.path_info, _params.urlencode()) yield mark_safe("<a href={0} class='active'>{1}</a>".format(url, text)) else: id_list.append(pk) _params.setlist(self.option.filed_name,id_list) url = "%s?%s" % (self.request.path_info, _params.urlencode()) yield mark_safe("<a href={0}>{1}</a>".format(url, text)) queryset = self.model_class.objects.filter(self.get_search_condition()).filter(**comb_codition).distinct()
11. popup
阶段一: 对于多选或单选 在后面增加popup按钮 其余不加
- 在后端生成返回给前端 - 如果是field是ModelChoiceField 则是Fk, 如果是ModelMultipChoice 则是M2M - GET请求 循环form,打印各个字段的类型 发现类型都为 <class 'django.forms.boundfield.BoundField'> 它是对字段的一次封装 def add_view(self, request, *args, **kwargs): model_form_class = self.get_model_form_class() if request.method == "GET": form = model_form_class() """ for field in form: print(type(field)) <class 'django.forms.boundfield.BoundField'> """ return render(request, "stark/add_view.html", {"form": form}) else: form = model_form_class(request.POST) if form.is_valid(): form.save() return redirect(self.get_list_url()) else: return render(request, "stark/add_view.html", {"form": form}) - 在 BoundField类里面再找一个field属性 九可以找到每个类的属性 最后三个类都为ModelChoiceField的子类 """ for bfield in form: print(type(bfield.field)) <class 'django.forms.fields.CharField'> <class 'django.forms.fields.CharField'> <class 'django.forms.fields.TypedChoiceField'> <class 'django.forms.models.ModelChoiceField'> <class 'django.forms.models.ModelMultipleChoiceField'> """
- 后台代码 def add_view(self, request, *args, **kwargs): model_form_class = self.get_model_form_class() if request.method == "GET": form = model_form_class() from django.forms import ModelChoiceField new_choice = [] for bfield in form: tmp = {"is_popup":False,"item":bfield} if isinstance(bfield.field,ModelChoiceField): tmp["is_popup"] = True new_choice.append(tmp) return render(request, "stark/add_view.html", {"form": new_choice}) else: form = model_form_class(request.POST) if form.is_valid(): form.save() return redirect(self.get_list_url()) else: return render(request, "stark/add_view.html", {"form": form}) - 前端 {% for dic in form %} <div class="form-group"> <label for="inputEmail3" class="col-sm-2 control-label">{{ dic.label }}</label> <div class="col-sm-10" style="position: relative"> {{ dic.item }} {% if dic.is_popup %} <div style="position: absolute;right: -8px;top: 10px;"> <a class="glyphicon glyphicon-search" aria-hidden="true"></a> </div> {% endif %} {{ dic.item.errors.0 }} </div> </div> {% endfor %}
阶段二: 实现popup
<form method="post" class="form-horizontal" novalidate> {% csrf_token %} {% for dic in form %} <div class="col-sm-6"> <div class="form-group" style="margin-bottom: 20px;"> <label for="inputEmail3" class="col-sm-2 control-label">{{ dic.item.field.label }}</label> <div class="col-sm-10" style="position: relative"> <!-- 判断,如果field是ModelChoiceField,则FK; 如果是ModelMultipleChoiceField,则M2M --> {{ dic.item }} {% if dic.is_popup %} <div style="position: absolute;right: -8px;top: 10px;"> <a onclick="popUp('{{ dic.popup_url }}')" class="glyphicon glyphicon-search"></a> </div> {% endif %} <div style="position: absolute;font-size: 12px;left: 15px; right: 15px;color: #e4393c;background: #FFEBEB;">{{ dic.item.errors.0 }}</div> </div> </div> </div> {% endfor %} <div class="col-sm-offset-11 col-sm-1"> <input type="submit" class="btn btn-primary" value="提交"> </div> </form> <script> function popupCallback(dic) { var op = document.createElement('option'); op.value = dic.id; op.text = dic.text; op.setAttribute('selected','selected'); document.getElementById(dic.popbackid).appendChild(op); } function popUp(url) { var popupPage = window.open(url, url, "status=1, height:500, width:600, toolbar=0, resizeable=0"); } </script>
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>正在关闭</title> </head> <body> <script> (function () { var dic = {{ json_result|safe }}; opener.popupCallback(dic); window.close(); })() </script> </body> </html>
def add_view(self, request, *args, **kwargs): model_form_class = self.get_model_form_class() _popbackid = request.GET.get('_popbackid') if request.method == "GET": form = model_form_class() from django.forms import ModelChoiceField new_choice = [] for bfield in form: temp = {"is_popup":False,"item":bfield} if isinstance(bfield.field,ModelChoiceField): #获取类名 related_class_name = bfield.field.queryset.model app_model_name = related_class_name._meta.app_label,related_class_name._meta.model_name print(app_model_name) base_url = reverse("stark:%s_%s_add"%app_model_name) popurl = "%s?_popbackid=%s"%(base_url,bfield.auto_id) temp["popup_url"] = popurl temp["is_popup"] = True new_choice.append(temp) return render(request, "stark/add_view.html", {"form": new_choice}) else: form = model_form_class(request.POST) if form.is_valid(): form_obj = form.save() if _popbackid: # 是popup请求 # render一个页面,写自执行函数 result = {'id':form_obj.pk, 'text':str(form_obj),'popbackid':_popbackid } return render(request,'stark/popup_response.html',{'json_result':json.dumps(result,ensure_ascii=False)}) else: return redirect(self.get_list_url()) else: return render(request, "stark/add_view.html", {"form": form})
阶段三: 编辑和增加生成的页面都用到popup 代码需要重写一次,so引入 templatetags
from django.template import Library from django.forms import ModelChoiceField from django.shortcuts import reverse register = Library() @register.inclusion_tag('stark/form.html') def form(model_form_obj): new_choice = [] for bfield in model_form_obj: temp = {"is_popup": False, "item": bfield} if isinstance(bfield.field, ModelChoiceField): # 获取类名 related_class_name = bfield.field.queryset.model app_model_name = related_class_name._meta.app_label, related_class_name._meta.model_name print(app_model_name) base_url = reverse("stark:%s_%s_add" % app_model_name) popurl = "%s?_popbackid=%s" % (base_url, bfield.auto_id) temp["popup_url"] = popurl temp["is_popup"] = True new_choice.append(temp) return {"form": new_choice}
{% load staticfiles %} {% load change_form %} <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <link rel="stylesheet" href="{% static "stark/bootstrap/css/bootstrap.css" %}" /> <link rel="stylesheet" href="{% static "stark/css/stark-form.css" %}" /> </head> <body> <div class="container" style="width: 800px;height: 500px"> <h1 class="text-center">增加页面</h1> {% form form %} </div> </body> </html>
{% load staticfiles %} {% load change_form %} <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <link rel="stylesheet" href="{% static "stark/bootstrap/css/bootstrap.css" %}" /> <link rel="stylesheet" href="{% static "stark/css/stark-form.css" %}" /> </head> <body> {# <h1>修改页面</h1>#} {# <form method="post" novalidate>#} {# {% csrf_token %}#} {# {{ form.as_p }}#} {# <input type="submit" value="提交">#} {# </form>#} <div class="container" style="width: 800px;height: 500px"> <h1 class="text-center">编辑页面</h1> {% form form %} </div> </body> </html>
def add_view(self, request, *args, **kwargs): model_form_class = self.get_model_form_class() _popbackid = request.GET.get('_popbackid') if request.method == "GET": form = model_form_class() return render(request, "stark/add_view.html", {"form": form}) else: form = model_form_class(request.POST) if form.is_valid(): form_obj = form.save() if _popbackid: # 是popup请求 # render一个页面,写自执行函数 result = {'id':form_obj.pk, 'text':str(form_obj),'popbackid':_popbackid } return render(request,'stark/popup_response.html',{'json_result':json.dumps(result,ensure_ascii=False)}) else: return redirect(self.get_list_url()) else: return render(request, "stark/add_view.html", {"form": form})
12. 其它列可编辑
子类 #8. 其它列可编辑 # def get_list_display(self): # data = [] # if self.list_display: # data.extend(self.list_display) # data.append(v1.StarkConfig.delete) # data.insert(0, v1.StarkConfig.checkbox) # return data edit_link = ["title"] class StarkConfig(object): edit_link = ["title"] edit_link = [] def get_edit_link(self): result = [] if self.edit_link: result.extend(self.edit_link) return result class ChangeList(object): self.edit_link = config.get_edit_link() def body_list(self): # 处理表中的数据 # [ UserInfoObj,UserInfoObj,UserInfoObj,UserInfoObj,] # [ UserInfo(id=1,name='alex',age=18),UserInfo(id=2,name='alex2',age=181),] data_list = self.data_list new_data_list = [] for row in data_list: # row是 UserInfo(id=2,name='alex2',age=181) # row.id,row.name,row.age temp = [] for field_name in self.list_display: if isinstance(field_name,str): val = getattr(row,field_name) # # 2 alex2 # 判断是否在编辑列表中 if field_name in self.edit_link: val = self.edit_link_url(row.pk,val) else: val = field_name(self.config,row) temp.append(val) new_data_list.append(temp) return new_data_list # 其它列的作为编辑功能的url def edit_link_url(self,pk,text): query_str = self.request.GET.urlencode() # page=2&nid=1 params = QueryDict(mutable=True) params[self.config._query_param_key] = query_str return mark_safe('<a href="%s?%s">%s</a>' % (self.config.get_change_url(pk), params.urlencode(),text))
整个流程
1. Django项目启动 自动加载文件 制作启动文件 1. 注册strak 在apps.py 类里面增加如下 def ready(self): from django.utils.module_loading import autodiscover_modules autodiscover_modules("stark") 2. 在已经注册的app中创建stark.py文件 加载 2. 在stark中模仿AdminSite ModelAdmin类写代码 注册自己的类 class StarkConfig(object): def __init__(self,model_class,site): self.model_class = model_class self.site = site class StarkSite(object): def __init__(self): self._registey = {} def register(self,model_class,stark_config_class=None): if not stark_config_class: stark_config_class = StarkConfig self._registey[model_class] = stark_config_class(model_class,self) site = StarkSite() 3. 将注册的类自动生成url - urls.py from stark.service import v1 urlpatterns = [ url(r'^stark/', v1.site.urls), ] - 为每个类生成4个url v1.py class StarkConfig(object): def __init__(self,model_class,site): self.model_class = model_class self.site = site def get_urls(self): app_model_name = (self.model_class._meta.app_label,self.model_class._meta.model_name,) url_patterns = [ url(r'^$',self.changelist_view,name="%s_%s_changlist" %app_model_name), url(r'^add/$',self.add_view,name="%s_%s_add" %app_model_name), url(r'^(\d+)/delete/$',self.delete_view,name="%s_%s_delete" %app_model_name), url(r'^(\d+)/change/$',self.change_view,name="%s_%s_chang" %app_model_name), ] return url_patterns @property def urls(self): return self.get_urls() def changelist_view(self,request,*args,**kwargs): return HttpResponse('列表') def add_view(self,request,*args,**kwargs): return HttpResponse('添加') def delete_view(self,request,nid,*args,**kwargs): return HttpResponse('删除') def change_view(self,request,nid,*args,**kwargs): return HttpResponse('修改') class StarkSite(object): def __init__(self): self._registey = {} def register(self,model_class,stark_config_class=None): if not stark_config_class: stark_config_class = StarkConfig self._registey[model_class] = stark_config_class(model_class,self) def get_urls(self): url_pattern = [] for model_class,stark_config_obj in self._registry.items(): app_name = model_class._meta.app_label model_name = model_class._meta.model_name curd_url = url(r'^%s/%s/' %(app_name,model_name,) , (stark_config_obj.urls,None,None)) url_pattern.append(curd_url) return url_pattern @property def urls(self): return (self.get_urls(),None,'stark') site = StarkSite() 4. 列表页面展示 - v1.py def changelist_view(self,request,*args,**kwargs): # 处理表头 head_list = [] for field_name in self.list_display: if isinstance(field_name,str): # 根据类和字段名称,获取字段对象的verbose_name verbose_name = self.model_class._meta.get_field(field_name).verbose_name else: verbose_name = field_name(self,is_header=True) head_list.append(verbose_name) # 处理表中的数据 # [ UserInfoObj,UserInfoObj,UserInfoObj,UserInfoObj,] # [ UserInfo(id=1,name='alex',age=18),UserInfo(id=2,name='alex2',age=181),] data_list = self.model_class.objects.all() new_data_list = [] for row in data_list: # row是 UserInfo(id=2,name='alex2',age=181) # row.id,row.name,row.age temp = [] for field_name in self.list_display: if isinstance(field_name,str): val = getattr(row,field_name) # # 2 alex2 else: val = field_name(self,row) temp.append(val) new_data_list.append(temp) return render(request,'stark/changelist.html',{'data_list':new_data_list,'head_list':head_list}) - shark.py class UserInfoConfig(v1.StarkConfig): def checkbox(self,obj=None,is_header=False): if is_header: return '选择' return mark_safe('<input type="checkbox" name="pk" value="%s" />' %(obj.id,)) def edit(self,obj=None,is_header=False): if is_header: return '编辑' return mark_safe('<a href="/edit/%s">编辑</a>' %(obj.id,)) list_display = [checkbox,'id','name',edit] 4. 显示增加按钮 - 先判断是否显示,再通过反向解析生成增加链接 - 后端 # 是否显示增加按钮 show_add_btn = True def get_show_btn(self): return self.show_add_btn return render(request, 'stark/changelist.html', {'data_list': new_data_list, 'head_list': head_list,"add_url":self.get_add_url(),"show_add_btn":self.get_show_btn()}) - 前端 {% if show_add_btn %} <a class="btn btn-primary" href="{{ add_url }}">增加</a> {% endif %} 5. 增加内容页面 1. 通过ModelForm创建公共类 显示和提交 - 后端 def add_view(self, request, *args, **kwargs): class AddTable(ModelForm): class Meta: model = self.model_class fields = "__all__" if request.method == "GET": form = AddTable() return render(request,"stark/add_view.html",{"form":form}) else: form = AddTable(request.POST) if form.is_valid(): form.save() return redirect(self.get_list_url()) else: return render(request, "stark/add_view.html", {"form": form}) - 前端 <form method="post" novalidate> {% csrf_token %} {{ form.as_p }} <input type="submit" value="提交"> </form> 2. 升级 在子类自定义ModelForm类 子类之后可以自定义类 - v1.py model_form_class = None def get_model_form_class(self): if self.model_form_class: return self.model_form_class else: #方式一: # class AddTable(ModelForm): # class Meta: # model = self.model_class # fields = "__all__" # return AddTable #方式二: meta = type("Meta",(object,),{"model":self.model_class,"fields":"__all__"}) AddTable = type("AddTable",(ModelForm,),{"Meta":meta}) return AddTable def add_view(self, request, *args, **kwargs): model_form_class = self.get_model_form_class() if request.method == "GET": form = model_form_class() return render(request,"stark/add_view.html",{"form":form}) else: form = model_form_class(request.POST) if form.is_valid(): form.save() return redirect(self.get_list_url()) else: return render(request, "stark/add_view.html", {"form": form}) - stark.py class UserInfoModelForm(ModelForm): class Meta: model = models.UserInfo fields = ["name","password"] error_messages = { "name":{ 'required':'用户名不能为空' } } class UserInfoConfig(v1.StarkConfig): model_form_class = UserInfoModelForm 6. 修改 和 删除 def change_view(self, request, nid, *args, **kwargs): obj = self.model_class.objects.filter(pk=nid).first() if not obj: return redirect(self.get_list_url()) model_form_class = self.get_model_form_class() if request.method == "GET": form = model_form_class(instance=obj) return render(request,"stark/change_view.html",{"form":form}) else: form = model_form_class(instance=obj,data=request.POST) if form.is_valid: form.save() return redirect(self.get_list_url()) return render(request,"stark/change_view.html",{"form":form}) def delete_view(self, request, nid, *args, **kwargs): self.model_class.objects.filter(pk=nid).delete() return redirect(self.get_list_url())