仿造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, width: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;width: 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;width: 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()#第一步,單例模式