仿造Django中的admin自己实现增删改查、模糊搜索、批量操作、条件筛选、popup功能的插件
1.创建组件
首先创建一个app,这里取名为stark,在settings.py中将其进行注册
INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'stark.apps.StarkConfig', ]
2.启动项
在stark的apps中写入启动项
#在apps.py中: from django.apps import AppConfig #必须写的内容,是Django启动的时候会自动加载 class StarkConfig(AppConfig): name = 'stark' def ready(self): from django.utils.module_loading import autodiscover_modules autodiscover_modules('stark')
3.创建插件文件
在stark下创建一个叫service的包,包中创建一个文件,这里取名为v1.py。在v1中创建类似于AdminSite ModelAdmin的类,用于自己封装组件
class StarkConfig(object): """ 用于为每个类(即每张表)生成url对应关系,并处理用户请求的基类 日后会封装很多功能 """ def __init__(self,model_class,site): self.model_class = model_class self.site = site class StarkSite(object): ''' 单例模式创建的对象的类,是一个容器,用于放置处理请求对应关系 {model.UserInfo:StarkConfig(model.UserInfo,self)} ''' def __init__(self): self._registey = {} def register(self,model_class,stark_config_class=None): if not stark_config_class: #stark_config_class,没写派生类时默认给予StarkConfig stark_config_class = StarkConfig self._registey[model_class] = stark_config_class(model_class,self) #字典{表名:stark_config_class(表名,self)} site = StarkSite()#单例模式
4.自动生成url
在全局的ulrs.py 中
from django.conf.urls import url from stark.service import v1 urlpatterns = [ url(r'^stark/', v1.site.urls),#自己创建的类似admin的功能,需在app里有相应的注册 ]
在v1.py中,为每行需要操作的表生成增删改查4个url
class StarkConfig(object): def __init__(self,model_class,site): self.model_class = model_class self.site = site #装饰器,为了传参数request def wrap(self,view_func): def inner(request,*args,**kwargs): self.request=request return view_func(request,*args,**kwargs) return inner def get_urls(self):#第五步 app_model_name=(self.model_class._meta.app_label,self.model_class._meta.model_name,)#元祖(app名,表名) url_patterns=[ url(r'^$',self.wrap(self.changelist_view),name='%s_%s_changelist'%app_model_name), url(r'^add/$',self.wrap(self.add_view),name='%s_%s_add'%app_model_name), url(r'^(d+)/delete/$',self.wrap(self.delete_view),name='%s_%s_delete'%app_model_name), url(r'^(d+)/change/$',self.wrap(self.change_view),name='%s_%s_change'%app_model_name), ] url_patterns.extend(self.extra_url())#除增删改查外,想要自定义的新增的url return url_patterns#最后就得到了需要用到的一堆url def extra_url(self): return [] ######################################## @property def urls(self):#第四步 return self.get_urls() def urls(self): return self.get_urls() #########反向生成url##################### def get_change_url(self, nid): name = 'stark:%s_%s_change' % (self.model_class._meta.app_label, self.model_class._meta.model_name) edit_url = reverse(name, args=(nid,)) return edit_url def get_list_url(self): name = 'stark:%s_%s_changelist' % (self.model_class._meta.app_label, self.model_class._meta.model_name) edit_url = reverse(name) return edit_url def get_add_url(self): name = 'stark:%s_%s_add' % (self.model_class._meta.app_label, self.model_class._meta.model_name) edit_url = reverse(name) return edit_url def get_delete_url(self, nid): name = 'stark:%s_%s_delete' % (self.model_class._meta.app_label, self.model_class._meta.model_name) edit_url = reverse(name, args=(nid,)) return edit_url ######视图函数(之后会具体扩展)#### 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): ''' 单例模式创建的对象的类,是一个容器,用于放置处理请求对应关系 {model.UserInfo:StarkConfig(model.UserInfo,self)} ''' def __init__(self): self._registry = {} def register(self,model_class,stark_config_class=None): if not stark_config_class: #stark_config_class即29,没写那个派生类的时候默认给予StarkConfig stark_config_class=StarkConfig self._registry[model_class]=stark_config_class(model_class,self) #表名:stark_config_class(表名,self) def get_urls(self):#第三步,给url url_pattern=[] for model_class,stark_config_obj in self._registry.items():#去字典里取值 app_name=model_class._meta.app_label#app名 model_name=model_class._meta.model_name#表名 curd_url=url(r'^%s/%s/'%(app_name,model_name),(stark_config_obj.urls,None,None)) #拼接生成url,需执行stark_config_obj.urls———第四步 url_pattern.append(curd_url) return url_pattern @property def urls(self):#第二步,要url return (self.get_urls(),None,'stark') site=StarkSite()#第一步,单例模式
5.定制功能
定制每一个列表页面(查)都会有的功能,若不具备该功能可在派生类中修改。
这里用了分页功能,可在全局中创建文件夹utils,将自己写的分页器page.py放入其中,之后在v1.py中导入即可使用。
class StarkConfig(object): """ 用于为每个类(即每张表)生成url对应关系,并处理用户请求的基类 """ def __init__(self,model_class,site): self.model_class=model_class self.site=site self.request=None ############定制功能#################### #########1 默认每个tr都会拥有的td 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 '编辑操作' #url地址栏的搜索条件 query_str=self.request.GET.urlencode() if query_str: #重新构造<button class="btn btn-primary"></button> params=QueryDict(mutable=True) params[self._query_param_key]=query_str return mark_safe('<button class="btn btn-primary"><a href="%s?%s" style="color:white;">编辑</a></button>' %(self.get_change_url(obj.id),params.urlencode(),)) return mark_safe('<button class="btn btn-primary"><a href="%s" style="color:white;">编辑</a></button>' %(self.get_change_url(obj.id),)) def delete(self,obj=None,is_header=False): if is_header: return '删除操作' query_str = self.request.GET.urlencode() if query_str: # 重新构造 params = QueryDict(mutable=True) params[self._query_param_key] = query_str return mark_safe('<button class="btn btn-danger"><a href="%s?%s" style="color:white;">删除</a></button>' % (self.get_delete_url(obj.id), params.urlencode(),)) return mark_safe('<button class="btn btn-danger"><a href="%s" style="color:white;">删除</a></button>'%(self.get_delete_url(obj.id),) ) list_display=[] #得到派生类中自定义的list_display def get_list_display(self): data=[] if self.list_display:#派生类中定义的要显示的字段 data.extend(self.list_display)#加入到data中 data.append(StarkConfig.edit)#加入编辑td data.append(StarkConfig.delete)#加入删除td data.insert(0,StarkConfig.checkbox)#在最前面插一个td return data edit_link=[] def get_edit_link(self): result=[] if self.edit_link: result.extend(self.edit_link) return result ######### 2是否显示add按钮 show_add_btn = True # 默认显示 def get_show_add_btn(self): return self.show_add_btn #########3 关键字搜索 show_search_form = False#默认不显示 def get_show_search_form(self): return self.show_search_form search_fields = []#关键字默认为空 def get_search_fields(self): result = [] if self.search_fields: result.extend(self.search_fields)#派生类中自定义的关键字 return result def get_search_condition(self): key_word = self.request.GET.get(self.search_key)#'_q' search_fields = self.get_search_fields()#关键字 condition = Q()#创建Q对象用于与或 condition.connector = 'or'#搜索条件之间用或连接 if key_word and self.get_show_search_form(): for field_name in search_fields: condition.children.append((field_name, key_word)) return condition #############4 actions,批量功能,需拥有权限才可拥有此功能 show_actions = False#默认不显示 def get_show_actions(self): return self.show_actions actions = []#默认批量操作内容为空 def get_actions(self): result = [] if self.actions: result.extend(self.actions)#加入派生类中自定制的批量操作 return result #############5 组合搜索 show_comb_filter = False def get_show_comb_filter(self): return self.show_comb_filter comb_filter=[]#默认为空 def get_comb_filter(self): result=[] if self.comb_filter: result.extend(self.comb_filter)#得到派生类中的条件删选 return result #############6排序 order_by = [] def get_order_by(self): result = [] result.extend(self.order_by) return result
6.ChangeList
因为列表展示页面(查)需要后端向前端传很多的参数,所以我们这里讲所有需要传的参数封装到一个额外的类中,这样我们在视图函数中只需要将这个类实例化,只用向前端传这个类,在前端只要 ChangeList.方法或字段 就可以了
class ChangeList(object): ''' 很牛逼的一个类,封装了所有视图函数想要往前端传的内容 功能:使视图函数中的代码变的简洁 ''' def __init__(self,config,queryset): self.config=config#stark.py中写了派生类的话就是那个类,没写的话默认就是StarkConfig self.list_display=config.get_list_display() self.edit_link = config.get_edit_link() self.model_class=config.model_class#数据库的表 self.request=config.request#StarkConfig中默认是None,不过程序运行后就会有 self.show_add_btn=config.get_show_add_btn() # 搜索框 self.show_search_form = config.get_show_search_form() self.search_form_val = config.request.GET.get(config.search_key, '')#搜索关键字“_q”,首次访问网页默认是空 # 批量操作 self.actions=config.get_actions()#得到派生类中写的actions的内容[] self.show_actions=config.get_show_actions()#操作框 #组合搜索 self.show_comb_filter=config.get_show_comb_filter() self.comb_filter=config.get_comb_filter() from utils.pager import Pagination #分页器 current_page = self.request.GET.get('page', 1)#得到传入的page,没有默认为第一页 total_count = queryset.count()#要显示的数据的量,queryset在视图函数中有数据库操作的赋值 page_obj = Pagination(current_page, total_count, self.request.path_info, self.request.GET, per_page_count=5) #当前页 数据量 当前url不带问号 ?后面的条件内容 设定的每页显示的数据量条数 self.page_obj = page_obj#得到最终生成的分页器对象 self.data_list = queryset[page_obj.start:page_obj.end]#得到分页后的数据,用于页面展示 #批量操作 def modify_actions(self): result = []#批量操作内容,默认为空,去派生类中定义 for func in self.actions:#self.actions=config.get_actions(),默认为空 temp = {'name':func.__name__,'text':func.short_desc}#name是函数名,text是自加的描述 result.append(temp) return result def add_url(self):#添加操作的url query_str = self.request.GET.urlencode() if query_str: # 重新构造,用于跳转 params = QueryDict(mutable=True) params[self.config._query_param_key] = query_str return self.config.get_add_url()+'?'+params.urlencode() return self.config.get_add_url() def head_list(self): #构造表头 result = [] # [checkbox,'id','name',edit,del] 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.config, is_header=True)# 去派生类中执行 result.append(verbose_name) return result def body_list(self): # 处理表中的数据 data_list = self.data_list#self.data_list = queryset[page_obj.start:page_obj.end] new_data_list = [] for row in data_list: # row是 每一条数据对象UserInfo(id=2,name='alex2',age=181) temp = [] for field_name in self.list_display: if isinstance(field_name,str):#派生类中定义的显示字段 val = getattr(row,field_name) else:#每个td都拥有的功能,checkbox、edit、delete、 val = field_name(self.config,row) # 用于定制编辑列 if field_name in self.edit_link: val = self.edit_link_tag(row.pk, val) temp.append(val) new_data_list.append(temp) return new_data_list def gen_comb_filter(self): #生成器函数 """ [ FilterRow(((1,'男'),(2,'女'),)), FilterRow([obj,obj,obj,obj ]), FilterRow([obj,obj,obj,obj ]), ] """ ''' comb_filter = [ v1.FilterOption('gender', is_choice=True),#关键字传参,代表是choice v1.FilterOption('depart'),#, 筛选只显示id大于三的部门condition={'id__gt': 3} v1.FilterOption('roles', True),#True传入,代表是多选 ] ''' from django.db.models import ForeignKey,ManyToManyField for option in self.comb_filter: _field = self.model_class._meta.get_field(option.field_name)#字段 if isinstance(_field,ForeignKey): # 获取当前字段depart,关联的表 Department表并获取其所有数据 # print(field_name,_field.rel.to.objects.all()) row = FilterRow(option, option.get_queryset(_field), self.request) elif isinstance(_field,ManyToManyField): # print(field_name, _field.rel.to.objects.all()) # data_list.append( FilterRow(_field.rel.to.objects.all()) ) row = FilterRow(option,option.get_queryset(_field), self.request) else: # print(field_name,_field.choices) # data_list.append( FilterRow(_field.choices) ) row = FilterRow(option,option.get_choices(_field),self.request) # 可迭代对象,迭代详细在FilterRow的__iter__中 yield row def edit_link_tag(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,)) # /stark/app01/userinfo/
7.组合搜索(筛选)
组合搜索(筛选功能)是一大难点,这里还需定义两个类FilterOption和FilterRow
FilterOption:用于封装筛选条件的配置信息。FilterOption的实例化由派生类决定
FilterRow:可迭代对象,封装了筛选中的每一行数据。
class FilterOption(object): def __init__(self, field_name, multi=False, condition=None, is_choice=False,text_func_name=None, val_func_name=None): """ :param field_name: 字段 :param multi: 是否多选 :param condition: 显示数据的筛选条件 :param is_choice: 是否是choice """ self.field_name = field_name self.multi = multi self.condition = condition self.is_choice = is_choice self.text_func_name = text_func_name#组合搜索时,页面上生成显示的文本的函数 self.val_func_name = val_func_name#组合搜索时,页面上生成的a标签中的值的函数 def get_queryset(self, _field): if self.condition:#是数据的筛选条件 return _field.rel.to.objects.filter(**self.condition)#拿到筛选后的对象 return _field.rel.to.objects.all()#默认“全部”按钮被选中,给出所有对象 def get_choices(self, _field):#是choices return _field.choices #可迭代对象,封装了筛选中的每一行数据。 class FilterRow(object): def __init__(self, option, data, request): self.option = option self.data = data#关联字段所关联的表的所有有关联的数据 # request.GET self.request = request def __iter__(self): params = copy.deepcopy(self.request.GET)#深拷贝?后面的内容,得到QueryDict params._mutable = True#可修改 current_id = params.get(self.option.field_name) #params.get(字段),得到的是值 current_id_list = params.getlist(self.option.field_name) # [1,2,3] if self.option.field_name in params:#地址栏已存在筛选条件 # del params[self.option.field_name],先删除 origin_list = params.pop(self.option.field_name)#删除,并得到删除内容 url = "{0}?{1}".format(self.request.path_info, params.urlencode()) yield mark_safe('<a href="{0}">全部</a>'.format(url))#“全部按钮”不被选中 params.setlist(self.option.field_name, origin_list)#将本身已存在的筛选条件放入params中 else: url = "{0}?{1}".format(self.request.path_info, params.urlencode())#地址栏不存在筛选条件 yield mark_safe('<a class="active" href="{0}">全部</a>'.format(url))#默认“全部”按钮被选中 for val in self.data: if self.option.is_choice:# ( (1,男),(2,女) ) pk, text = str(val[0]), val[1] else:#每个val都是对象 # pk, text = str(val.pk), str(val) text = self.option.text_func_name(val) if self.option.text_func_name else str(val) pk = str(self.option.val_func_name(val)) if self.option.val_func_name else str(val.pk) # 当前URL?option.field_name # 当前URL?gender=pk #制定url的显示规则: # self.request.path_info # http://127.0.0.1:8005/arya/crm/customer/?gender=1&id=2 # self.request.GET['gender'] = 1 # &id=2gender=1 if not self.option.multi: # 单选 params[self.option.field_name] = pk#1,2 url = "{0}?{1}".format(self.request.path_info, params.urlencode()) if current_id == pk:#当前url筛选条件中的值 yield mark_safe("<a class='active' href='{0}'>{1}</a>".format(url, text))#该筛选按钮被选中 else: yield mark_safe("<a href='{0}'>{1}</a>".format(url, text))#其余按钮,没被选中 else: # 多选 current_id_list = ["1","2"] _params = copy.deepcopy(params) id_list = _params.getlist(self.option.field_name)#["1","2","3","4"] if pk in current_id_list:#值已存在,表示该按钮已被选中 id_list.remove(pk)#将该值从id_list中去除 _params.setlist(self.option.field_name, id_list)#["2","3","4"] url = "{0}?{1}".format(self.request.path_info, _params.urlencode()) #该按钮被选中,但其a标签的href中跳转的链接即当前url去除本身按钮id的状态,即该按钮,被再次点击时,就会恢复未选中状态 yield mark_safe("<a class='active' href='{0}'>{1}</a>".format(url, text)) else:#值未存在 id_list.append(pk) # params中被重新赋值 _params.setlist(self.option.field_name, id_list) # 创建URL,赋予其被点时,使其产生被选中 url = "{0}?{1}".format(self.request.path_info, _params.urlencode()) yield mark_safe("<a href='{0}'>{1}</a>".format(url, text))
8.popup功能
popup功能用于添加和编辑页面,在操作时选择外键关联的select框,可临时添加一个对象的功能
添加和编辑的视图函数中需要有以下代码:
def xxx_view(self,request): _popbackid = request.GET.get('_popbackid')#临时需要添加的外键字段所对应的输入框的id if request.method == 'POST': form = model_form_class(request.POST) if form.is_valid(): new_obj=form.save() if _popbackid: # 判断是否是来源于popup请求 # render一个页面,写自执行函数 # popUp('/stark/crm/userinfo/add/?_popbackid=id_consultant&model_name=customer&related_name=consultant') from django.db.models.fields.reverse_related import ManyToOneRel, ManyToManyRel result = {'status': False, 'id': None, 'text': None, 'popbackid': _popbackid} model_name = request.GET.get('model_name') # customer related_name = request.GET.get('related_name') # consultant, "None" for related_object in new_obj._meta.related_objects:#关联表的对象 _model_name = related_object.field.model._meta.model_name _related_name = related_object.related_name # 判断外键关联字段是否是主键id if (type(related_object) == ManyToOneRel): _field_name = related_object.field_name else: _field_name = 'pk' _limit_choices_to = related_object.limit_choices_to if model_name == _model_name and related_name == str(_related_name): is_exists = self.model_class.objects.filter(**_limit_choices_to, pk=new_obj.pk).exists() if is_exists: # 如果新创建的用户是可查看的人,页面才增加 # 分门别类做判断: result['status'] = True result['text'] = str(new_obj) result['id'] = getattr(new_obj, _field_name) return render(request, 'stark/popup_response.html', {'json_result': json.dumps(result, ensure_ascii=False)}) return render(request, 'stark/popup_response.html', {'json_result': json.dumps(result, ensure_ascii=False)}) else: list_query_str = request.GET.get(self._query_param_key) list_url = '%s?%s' % (self.get_list_url(), list_query_str,) return redirect(list_url) # return redirect(self.get_list_url()) return render(request, 'stark/add_view.html', {'form': form, 'config': self})
因为编辑和添加的前端页面代码大量冲重合,所以这里引入form.html和templatetags。
templatetages下创建change_form.py
from django.template import Library from django.urls import reverse from stark.service.v1 import site register = Library() # 自定义标签 @register.inclusion_tag('stark/form.html') def new_form(config,model_form_obj): new_form = [] for bfield in model_form_obj:#model_form_obj是每一条记录 temp = {'is_popup': False, 'item': bfield} # bfield.field是ModelForm读取对应的models.类,然后根据每一个数据库字段,生成Form的字段 from django.forms.boundfield import BoundField from django.db.models.query import QuerySet from django.forms.models import ModelChoiceField if isinstance(bfield.field, ModelChoiceField):#是单选和多选————>外键字段 related_class_name = bfield.field.queryset.model#得到字段的field if related_class_name in site._registry:#已注册 app_model_name = related_class_name._meta.app_label, related_class_name._meta.model_name # FK,One,M2M: 当前字段所在的类名和related_name model_name = config.model_class._meta.model_name related_name = config.model_class._meta.get_field(bfield.name).rel.related_name # print(model_name,related_name) base_url = reverse("stark:%s_%s_add" % app_model_name)#应用名_类名_add,反向生成url #bfield.auto_id是内置方法,得到该input框的id # 带有回调参数的url popurl = "%s?_popbackid=%s&model_name=%s&related_name=%s" % (base_url, bfield.auto_id,model_name,related_name) temp['is_popup'] = True temp['popup_url'] = popurl new_form.append(temp)#{'is_popup': True, 'item': bfield,'popup_url':popurl} return {'new_form':new_form}
templates下的form.html(内含jQuery,处理popup回调)
<form method="post" class="form-horizontal" novalidate> {% csrf_token %} {# dic={'is_popup': True, 'item': bfield,'popup_url':popurl}#} {% for dic in new_form %} <div class="col-sm-4 col-sm-offset-4"> <div class="form-group"> <label for="" class="col-sm-2 control-label">{{ dic.item.field.label }}:</label> <div class="col-sm-9" style="position: relative"> {# modelform自动形成input#} {{ dic.item }} {% if dic.is_popup %}{# 单选或多选#} <div style="position: absolute;top: 10px;left: 330px;"> <a href="" onclick="popUp('{{ dic.popup_url }}')"><i class="fa fa-arrows" aria-hidden="true"></i></a> </div> {% endif %} <div style="position: absolute;font-size: 12px;top: 18px;right: 20px;color: #e4393c;background: #FFEBEB;">{{ dic.item.errors.0 }}</div> </div> </div> </div> {% endfor %} <div class="col-sm-offset-7 col-sm-3"> <input type="submit" class="btn btn-primary" value="提交"> </div> </form> <script> function popUp(url) { var popupPage = window.open(url, url, "status=1, height:500, 600, toolbar=0, resizeable=0"); } function popupCallback(dic) { if (dic.status) { var op = document.createElement('option'); op.value = dic.id; op.text = dic.text; op.setAttribute('selected', 'selected'); document.getElementById(dic.popbackid).appendChild(op); } } </script>
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>正在关闭</title> </head> <body> <script> (function () { var dic = {{ json_result|safe }}; {# 发起popup请求的页面执行该回调函数#} opener.popupCallback(dic); window.close(); })() </script> </body> </html>
添加和修改的html页面将会在之后给出
9.视图函数
9.1 静态文件
创建一个static文件夹,用于存放静态文件。这里用到了bootstrap、font-awesome和jQuery
9.2 基板
因为增删改查的前端页面代码重复量较多,所以我们设置一个基板,用于减少代码冗余。
{% load staticfiles %} <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>{% block title %}{% endblock %}</title> <link rel="stylesheet" href="{% static 'bootstrap-3.3.7-dist/css/bootstrap.css' %}"> <link rel="stylesheet" href="{% static "mycss/stark-form.css" %}" /> <link rel="stylesheet" href="{% static 'font-awesome/css/font-awesome.min.css' %}"> {% block css %} {% endblock %} </head> <body> {% block body %} {% endblock %} {% block js %} {% endblock %} </body> </html>
9.3 查
注意组合搜索功能和批量功能
# 默认列表页面 def changelist_view(self, request,*args, **kwargs): #分页,已改写到ChangeList类中 # from utils.pager import Pagination # current_page=request.GET.get('page',1) # total_count=self.model_class.objects.all().count() # page_obj=Pagination(current_page,total_count,request.path_info,request.GET,per_page_count=4) if request.method=='GET': comb_condition = {}#筛选条件默认为空 option_list = self.get_comb_filter()#拿到派生类中定制的筛选条件 for key in request.GET.keys():#?后面的键 value_list = request.GET.getlist(key)#拿到键对应的值[1,2,3] flag = False for option in option_list:#option是每一个删选条件 if option.field_name == key:#该条件已存在于地址栏 flag = True break if flag: #comb_condition = {"id__in":[1,2,3].......} comb_condition["%s__in" % key] = value_list # 带搜索条件的数据,没有搜索条件的话就是全部数据。有筛选条件的话,还得处理成筛选后的数据 queryset=self.model_class.objects.filter(self.get_search_condition()).filter(**comb_condition).distinct() the_list=ChangeList(self,queryset)#封装好要向前端传的值 return render(request, 'stark/changelist.html', {'the_list':the_list}) elif request.method=='POST' and self.get_show_actions():#批量操作 func_name_str = request.POST.get('list_action')#前端传的操作name action_func = getattr(self, func_name_str)#反射,得到处理的方式 ret = action_func(request) if ret: return ret
{% extends 'stark/base_temp.html' %} {% block title %}列表页面{% endblock %} {% block css %} <style> h1 { margin-bottom: 50px; } td, th { text-align: center; } .list-filter a { display: inline-block; padding: 3px 6px; border: 1px solid #2e6da4; margin: 3px 0; } .list-filter a.active { background-color: #2e6da4; color: white; } </style> {% endblock %} {% block body %} <div class="container"> <div class="row"> <div class="col-md-10 col-md-offset-1"> <h1 class="text-center">列表页面</h1> {# 筛选栏#} {% if the_list.show_comb_filter %} <div class="list-filter"> {% for item in the_list.gen_comb_filter %} <div> {% for foo in item %} {{ foo }} {% endfor %} </div> {% endfor %} </div> {% endif %} {# 搜索栏#} {% if the_list.show_search_form %} <div class="form-group pull-right"> <form method="get"> <input name="{{ the_list.config.search_key }}" value="{{ the_list.search_form_val }}" class="form-control" placeholder="请输入搜索条件" type="text" style="display:inline-block; 200px;"/> <button class="btn btn-primary"><span class="glyphicon glyphicon-search"></span></button> </form> </div> {% endif %} {# 带有批量执行操作的表格#} <form method="post"> {% csrf_token %} {% if the_list.show_actions %} <div class="form-group"> <select name="list_action" class="form-control" style="display:inline-block; 200px;"> {% for item in the_list.modify_actions %} <option value="{{ item.name }}">{{ item.text }}</option> {% endfor %} </select> <button class="btn btn-danger">执行</button> </div> {% endif %} <table class="table table-bordered table-striped"> <thead> <tr> <th>编号</th> {% for item in the_list.head_list %} <th>{{ item }}</th> {% endfor %} </tr> </thead> <tbody> {% for obj in the_list.body_list %} <tr> <td><b>({{ forloop.counter }})</b></td> {% for col in obj %} <td>{{ col }}</td> {% endfor %} </tr> {% endfor %} </tbody> </table> </form> {# 添加按钮#} <div class="pull-left"> {% if the_list.show_add_btn %} <a href="{{ the_list.add_url }}" class="btn btn-info"> 添加 </a> {% endif %} </div> {# 分页器#} <div class="pager"> <nav aria-label="Page navigation"> <ul class="pagination"> {{ the_list.page_obj.page_html|safe }} </ul> </nav> </div> </div> </div> </div> {% endblock %}
9.4 modelform
modelform有两种写法
model_form_class = None def get_model_form_class(self): if self.model_form_class: return self.model_form_class from django.forms import ModelForm #方式一: # class TestModelForm(ModelForm): # class Meta: # model = self.model_class # fields = "__all__" # # error_messages = { # "__all__":{ # # }, # 'email': { # 'required': '', # 'invalid': '邮箱格式错误..', # } # } #方式二: type创建TestModelForm类 meta = type('Meta', (object,), {'model': self.model_class, 'fields': '__all__'}) TestModelForm = type('TestModelForm', (ModelForm,), {'Meta': meta}) return TestModelForm
9.5 增页面(含popup功能)
class StarkConfig(object): #增 def add_view(self, request, *args, **kwargs): # 添加页面 model_form_class = self.get_model_form_class()#根据modelform生成input _popbackid = request.GET.get('_popbackid')#临时需要添加的外键字段所对应的输入框的id if request.method == 'GET': form = model_form_class() else: form = model_form_class(request.POST) if form.is_valid(): new_obj=form.save() if _popbackid: # 判断是否是来源于popup请求 # render一个页面,写自执行函数 # popUp('/stark/crm/userinfo/add/?_popbackid=id_consultant&model_name=customer&related_name=consultant') from django.db.models.fields.reverse_related import ManyToOneRel, ManyToManyRel result = {'status': False, 'id': None, 'text': None, 'popbackid': _popbackid} model_name = request.GET.get('model_name') # customer related_name = request.GET.get('related_name') # consultant, "None" for related_object in new_obj._meta.related_objects:#关联表的对象 _model_name = related_object.field.model._meta.model_name _related_name = related_object.related_name # 判断外键关联字段是否是主键id if (type(related_object) == ManyToOneRel): _field_name = related_object.field_name else: _field_name = 'pk' _limit_choices_to = related_object.limit_choices_to if model_name == _model_name and related_name == str(_related_name): is_exists = self.model_class.objects.filter(**_limit_choices_to, pk=new_obj.pk).exists() if is_exists: # 如果新创建的用户是可查看的人,页面才增加 # 分门别类做判断: result['status'] = True result['text'] = str(new_obj) result['id'] = getattr(new_obj, _field_name) return render(request, 'stark/popup_response.html', {'json_result': json.dumps(result, ensure_ascii=False)}) return render(request, 'stark/popup_response.html', {'json_result': json.dumps(result, ensure_ascii=False)}) else: list_query_str = request.GET.get(self._query_param_key) list_url = '%s?%s' % (self.get_list_url(), list_query_str,) return redirect(list_url) # return redirect(self.get_list_url()) return render(request, 'stark/add_view.html', {'form': form, 'config': self})
{% extends 'stark/base_temp.html' %} {% load change_form %} {% block title %}添加页面{% endblock %} {% block css %}{% endblock %} {% block body %} <h1 class="text-center">添加页面</h1> <form method="post" novalidate> {% csrf_token %} {# {% include 'stark/form.html' %}#} {% new_form config form %}{# def new_form(model_form_obj):#} </form> {% endblock %}
9.6 改页面(含popup功能)
class StarkConfig(object): def change_view(self, request, nid,*args, **kwargs): # self.model_class.objects.filter(id=nid) obj = self.model_class.objects.filter(pk=nid).first() print(obj) if not obj: return redirect(self.get_list_url()) model_form_class = self.get_model_form_class() _popbackid = request.GET.get('_popbackid')#临时需要添加的外键字段所对应的输入框的id # GET,显示标签+默认值 if request.method == 'GET': form = model_form_class(instance=obj) return render(request, 'stark/change_view.html', {'form': form,'config': self}) else: form = model_form_class(instance=obj, data=request.POST) if form.is_valid(): form.save() list_query_str=request.GET.get(self._query_param_key) list_url='%s?%s'%(self.get_list_url(),list_query_str,) return redirect(list_url) return render(request, 'stark/change_view.html', {'form': form})
{% extends 'stark/base_temp.html' %} {% load change_form %} {% block title %}添加页面{% endblock %} {% block css %}{% endblock %} {% block body %} <h1 class="text-center">修改页面</h1> <form method="post" novalidate> {# {% include 'stark/form.html' %}#} {% new_form config form %}{# def new_form(model_form_obj):#} </form> {% endblock %}
9.7 删页面
删页面最为简单,但也要注意跳转功能,总不能在第三页点删除键后跳到了第一页吧
class StarkConfig(object): def delete_view(self, request, nid,*args, **kwargs): self.model_class.objects.filter(pk=nid).delete() list_query_str = request.GET.get(self._query_param_key) list_url = '%s?%s' % (self.get_list_url(), list_query_str,) return redirect(list_url)
end
至此,插件v1就已全部完成。在使用时,需要在app中导入这个文件,即创建一个stark.py文件,在文件中进行注册。
在stark.py中,只注册的话就执行默认功能,即只具备查、删、改功能。若想要拥有更多功能,需在stark.py中自己写一个派生类,利用钩子函数进行扩展。
举例说明:
这里进行对插件的使用的举例说明
我们创建一个新的app并建表,然后在app下创建stark.py,无需写路由和视图函数即可得到增删改查以及模糊搜索、批量操作、条件筛选、popup等功能。具体是否拥有该功能,由权限决定。
from django.db import models # Create your models here. class UserInfo(models.Model): name=models.CharField(max_length=32) pwd=models.CharField(max_length=32) email=models.EmailField(max_length=32) tp=models.ForeignKey('Type') class Meta: verbose_name_plural = "用户表" def __str__(self): return self.name class Type(models.Model): name=models.CharField(max_length=32) role=models.ManyToManyField('Role') class Meta: verbose_name_plural = "类型表" def __str__(self): return self.name class Role(models.Model): name=models.CharField(max_length=32) salary=models.IntegerField(default=0) class Meta: verbose_name_plural = "角色表" def __str__(self): return self.name class Host(models.Model): hostname = models.CharField(verbose_name='主机名',max_length=32) ip = models.GenericIPAddressField(verbose_name="IP",protocol='ipv4') port = models.IntegerField(verbose_name='端口')
print('我是stark') from django.shortcuts import HttpResponse,render,redirect from django.utils.safestring import mark_safe from django.conf.urls import url from stark.service import v1 from app01 import models from django.forms import ModelForm class UserInfoModelForm(ModelForm): class Meta: model = models.UserInfo fields = '__all__' error_messages = { 'name':{ 'required':'用户名不能为空' } } class UserinfoConfig(v1.StarkConfig): ''' 自己定义的派生类,可以有29种额外的显示方式,效果与admin相同 ''' list_display=['id','name','pwd','email'] def extra_url(self): url_list=[ #除增删改查外,想要新增的url ] return url_list show_add_btn = True model_form_class = UserInfoModelForm show_search_form = True#搜索框 search_fields = ['name__contains', 'email__contains']#模糊搜索 show_actions = True#批量操作框 #批量删除 def multi_del(self,request): pk_list = request.POST.getlist('pk')#得到所有的勾选的项 self.model_class.objects.filter(id__in=pk_list).delete() return HttpResponse('删除成功') # return redirect("http://www.baidu.com") multi_del.desc_text = "批量删除"#给函数内部加一个字段 def multi_init(self,request): pk_list = request.POST.getlist('pk') #self.model_class.objects.filter(id__in=pk_list).delete() # return HttpResponse('删除成功') #return redirect("http://www.baidu.com") multi_init.desc_text = "初始化" actions = [multi_del, multi_init]#给actions加入定制的功能 class HostModelForm(ModelForm): class Meta: model = models.Host fields = ['id','hostname','ip','port'] error_messages = { 'hostname':{ 'required':'主机名不能为空', }, 'ip':{ 'required': 'IP不能为空', 'invalid': 'IP格式错误', } } class HostConfig(v1.StarkConfig): def ip_port(self,obj=None,is_header=False): if is_header: return '自定义列' return "%s:%s" %(obj.ip,obj.port,) list_display = ['id','hostname','ip','port',ip_port] # get_list_display show_add_btn = True # get_show_add_btn model_form_class = HostModelForm # get_model_form_class def extra_url(self): urls = [ url('^report/$',self.report_view) ] return urls def report_view(self,request): return HttpResponse('自定义报表') def delete_view(self,request,nid,*args,**kwargs): if request.method == "GET": return render(request,'my_delete.html') else: self.model_class.objects.filter(pk=nid).delete() return redirect(self.get_list_url()) #相应的注册 #第二个参数传入自己写的类时,可以拥有自己写的类中的额外的方法 v1.site.register(models.UserInfo,UserinfoConfig) v1.site.register(models.Role) v1.site.register(models.Type) v1.site.register(models.Host,HostConfig)
附:
import copy import json from django.shortcuts import HttpResponse,render,redirect from django.urls import reverse from django.conf.urls import url, include from django.utils.safestring import mark_safe from django.http import QueryDict from django.db.models import Q #用于封装筛选条件的配置信息 class FilterOption(object): def __init__(self, field_name, multi=False, condition=None, is_choice=False,text_func_name=None, val_func_name=None): """ :param field_name: 字段 :param multi: 是否多选 :param condition: 显示数据的筛选条件 :param is_choice: 是否是choice """ self.field_name = field_name self.multi = multi self.condition = condition self.is_choice = is_choice self.text_func_name = text_func_name#组合搜索时,页面上生成显示的文本的函数 self.val_func_name = val_func_name#组合搜索时,页面上生成的a标签中的值的函数 def get_queryset(self, _field): if self.condition:#是数据的筛选条件 return _field.rel.to.objects.filter(**self.condition)#拿到筛选后的对象 return _field.rel.to.objects.all()#默认“全部”按钮被选中,给出所有对象 def get_choices(self, _field):#是choices return _field.choices #可迭代对象,封装了筛选中的每一行数据。 class FilterRow(object): def __init__(self, option, data, request): self.option = option self.data = data#关联字段所关联的表的所有有关联的数据 # request.GET self.request = request def __iter__(self): params = copy.deepcopy(self.request.GET)#深拷贝?后面的内容,得到QueryDict params._mutable = True#可修改 current_id = params.get(self.option.field_name) #params.get(字段),得到的是值 current_id_list = params.getlist(self.option.field_name) # [1,2,3] if self.option.field_name in params:#地址栏已存在筛选条件 # del params[self.option.field_name],先删除 origin_list = params.pop(self.option.field_name)#删除,并得到删除内容 url = "{0}?{1}".format(self.request.path_info, params.urlencode()) yield mark_safe('<a href="{0}">全部</a>'.format(url))#“全部按钮”不被选中 params.setlist(self.option.field_name, origin_list)#将本身已存在的筛选条件放入params中 else: url = "{0}?{1}".format(self.request.path_info, params.urlencode())#地址栏不存在筛选条件 yield mark_safe('<a class="active" href="{0}">全部</a>'.format(url))#默认“全部”按钮被选中 for val in self.data: if self.option.is_choice:# ( (1,男),(2,女) ) pk, text = str(val[0]), val[1] else:#每个val都是对象 # pk, text = str(val.pk), str(val) text = self.option.text_func_name(val) if self.option.text_func_name else str(val) pk = str(self.option.val_func_name(val)) if self.option.val_func_name else str(val.pk) # 当前URL?option.field_name # 当前URL?gender=pk #制定url的显示规则: # self.request.path_info # http://127.0.0.1:8005/arya/crm/customer/?gender=1&id=2 # self.request.GET['gender'] = 1 # &id=2gender=1 if not self.option.multi: # 单选 params[self.option.field_name] = pk#1,2 url = "{0}?{1}".format(self.request.path_info, params.urlencode()) if current_id == pk:#当前url筛选条件中的值 yield mark_safe("<a class='active' href='{0}'>{1}</a>".format(url, text))#该筛选按钮被选中 else: yield mark_safe("<a href='{0}'>{1}</a>".format(url, text))#其余按钮,没被选中 else: # 多选 current_id_list = ["1","2"] _params = copy.deepcopy(params) id_list = _params.getlist(self.option.field_name)#["1","2","3","4"] if pk in current_id_list:#值已存在,表示该按钮已被选中 id_list.remove(pk)#将该值从id_list中去除 _params.setlist(self.option.field_name, id_list)#["2","3","4"] url = "{0}?{1}".format(self.request.path_info, _params.urlencode()) #该按钮被选中,但其a标签的href中跳转的链接即当前url去除本身按钮id的状态,即该按钮,被再次点击时,就会恢复未选中状态 yield mark_safe("<a class='active' href='{0}'>{1}</a>".format(url, text)) else:#值未存在 id_list.append(pk) # params中被重新赋值 _params.setlist(self.option.field_name, id_list) # 创建URL,赋予其被点时,使其产生被选中 url = "{0}?{1}".format(self.request.path_info, _params.urlencode()) yield mark_safe("<a href='{0}'>{1}</a>".format(url, text)) class ChangeList(object): ''' 很牛逼的一个类,封装了所有视图函数想要往前端传的内容 功能:使视图函数中的代码变的简洁 ''' def __init__(self,config,queryset): self.config=config#stark.py中写了派生类的话就是那个类,没写的话默认就是StarkConfig self.list_display=config.get_list_display() self.edit_link = config.get_edit_link() self.model_class=config.model_class#数据库的表 self.request=config.request#StarkConfig中默认是None,不过程序运行后就会有 self.show_add_btn=config.get_show_add_btn() # 搜索框 self.show_search_form = config.get_show_search_form() self.search_form_val = config.request.GET.get(config.search_key, '')#搜索关键字“_q”,首次访问网页默认是空 # 批量操作 self.actions=config.get_actions()#得到派生类中写的actions的内容[] self.show_actions=config.get_show_actions()#操作框 #组合搜索 self.show_comb_filter=config.get_show_comb_filter() self.comb_filter=config.get_comb_filter() from utils.pager import Pagination #分页器 current_page = self.request.GET.get('page', 1)#得到传入的page,没有默认为第一页 total_count = queryset.count()#要显示的数据的量,queryset在视图函数中有数据库操作的赋值 page_obj = Pagination(current_page, total_count, self.request.path_info, self.request.GET, per_page_count=5) #当前页 数据量 当前url不带问号 ?后面的条件内容 设定的每页显示的数据量条数 self.page_obj = page_obj#得到最终生成的分页器对象 self.data_list = queryset[page_obj.start:page_obj.end]#得到分页后的数据,用于页面展示 #批量操作 def modify_actions(self): result = []#批量操作内容,默认为空,去派生类中定义 for func in self.actions:#self.actions=config.get_actions(),默认为空 temp = {'name':func.__name__,'text':func.short_desc}#name是函数名,text是自加的描述 result.append(temp) return result def add_url(self):#添加操作的url query_str = self.request.GET.urlencode() if query_str: # 重新构造,用于跳转 params = QueryDict(mutable=True) params[self.config._query_param_key] = query_str return self.config.get_add_url()+'?'+params.urlencode() return self.config.get_add_url() def head_list(self): #构造表头 result = [] # [checkbox,'id','name',edit,del] 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.config, is_header=True)# 去派生类中执行 result.append(verbose_name) return result def body_list(self): # 处理表中的数据 data_list = self.data_list#self.data_list = queryset[page_obj.start:page_obj.end] new_data_list = [] for row in data_list: # row是 每一条数据对象UserInfo(id=2,name='alex2',age=181) temp = [] for field_name in self.list_display: if isinstance(field_name,str):#派生类中定义的显示字段 val = getattr(row,field_name) else:#每个td都拥有的功能,checkbox、edit、delete、 val = field_name(self.config,row) # 用于定制编辑列 if field_name in self.edit_link: val = self.edit_link_tag(row.pk, val) temp.append(val) new_data_list.append(temp) return new_data_list def gen_comb_filter(self): #生成器函数 """ [ FilterRow(((1,'男'),(2,'女'),)), FilterRow([obj,obj,obj,obj ]), FilterRow([obj,obj,obj,obj ]), ] """ ''' comb_filter = [ v1.FilterOption('gender', is_choice=True),#关键字传参,代表是choice v1.FilterOption('depart'),#, 筛选只显示id大于三的部门condition={'id__gt': 3} v1.FilterOption('roles', True),#True传入,代表是多选 ] ''' from django.db.models import ForeignKey,ManyToManyField for option in self.comb_filter: _field = self.model_class._meta.get_field(option.field_name)#字段 if isinstance(_field,ForeignKey): # 获取当前字段depart,关联的表 Department表并获取其所有数据 # print(field_name,_field.rel.to.objects.all()) row = FilterRow(option, option.get_queryset(_field), self.request) elif isinstance(_field,ManyToManyField): # print(field_name, _field.rel.to.objects.all()) # data_list.append( FilterRow(_field.rel.to.objects.all()) ) row = FilterRow(option,option.get_queryset(_field), self.request) else: # print(field_name,_field.choices) # data_list.append( FilterRow(_field.choices) ) row = FilterRow(option,option.get_choices(_field),self.request) # 可迭代对象,迭代详细在FilterRow的__iter__中 yield row def edit_link_tag(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,)) # /stark/app01/userinfo/ class StarkConfig(object): """ 用于为每个类(即每张表)生成url对应关系,并处理用户请求的基类 """ def __init__(self,model_class,site): self.model_class=model_class self.site=site self.request=None self._query_param_key='_listfilter'#?后面的条件内容 self.search_key='_q'#搜索关键字 #####################################定制功能###################################### #########1 默认每个tr都会拥有的td 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 '编辑操作' #url地址栏的搜索条件 query_str=self.request.GET.urlencode() if query_str: #重新构造<button class="btn btn-primary"></button> params=QueryDict(mutable=True) params[self._query_param_key]=query_str return mark_safe('<button class="btn btn-primary"><a href="%s?%s" style="color:white;">编辑</a></button>' %(self.get_change_url(obj.id),params.urlencode(),)) return mark_safe('<button class="btn btn-primary"><a href="%s" style="color:white;">编辑</a></button>' %(self.get_change_url(obj.id),)) def delete(self,obj=None,is_header=False): if is_header: return '删除操作' query_str = self.request.GET.urlencode() if query_str: # 重新构造 params = QueryDict(mutable=True) params[self._query_param_key] = query_str return mark_safe('<button class="btn btn-danger"><a href="%s?%s" style="color:white;">删除</a></button>' % (self.get_delete_url(obj.id), params.urlencode(),)) return mark_safe('<button class="btn btn-danger"><a href="%s" style="color:white;">删除</a></button>'%(self.get_delete_url(obj.id),) ) list_display=[] #得到派生类中自定义的list_display def get_list_display(self): data=[] if self.list_display:#派生类中定义的要显示的字段 data.extend(self.list_display)#加入到data中 data.append(StarkConfig.edit)#加入编辑td data.append(StarkConfig.delete)#加入删除td data.insert(0,StarkConfig.checkbox)#在最前面插一个td return data edit_link=[] def get_edit_link(self): result=[] if self.edit_link: result.extend(self.edit_link) return result ######### 2是否显示add按钮 show_add_btn = True # 默认显示 def get_show_add_btn(self): return self.show_add_btn #########3 关键字搜索 show_search_form = False#默认不显示 def get_show_search_form(self): return self.show_search_form search_fields = []#关键字默认为空 def get_search_fields(self): result = [] if self.search_fields: result.extend(self.search_fields)#派生类中自定义的关键字 return result def get_search_condition(self): key_word = self.request.GET.get(self.search_key)#'_q' search_fields = self.get_search_fields()#关键字 condition = Q()#创建Q对象用于与或 condition.connector = 'or'#搜索条件之间用或连接 if key_word and self.get_show_search_form(): for field_name in search_fields: condition.children.append((field_name, key_word)) return condition #############4 actions show_actions = False#默认不显示 def get_show_actions(self): return self.show_actions actions = []#默认批量操作内容为空 def get_actions(self): result = [] if self.actions: result.extend(self.actions)#加入派生类中自定制的批量操作 return result #############5 组合搜索 show_comb_filter = False def get_show_comb_filter(self): return self.show_comb_filter comb_filter=[]#默认为空 def get_comb_filter(self): result=[] if self.comb_filter: result.extend(self.comb_filter)#得到派生类中的条件删选 return result #############6排序 order_by = [] def get_order_by(self): result = [] result.extend(self.order_by) return result ##################################访问相应网址时需要作数据处理的视图函数########################## # 默认列表页面 def changelist_view(self, request,*args, **kwargs): #分页,已改写到ChangeList类中 # from utils.pager import Pagination # current_page=request.GET.get('page',1) # total_count=self.model_class.objects.all().count() # page_obj=Pagination(current_page,total_count,request.path_info,request.GET,per_page_count=4) if request.method=='GET': comb_condition = {}#筛选条件默认为空 option_list = self.get_comb_filter()#拿到派生类中定制的筛选条件 for key in request.GET.keys():#?后面的键 value_list = request.GET.getlist(key)#拿到键对应的值[1,2,3] flag = False for option in option_list:#option是每一个删选条件 if option.field_name == key:#该条件已存在于地址栏 flag = True break if flag: #comb_condition = {"id__in":[1,2,3].......} comb_condition["%s__in" % key] = value_list # 带搜索条件的数据,没有搜索条件的话就是全部数据。有筛选条件的话,还得处理成筛选后的数据 queryset=self.model_class.objects.filter(self.get_search_condition()).filter(**comb_condition).distinct() the_list=ChangeList(self,queryset)#封装好要向前端传的值 return render(request, 'stark/changelist.html', {'the_list':the_list}) elif request.method=='POST' and self.get_show_actions():#批量操作 func_name_str = request.POST.get('list_action')#前端传的操作name action_func = getattr(self, func_name_str)#反射,得到处理的方式 ret = action_func(request) if ret: return ret # 一劳永逸的modelform model_form_class = None def get_model_form_class(self): if self.model_form_class: return self.model_form_class from django.forms import ModelForm # class TestModelForm(ModelForm): # class Meta: # model = self.model_class # fields = "__all__" # # error_messages = { # "__all__":{ # # }, # 'email': { # 'required': '', # 'invalid': '邮箱格式错误..', # } # } # type创建TestModelForm类 meta = type('Meta', (object,), {'model': self.model_class, 'fields': '__all__'}) TestModelForm = type('TestModelForm', (ModelForm,), {'Meta': meta}) return TestModelForm #增 def add_view(self, request, *args, **kwargs): # 添加页面 model_form_class = self.get_model_form_class()#根据modelform生成input _popbackid = request.GET.get('_popbackid')#临时需要添加的外键字段所对应的输入框的id if request.method == 'GET': form = model_form_class() else: form = model_form_class(request.POST) if form.is_valid(): new_obj=form.save() if _popbackid: # 判断是否是来源于popup请求 # render一个页面,写自执行函数 # popUp('/stark/crm/userinfo/add/?_popbackid=id_consultant&model_name=customer&related_name=consultant') from django.db.models.fields.reverse_related import ManyToOneRel, ManyToManyRel result = {'status': False, 'id': None, 'text': None, 'popbackid': _popbackid} model_name = request.GET.get('model_name') # customer related_name = request.GET.get('related_name') # consultant, "None" for related_object in new_obj._meta.related_objects:#关联表的对象 _model_name = related_object.field.model._meta.model_name _related_name = related_object.related_name # 判断外键关联字段是否是主键id if (type(related_object) == ManyToOneRel): _field_name = related_object.field_name else: _field_name = 'pk' _limit_choices_to = related_object.limit_choices_to if model_name == _model_name and related_name == str(_related_name): is_exists = self.model_class.objects.filter(**_limit_choices_to, pk=new_obj.pk).exists() if is_exists: # 如果新创建的用户是可查看的人,页面才增加 # 分门别类做判断: result['status'] = True result['text'] = str(new_obj) result['id'] = getattr(new_obj, _field_name) return render(request, 'stark/popup_response.html', {'json_result': json.dumps(result, ensure_ascii=False)}) return render(request, 'stark/popup_response.html', {'json_result': json.dumps(result, ensure_ascii=False)}) else: list_query_str = request.GET.get(self._query_param_key) list_url = '%s?%s' % (self.get_list_url(), list_query_str,) return redirect(list_url) # return redirect(self.get_list_url()) return render(request, 'stark/add_view.html', {'form': form, 'config': self}) #删 def delete_view(self, request, nid,*args, **kwargs): self.model_class.objects.filter(pk=nid).delete() list_query_str = request.GET.get(self._query_param_key) list_url = '%s?%s' % (self.get_list_url(), list_query_str,) return redirect(list_url) #改 def change_view(self, request, nid,*args, **kwargs): # self.model_class.objects.filter(id=nid) obj = self.model_class.objects.filter(pk=nid).first() print(obj) if not obj: return redirect(self.get_list_url()) model_form_class = self.get_model_form_class() _popbackid = request.GET.get('_popbackid')#临时需要添加的外键字段所对应的输入框的id # GET,显示标签+默认值 if request.method == 'GET': form = model_form_class(instance=obj) return render(request, 'stark/change_view.html', {'form': form,'config': self}) else: form = model_form_class(instance=obj, data=request.POST) if form.is_valid(): form.save() list_query_str=request.GET.get(self._query_param_key) list_url='%s?%s'%(self.get_list_url(),list_query_str,) return redirect(list_url) return render(request, 'stark/change_view.html', {'form': form}) ############################反向生成url########################################## def get_change_url(self, nid): name = 'stark:%s_%s_change' % (self.model_class._meta.app_label, self.model_class._meta.model_name) edit_url = reverse(name, args=(nid,)) return edit_url def get_list_url(self): name = 'stark:%s_%s_changelist' % (self.model_class._meta.app_label, self.model_class._meta.model_name) edit_url = reverse(name) return edit_url def get_add_url(self): name = 'stark:%s_%s_add' % (self.model_class._meta.app_label, self.model_class._meta.model_name) edit_url = reverse(name) return edit_url def get_delete_url(self, nid): name = 'stark:%s_%s_delete' % (self.model_class._meta.app_label, self.model_class._meta.model_name) edit_url = reverse(name, args=(nid,)) return edit_url ########################################################################################################## #装饰器,为了传参数request def wrap(self,view_func): def inner(request,*args,**kwargs): self.request=request return view_func(request,*args,**kwargs) return inner def get_urls(self):#第五步 app_model_name=(self.model_class._meta.app_label,self.model_class._meta.model_name,)#元祖(app名,表名) url_patterns=[ url(r'^$',self.wrap(self.changelist_view),name='%s_%s_changelist'%app_model_name), url(r'^add/$',self.wrap(self.add_view),name='%s_%s_add'%app_model_name), url(r'^(d+)/delete/$',self.wrap(self.delete_view),name='%s_%s_delete'%app_model_name), url(r'^(d+)/change/$',self.wrap(self.change_view),name='%s_%s_change'%app_model_name), ] url_patterns.extend(self.extra_url())#除增删改查外,想要自定义的新增的url return url_patterns#最后就得到了需要用到的一堆url def extra_url(self): return [] ############################################################################################# @property def urls(self):#第四步 return self.get_urls() ########传说中类与类之间的分界线############################################################################ class StarkSite(object): ''' 单例模式创建的对象的类,是一个容器,用于放置处理请求对应关系 {model.UserInfo:StarkConfig(model.UserInfo,self)} ''' def __init__(self): self._registry = {} def register(self,model_class,stark_config_class=None): if not stark_config_class: #stark_config_class即29,没写那个派生类的时候默认给予StarkConfig stark_config_class=StarkConfig self._registry[model_class]=stark_config_class(model_class,self) #表名:stark_config_class(表名,self) def get_urls(self):#第三步,给url url_pattern=[] for model_class,stark_config_obj in self._registry.items():#去字典里取值 app_name=model_class._meta.app_label#app名 model_name=model_class._meta.model_name#表名 curd_url=url(r'^%s/%s/'%(app_name,model_name),(stark_config_obj.urls,None,None)) #拼接生成url,需执行stark_config_obj.urls———第四步 url_pattern.append(curd_url) return url_pattern @property def urls(self):#第二步,要url return (self.get_urls(),None,'stark') site=StarkSite()#第一步,单例模式