一,保存搜索條件(Save search conditions)
kept conditions(保存條件)的應用場景比較常見,在我們查詢時,可以實現多條件的篩選查詢,比如:在淘寶上,選擇了其中的一個條件后,我們可以繼續選擇其他的一些過濾條件,然后就可以實現多條件的查詢。
那么代碼時怎么實現的呢?
我們在用戶提交查詢條件請求時,一般都是GET請求,在相關路徑后拼上?條件的形式,在Django中,我們在后端接收是通過request.GET這個屬性得到所有的條件,在Django中,request.GET是一個QueryDict類型的數據。<QueryDict: {}>
#訪問一個請求 print(request.GET,type(request.GET)) #<QueryDict: {}> <class 'django.http.request.QueryDict'>
我們點擊它的源碼,看看這個QueryDict的內部都做了些什么(from django.http.request import QueryDict)
class QueryDict(MultiValueDict): _mutable = True _encoding = None def __init__(self, query_string=None, mutable=False, encoding=None): super(QueryDict, self).__init__() if not encoding: encoding = settings.DEFAULT_CHARSET self.encoding = encoding query_string = query_string or '' self._mutable = mutable
這是截取的QueryDict中的一部分,在QueryDict中,有一個屬性_mutable,這個值是True時,表示QueryDict可以被修改,否則表示無法被修改,通過源碼可以看出,QueryDict在實例一個對象時,self._mutable是False,是不可修改的
而request.GET是一個QueryDict的實例,所以,request.GET是不可以被修改的
如果我們要修改或者添加request.GET中的值時,我們必須設置一個屬性值request.GET._mutable=True,這樣就可以修改了。
那我們如何保存搜索條件呢,要保存搜索條件,我們首先要得到用戶訪問時攜帶的條件,那這個條件怎么獲取呢?
在Django中,提供了一個方法用於或者請求的字符串格式的搜索條件,這個方法就是:request.GET.urlencode()
比如:
#我們訪問路徑:http://127.0.0.1:8000/Xadmin/demo/order/?id=1&page=2 #得到的是這個 print(request.GET,request.GET.urlencode()) <QueryDict: {'id': ['1'], 'page': ['2']}> id=1&page=2
通過這個方法我們可以把獲取的這個參數拼接到相應路徑后面。
如果我們對request.GET這個參數修改時,我們最好是copy一個同樣的數據,而不是在原數據上進行修改,這樣可以保證數據的安全性,因為在其他地方,我們肯定還會使用這個request.GET這個參數。
注意:在copy時,我們不需要設置_mutable=True這個參數就可以修改,因為Django已經為我們想到了這個情況,在QueryDict的源碼中,對這兩個copy有重寫,默認的參數就是mutable=True,所以我們使用copy后,可以直接對原數據修改。
def __copy__(self): result = self.__class__('', mutable=True, encoding=self.encoding) for key, value in six.iterlists(self): result.setlist(key, value) return result def __deepcopy__(self, memo): result = self.__class__('', mutable=True, encoding=self.encoding) memo[id(self)] = result for key, value in six.iterlists(self): result.setlist(copy.deepcopy(key, memo), copy.deepcopy(value, memo)) return result
所以,我們在取到這個值時,直接對這個值進行copy,然后在做修改
params = request.GET import copy params = copy.deepcopy(params) #可以直接對這個值做修改 params["要修改的值"] = value
二、搜索框功能的實現(search_fields)
相關實現的視圖函數部分
#配置類的實現代碼 from django.db.models import Q from django.shortcuts import HttpResponse, render # 配置類 class ModelXadmin(object): # 配置類中默認的搜索內容 search_fields = [] def __init__(self, model, site): self.model = model self.site = site self.model_name = "" self.app_name = "" # 將搜索的實現單獨的封裝到一個函數中 def get_search_condition(self, request): # 實例化一個Q對象 search_condition = Q() # 設置多條件搜索的關系為或 search_condition.connector = "or" # print("search_fields", self.search_fields) # ["title","price"] # 獲取輸入的搜索內容 key_word = request.GET.get('q') if key_word: # 對搜索內容判斷,如果為空,models.XXX.objects.filter()時 會報錯 for search_field in self.search_fields: search_condition.children.append((search_field + "__icontains", key_word)) return search_condition # 查看視圖函數 def list_view(self, request): """ self.model: 用戶訪問哪張表,self.model就是誰 data_list: 查看表下的數據 :param request: :return: """ search_condition = self.get_search_condition(request) data_list = self.model.objects.filter(search_condition) model_name = self.model._meta.model_name return render(request, 'list_view.html', locals())
備注:示例化Q對象值為空時,filter(Q對象) 等同於all()
相關實現的模板部分
{% if search_fields %} <form action="" class="form-inline"> <div style="margin-bottom: 20px;" class="input-group"> <input type="text" class="form-control" placeholder="請輸入內容" name="q"> <span class="input-group-btn"> <button class="btn btn-info" type="submit">搜索</button> </span> </div> </form> {% endif %}
三、批量操作的實現(actions)
批量操作中相關視圖函數的實現
1. 實現的一個基本思路:
- 首先,在默認的基礎配置類中一定要有一個可設置批量操作的屬性,並規定一個實現的方式,自定義的一個格式。
- 在后端做一個基礎的處理,如果用戶為配置該屬性,則默認實現批量刪除的功能,如果用戶自定義了,則優先顯示用戶自定義的屬性,再在最后展示默認的批量刪除操作
- 構建在前端的展示方式:使用select的下拉框展示option中value為批量操作的方法名(函數名)(備注:因為在前端中不能使用__name__這個屬性,所以在后端構建),文本內容為對這個方法的描述內容
- 將所有的有效控件放置在一個form表單中,發送post請求,(有效控件包括:select標簽中的option標簽:所有的批量操作的方法action,表單中的每行的checkbox,代表該行數據的pk值)
- 取到對應的方法名(字符串形式的)和對應的數據,通過反射執行該方法,進行相應批量操作
- 返回處理結果,或者直接展示當前頁面
2.具體實現代碼:
class ModelXadmin(object): actions=[] # 默認的批量刪除操作,默認出入兩個參數:1.request 2.包含所有執行批量操作數據的queryset集 def patch_delete(self,request,queryset): queryset.delete() # 方法的描述內容 patch_delete.short_description="批量刪除" # action的基礎處理 優先展示用戶的配置信息 def get_actions(self): temp=[] temp.extend(self.actions) temp.append(self.patch_delete) return temp def __init__(self,model,site): self.model=model self.site=site self.model_name="" self.app_name=""
構建展示方式:
class ShowList(object): def __init__(self,config,request,data_list): self.config=config self.data_list=data_list self.request=request # 封裝的展示類中 self.actions=self.config.get_actions() # 處理前端的展示信息 構建一個包含每一個action字典的列表 def new_actions(self): temp=[] for action in self.actions: temp.append({ "name":action.__name__, "desc":action.short_description }) return temp
批量操作中相關模板部分的實現
<form action="" method="post"> {% csrf_token %} <div> <select name="action" id="" class="form-control" style="display: inline-block;width: 300px"> <option value="">---------------</option> {% for action_dict in show_list.new_actions %} <option value="{{ action_dict.name }}">{{ action_dict.desc }}</option> {% endfor %} </select> <input type="submit" value="Go" class="btn btn-warning"> </div> <table class="table table-bordered table-striped"> <thead> <tr> {% for item in show_list.get_header %} <th>{{ item }}</th> {% endfor %} </tr> </thead> <tbody> {% for new_data in show_list.get_body %} <tr> {% for item in new_data %} <td>{{ item }}</td> {% endfor %} </tr> {% endfor %} </tbody> </table> </form>
發送post請求后,后端處理:
class ModelXadmin(object): def __init__(self, model, site): self.model = model self.site = site def list_view(self, request): """ self.model: 用戶訪問哪張表,self.model就是誰 data_list: 查看表下的數據 ShowList(self,data_list) # self: 當前查看表的配置類對象 :param request: :return: """ if request.method == "POST": # action action = request.POST.get("action") selected_pk_list = request.POST.getlist("selected") # 多選框時使用getlist方法接收所有的值 queryset = self.model.objects.filter(pk__in=selected_pk_list) # 過濾符合條件的所有的數據 action = getattr(self, action) # 通過反射獲取結果 ret = action(request, queryset) return ret
四、過濾器的實現(list_filter)
1.過濾器實現的基本思路:
- 基礎配置類中配置過濾的相關屬性,以及適用方式(輸入要過濾的字段即可,一般輸入FK字段,M2M字段,其他字段沒有意義)
- 處理配置信息,構建展示方式(后端生成a標簽,實現保存搜索條件和相應條件對應的對象顯示突出,即用戶點那個那個條件變色)
- 接收過濾對象,后端進行篩選數據,同search_fields
2.具體的代碼實現:
class ModelXadmin(object): list_filter=[]
構建展示內容:
from copy import deepcopy from django.utils.safestring import mark_safe class ShowList(object): def __init__(self,config,request,data_list): self.config=config self.data_list=data_list self.request=request # list_filter self.list_filter=self.config.list_filter # ["publish","auhtors] def get_filter_link_tags(self): # 構建的最終方式:link_tags={"publish":["a標簽","a標簽"],"author":["a標簽","a標簽"]} link_tags={} # 循環每一個過濾條件 for filter_field in self.list_filter: # ["publish","auhtors] # 保存過濾條件 不要對原數據修改 保證數據的安全性 params = deepcopy(self.request.GET) # {"authors":2} # 構建方式中a標簽的href是:?過濾字段名 = 選中對象的pk值 # 獲取當前選中的對象 注意:必須從原數據中取,原數據是不變的 deepcopy的數據是可變的 current_id=self.request.GET.get(filter_field) # print("current_id",current_id) # 獲取當前字段對象 filter_field_obj=self.config.model._meta.get_field(filter_field) # 獲取當前字段對象對應的所有的數據 related_data_list=filter_field_obj.rel.to.objects.all() temp=[] for obj in related_data_list: params[filter_field]=obj.pk # 保存搜索條件用 _url=params.urlencode() # 獲取字符串形式的參數 # 如果當前對象 = 搜索條件中的對象 做特殊展示 if current_id==str(obj.pk): # 備注:obj.pk 是數字類型,一定要做類型轉換 s="<a class='item' href='?%s'>%s</a>"%(_url,str(obj)) else: s = "<a href='?%s'>%s</a>" % (_url, str(obj)) temp.append(mark_safe(s)) link_tags[filter_field]=temp return link_tags
對用戶選擇的過濾條件做篩選處理
class ModelXadmin(object): def list_view(self, request): filter_condition = Q() for filter_field, val in request.GET.items(): # 報存搜索條件處理時,因為還會有其他的干擾條件,必須過濾掉,比如:分頁的page參數 搜索框的q參數等 if filter_field not in ["page", "q"]: filter_condition.children.append((filter_field, val)) data_list = self.model.objects.filter(filter_condition)
五、其他功能實現(展示字段的實現、展示字段可點擊跳轉的實現、增刪改查)
from django.shortcuts import HttpResponse,render,redirect from django.urls import reverse from django.utils.safestring import mark_safe class ShowList(object): def __init__(self,config,request,data_list): self.config=config self.data_list=data_list self.request=request # 分頁器組件相關配置 current_page=request.GET.get("page") all_count=self.data_list.count() pagination=Pagination(current_page,all_count,request.GET) self.pagination=pagination self.page_data_list=self.data_list[pagination.start:pagination.end] def get_header(self): # 處理表頭 # header_list=["ID","書籍名稱","出版社"] header_list = [] for field in self.config.new_list_display(): # [check,"nid","title","publish",edit,delete] if isinstance(field, str): if field == "__str__": val = self.config.model._meta.model_name.upper() else: field_obj = self.config.model._meta.get_field(field) val = field_obj.verbose_name else: val = field(self.config, is_header=True) # 獲取表頭,傳is_header=True header_list.append(val) return header_list def get_body(self): # 處理表單數據 new_data_list = [] for obj in self.page_data_list: # data_list [book_obj,book_obj2,...] temp = [] for field in self.config.new_list_display(): # ["nid","title","publish","authors"] if isinstance(field, str): try: from django.db.models.fields.related import ManyToManyField field_obj = self.config.model._meta.get_field(field) if isinstance(field_obj, ManyToManyField): t = [] for i in getattr(obj, field).all(): t.append(str(i)) val = ",".join(t) else: if field in self.config.list_display_link: edit_url = self.config.get_edit_url(obj) val = mark_safe("<a href='%s'>%s</a>" % (edit_url, getattr(obj, field))) else: val = getattr(obj, field) except Exception as e: val = getattr(obj, field) else: val = field(self.config, obj) temp.append(val) new_data_list.append(temp) ''' new_data_list=[ ["北京折疊",122,<a href=''>編輯</a>,<a href=''>刪除</a>], ["三體", 222,<a href=''>編輯</a>,<a href=''>刪除</a>], ] ''' return new_data_list class ModelXadmin(object): list_display=["__str__",] list_display_link=[] model_form_class=None def __init__(self,model,site): self.model=model self.site=site self.model_name="" self.app_name="" # 選擇按鈕 編輯 刪除 def edit(self, obj=None, is_header=False): if is_header: return "操作" _url=self.get_edit_url(obj) return mark_safe("<a href='%s'>編輯</a>" % _url) def delete(self, obj=None, is_header=False): if is_header: return "操作" _url=self.get_delete_url(obj) return mark_safe("<a href='%s'>刪除</a>"%_url) def checkbox(self, obj=None, is_header=False): if is_header: return "選擇" return mark_safe("<input type='checkbox' name='selected' value='%s'>"%obj.pk) # 反向解析當前表的增刪改查的url def get_edit_url(self,obj): # 反向解析:url url_name = "%s_%s_change" % (self.app_name, self.model_name) # http://127.0.0.1:8008/Xadmin/app01/book/(\d+)/change _url = reverse(url_name, args=(obj.pk,)) # return mark_safe("<a href='%s/change/'>編輯</a>"%obj.pk) return _url def get_list_url(self): # 反向解析:url url_name = "%s_%s_list" % (self.app_name, self.model_name) _url = reverse(url_name) return _url def get_add_url(self): # 反向解析:url url_name = "%s_%s_add" % (self.app_name, self.model_name) _url = reverse(url_name) return _url def get_delete_url(self,obj): # 反向解析:url url_name = "%s_%s_delete" % (self.app_name, self.model_name) _url = reverse(url_name, args=(obj.pk,)) return _url # 構建新的展示列表,默認加入選擇按鈕 編輯 刪除 def new_list_display(self): temp=[] temp.append(ModelXadmin.checkbox) temp.extend(self.list_display) if not self.list_display_link: temp.append(ModelXadmin.edit) temp.append(ModelXadmin.delete) return temp # 查看視圖函數 def list_view(self, request): """ self.model: 用戶訪問哪張表,self.model就是誰 data_list: 查看表下的數據 ShowList(self,data_list) # self: 當前查看表的配置類對象 :param request: :return: """ data_list = self.model.objects.all() show_list=ShowList(self,request,data_list) add_url = self.get_add_url() model_name = self.model._meta.model_name return render(request, 'list_view.html',locals()) # 獲取modelForm類 def get_model_form_class(self): if self.model_form_class: return self.model_form_class else: # 使用Django中ModelForm實現展示所有字段 from django.forms import ModelForm class DemoModelForm(ModelForm): class Meta: model=self.model fields="__all__" return DemoModelForm # 添加視圖函數 def add_view(self, request): DemoModelForm = self.get_model_form_class() if request.method=="POST": form=DemoModelForm(request.POST) if form.is_valid(): form.save() return redirect(self.get_list_url()) else: return render(request, 'add_view.html', locals()) form=DemoModelForm return render(request, 'add_view.html',locals()) # 編輯視圖函數 def change_view(self, request, id): edit_obj=self.model.objects.get(pk=id) DemoModelForm=self.get_model_form_class() if request.method=="POST": form=DemoModelForm(request.POST,instance=edit_obj) if form.is_valid(): form.save() return redirect(self.get_list_url()) else: return render(request, 'change_view.html', locals()) form=DemoModelForm(instance=edit_obj) return render(request, 'change_view.html',locals()) # 刪除視圖函數 def delete_view(self, request, id): list_url = self.get_list_url() if request.method=="POST": self.model.objects.filter(pk=id).delete() return redirect(list_url) return render(request, 'delete_view.html',{"list_url":list_url})
六、添加頁面中特殊字段的小窗口的動態添加的實現
1.實現的基本思路:
- 如何在一對多、多對多或者一對一的字段后顯示添加按鈕
- 如何彈出一個窗口
- 如何將新窗口中添加的數據綁定到老窗口
基本思路的相關實現:如何辨別這個字段是一個普通的字段還是一個特殊的字段?Django中我們可以循環每一個字段對象,判斷是不是這個類型的對象即可,以ModelForm為例,我們使用ModelForm示例一個對象,form_obj=ModelForm(),循環這個對象,一個元素就是一個字段對象,print(type(field)) 得到的是一個BoundField的對象:<class 'django.forms.boundfield.BoundField'>
from django.forms import ModelForm form_obj = ModelForm() for field in form_obj: print(type(field)) 結果為: <class 'django.forms.boundfield.BoundField'> <class 'django.forms.boundfield.BoundField'> <class 'django.forms.boundfield.BoundField'> <class 'django.forms.boundfield.BoundField'>
... # 每一個字段對象都是一個boundfield對象。
顯而易見,boundfield對原本的字段對象做了封裝 ,我們打開它的源碼,看看都有哪些可用的東西:
class BoundField(object): "A Field plus data" def __init__(self, form, field, name): self.form = form self.field = field self.name = name self.html_name = form.add_prefix(name) self.html_initial_name = form.add_initial_prefix(name) self.html_initial_id = form.add_initial_prefix(self.auto_id) if self.field.label is None: self.label = pretty_name(name) else: self.label = self.field.label self.help_text = field.help_text or '' @property def auto_id(self): """ Calculates and returns the ID attribute for this BoundField, if the associated Form has specified auto_id. Returns an empty string otherwise. """ auto_id = self.form.auto_id if auto_id and '%s' in force_text(auto_id): return force_text(auto_id) % self.html_name elif auto_id: return self.html_name return '' @property def id_for_label(self): """ Wrapper around the field widget's `id_for_label` method. Useful, for example, for focusing on this field regardless of whether it has a single widget or a MultiWidget. """ widget = self.field.widget id_ = widget.attrs.get('id') or self.auto_id return widget.id_for_label(id_)
這是截取的一段源碼:我們可以利用的有一個對象屬性:self.field = field,還有一個靜態方法:auto_id 它們有什么作用呢 ?print一下就知道了:
for boundfield in form_obj: print(boundfield.field) print(boundfield.auto_id) 結果為: <django.forms.fields.CharField object at 0x000002AED2E33BA8> id_title <django.forms.fields.CharField object at 0x000002AED2E33A58> id_desc <django.forms.models.ModelChoiceField object at 0x000002AED2E33B00> id_publish <django.forms.models.ModelMultipleChoiceField object at 0x000002AED2E33B38> id_authors
由此可見:boundfield.field 返回的是該字段的對象 boundfield.auto_id 返回一個 id_字段名 這種格式的字符串 也就是對應生成HTML標簽時的id值
回到正題,我們的目的是通過代碼過濾出這些特殊的一對多、多對多或者一對一字段,那怎么實現呢:直接判斷是不是這個類型,額外的一個知識點就是,這幾個特殊的字段在ModelForm中都是ModelChoiceField的對象,或者繼承這個類的對象,我們判斷是不是這個類的對象,就可以實現對這個的多慮,
代碼實現:
from django.forms.models import ModelChoiceField
for boundfield in form_obj: if isinstance(boundfield.field,ModelChoiceField): # 如果是這個類的對象,就給這個對象設置一個區分其他對象的屬性,比如: boundfield.is_field = True # obj.is_field=值
HTML的實現:
{% for field in form_obj %} <label for="">{{ field.label }}</label> {% if field.is_field %} <p>{{ field }} <span style="font-size:32px;font-weight: 500">+添加按鈕在此</span></p> {% else %} <p>{{ field }}</p> {% endif %} <span>{{ field.errors.0 }}</span> {% endfor %}
這樣我們就實現了第一步,那接下來第二步:打開一個窗口
基本思路:點擊添加按鈕,觸發一個打開一個新窗口的事件:
那么這個新窗口的路徑是什么?應該是我們點擊那個字段,就打開那個字段相對應的添加頁面,怎么實現呢?還是回到上面,我們循環每一個字段時,如果為特殊的字段,就獲取這個字段所對應的模型表,和所在的app,然后通過反向解析url,來或者當前這個字段的添加url,然后添加到這個對象的屬性中,直接在模板語言中獲取。
備注:這里有個小問題,那就是,如果我們沒有將當前字段所在的模型表注冊時,就不應該顯示可添加按鈕,同時,在反向解析url時,就會報錯,因為沒有注冊,就沒有這個模型類的相關url,也就沒有反向解析這個東西了。處理很簡單,加一個判斷,那怎么判斷?一個思路是,判斷當前字段所在的模型表,在不在注冊的字典中 site._registry,首先,怎么獲取當前字段所在模型表:一個知識點:取得字段對象,有一個queryset的屬性,這個queryset會獲取到當前字段在數據庫中所有的數據,在.model 就可以或者當前表,這個方法只對這些特殊字段可用!!!
for boundfield in form_obj: if isinstance(boundfield.field,ModelChoiceField): print(boundfield.field.queryset.model) 輸出結果為: <class 'blogs.models.Publisher'> <class 'blogs.models.Author'>
我們通過這個結果: ._meta.app_label 獲得當前字段所在app的字符串名, ._meta.model_name 得到當前字段所在的模型表的字符串名。
通過這個模型類,判斷在不在注冊的模型字典中:
視圖函數的相關實現:
for boundfield in form_obj: if isinstance(boundfield.field,ModelChoiceField):
# 如果在注冊表中,就添加屬性 if boundfield.field.queryset.model in site._registry: boundfield.is_field = True ret = boundfield.field.queryset.model._meta root = '%s_%s_add'%(ret.app_label,ret.model_name) root_url = reverse(root) boundfield.root_url = root_url+"?pop_back_id=%s"%boundfield.auto_id
# 拼接一個參數,用於將添加的數據添加到老窗口中,服務於第三步
HTML頁面的相關實現:
<form action="" method="post" novalidate> {% csrf_token %} {% for filed in form %} <div class="form-group" style="position: relative"> <label for="">{{ filed.label }}</label> {{ filed }} <span>{{ filed.errors.0 }}</span> {% if filed.is_field %} <a onclick="pop('{{ filed.root_url }}')"><span style="font-size: 22px;color: #1b6d85;
position: absolute;top: 27px;right:-23px">+</span></a> {% endif %} </div> {% endfor %} <input type="submit" class="btn btn-default"> </form> <script> function pop(url) { window.open(url,"","wdith=600,height=300,top=200,left=200") } </script>
第三步的實現:提交添加數據后,數據創建成功后我們可以獲取到一個新數據的obj,然后通過判斷路徑中有沒有參數pop_back_id對應的值,上面設置路徑時,添加的一個區分的一個標記。如果有值,表示是小窗口提交的值,否則表示正常的一個添加頁面,如果是正常的添加頁面,不做其他處理,如果是小窗口提交的數據,我們可以返回一個關閉窗口的HTML代碼,同時可以向老窗口傳參,將pk值,str(obj),以及是哪個字段發起的添加,就把數據添加到哪個數據下的標識:
視圖函數部分:
def add_view(self, request): DemoModelForm = self.get_model_form_class() if request.method=="POST": form=DemoModelForm(request.POST) if form.is_valid(): obj=form.save() pop_back_id=request.GET.get("pop_back_id") if pop_back_id: # 小窗口 text=str(obj) return render(request,"pop.html",locals()) else: # 大窗口 return redirect(self.get_list_url())
HTML部分:
pop.html 頁面:用於關閉這個小窗口,同時向老窗口傳值
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <script> window.opener.handle_pop("{{ obj.pk }}","{{ text }}","{{ pop_back_id }}"); window.close() </script> </body> </html>
原添加HTML頁面執行對應的添加數據函數
<script> function handle_pop(pk,text,pop_id) { var $option=$("<option>"); // <option></option> 創建一個option標簽 $option.html(text); // 添加文本值 $option.attr("value",pk); $option.attr("selected","selected"); // 添加默認選中的效果 $("#"+pop_id).append($option); // 將這個數據添加到對應的標簽中 } </script>
總結:
from django.conf.urls import url from django.shortcuts import HttpResponse,render,redirect from django.urls import reverse from django.utils.safestring import mark_safe from Xadmin.utils.page import Pagination from django.db.models import Q class ShowList(object): def __init__(self,config,request,data_list): self.config=config self.data_list=data_list self.request=request # 分頁器組件相關配置 current_page=request.GET.get("page") all_count=self.data_list.count() pagination=Pagination(current_page,all_count,request.GET) self.pagination=pagination self.page_data_list=self.data_list[pagination.start:pagination.end] self.actions=self.config.get_actions() # list_filter self.list_filter=self.config.list_filter # ["publish","auhtors] def get_filter_link_tags(self): #link_tags={"publish":["a","a"],"author":["a","a"]} link_tags={} from copy import deepcopy for filter_field in self.list_filter: # ["publish","auhtors] params = deepcopy(self.request.GET) # {"authors":2} current_id=self.request.GET.get(filter_field) print("current_id",current_id) filter_field_obj=self.config.model._meta.get_field(filter_field) related_data_list=filter_field_obj.rel.to.objects.all() temp=[] for obj in related_data_list: params[filter_field]=obj.pk _url=params.urlencode() if current_id==str(obj.pk): s="<a class='item' href='?%s'>%s</a>"%(_url,str(obj)) else: s = "<a href='?%s'>%s</a>" % (_url, str(obj)) temp.append(mark_safe(s)) link_tags[filter_field]=temp return link_tags def new_actions(self): temp=[] for action in self.actions: temp.append({ "name":action.__name__, "desc":action.short_description }) print("temp",temp) return temp def get_header(self): # 處理表頭 # header_list=["ID","書籍名稱","出版社"] header_list = [] for field in self.config.new_list_display(): # [check,"nid","title","publish",edit,delete] if isinstance(field, str): if field == "__str__": val = self.config.model._meta.model_name.upper() else: field_obj = self.config.model._meta.get_field(field) val = field_obj.verbose_name else: val = field(self.config, is_header=True) # 獲取表頭,傳is_header=True header_list.append(val) return header_list def get_body(self): # 處理表單數據 new_data_list = [] for obj in self.page_data_list: # data_list [book_obj,book_obj2,...] temp = [] for field in self.config.new_list_display(): # ["nid","title","publish","authors"] if isinstance(field, str): try: from django.db.models.fields.related import ManyToManyField field_obj = self.config.model._meta.get_field(field) if isinstance(field_obj, ManyToManyField): t = [] for i in getattr(obj, field).all(): t.append(str(i)) val = ",".join(t) else: if field in self.config.list_display_link: edit_url = self.config.get_edit_url(obj) val = mark_safe("<a href='%s'>%s</a>" % (edit_url, getattr(obj, field))) else: val = getattr(obj, field) except Exception as e: val = getattr(obj, field) else: val = field(self.config, obj) temp.append(val) new_data_list.append(temp) ''' new_data_list=[ ["北京折疊",122,<a href=''>編輯</a>,<a href=''>刪除</a>], ["三體", 222,<a href=''>編輯</a>,<a href=''>刪除</a>], ] ''' return new_data_list class ModelXadmin(object): list_display=["__str__",] list_display_link=[] search_fields=[] actions=[] list_filter=[] def patch_delete(self,request,queryset): queryset.delete() patch_delete.short_description="批量刪除" def get_actions(self): temp=[] temp.extend(self.actions) temp.append(self.patch_delete) return temp model_form_class=None def __init__(self,model,site): self.model=model self.site=site self.model_name="" self.app_name="" # 選擇按鈕 編輯 刪除 def edit(self, obj=None, is_header=False): if is_header: return "操作" _url=self.get_edit_url(obj) return mark_safe("<a href='%s'>編輯</a>" % _url) def delete(self, obj=None, is_header=False): if is_header: return "操作" _url=self.get_delete_url(obj) return mark_safe("<a href='%s'>刪除</a>"%_url) def checkbox(self, obj=None, is_header=False): if is_header: return "選擇" return mark_safe("<input type='checkbox' name='selected' value='%s'>"%obj.pk) # 反向解析當前表的增刪改查的url def get_edit_url(self,obj): # 反向解析:url url_name = "%s_%s_change" % (self.app_name, self.model_name) # http://127.0.0.1:8008/Xadmin/app01/book/(\d+)/change _url = reverse(url_name, args=(obj.pk,)) # return mark_safe("<a href='%s/change/'>編輯</a>"%obj.pk) return _url def get_list_url(self): # 反向解析:url url_name = "%s_%s_list" % (self.app_name, self.model_name) _url = reverse(url_name) return _url def get_add_url(self): # 反向解析:url url_name = "%s_%s_add" % (self.app_name, self.model_name) _url = reverse(url_name) return _url def get_delete_url(self,obj): # 反向解析:url url_name = "%s_%s_delete" % (self.app_name, self.model_name) _url = reverse(url_name, args=(obj.pk,)) return _url # 構建新的展示列表,默認加入選擇按鈕 編輯 刪除 def new_list_display(self): temp=[] temp.append(ModelXadmin.checkbox) temp.extend(self.list_display) if not self.list_display_link: temp.append(ModelXadmin.edit) temp.append(ModelXadmin.delete) return temp def get_search_condition(self,request): search_condition = Q() search_condition.connector = "or" print("search_fields", self.search_fields) # ["title","price"] key_word = request.GET.get('q') if key_word: for search_field in self.search_fields: search_condition.children.append((search_field + "__icontains", key_word)) return search_condition # 查看視圖函數 def list_view(self, request): """ self.model: 用戶訪問哪張表,self.model就是誰 data_list: 查看表下的數據 ShowList(self,data_list) # self: 當前查看表的配置類對象 :param request: :return: """ if request.method=="POST":# action print(request.POST) action=request.POST.get("action") selected_pk_list=request.POST.getlist("selected") queryset=self.model.objects.filter(pk__in=selected_pk_list) action=getattr(self,action) ret=action(request,queryset) return ret search_condition=self.get_search_condition(request) filter_condition=Q() for filter_field,val in request.GET.items(): if filter_field not in ["page","q"]: filter_condition.children.append((filter_field,val)) data_list = self.model.objects.filter(filter_condition).filter(search_condition) show_list=ShowList(self,request,data_list) add_url = self.get_add_url() model_name = self.model._meta.model_name return render(request, 'list_view.html', {"model_name":model_name,"add_url":add_url,"show_list":show_list}) # 獲取modelForm類 def get_model_form_class(self): if self.model_form_class: return self.model_form_class else: from django.forms import ModelForm class DemoModelForm(ModelForm): class Meta: model=self.model fields="__all__" return DemoModelForm # 添加視圖函數 def add_view(self, request): DemoModelForm = self.get_model_form_class() if request.method=="POST": form=DemoModelForm(request.POST) if form.is_valid(): form.save() return redirect(self.get_list_url()) else: return render(request, 'add_view.html', locals()) form=DemoModelForm return render(request, 'add_view.html',locals()) # 編輯視圖函數 def change_view(self, request, id): edit_obj=self.model.objects.get(pk=id) DemoModelForm=self.get_model_form_class() if request.method=="POST": form=DemoModelForm(request.POST,instance=edit_obj) if form.is_valid(): form.save() return redirect(self.get_list_url()) else: return render(request, 'change_view.html', locals()) form=DemoModelForm(instance=edit_obj) return render(request, 'change_view.html',locals()) # 刪除視圖函數 def delete_view(self, request, id): list_url = self.get_list_url() if request.method=="POST": self.model.objects.filter(pk=id).delete() return redirect(list_url) class Per(): def __init__(self): pass return render(request, 'delete_view.html',{"list_url":list_url}) def get_urls2(self): temp = [] self.app_name = self.model._meta.app_label # "app01" self.model_name = self.model._meta.model_name # "book" temp.append(url(r"^$", self.list_view,name="%s_%s_list"%(self.app_name,self.model_name))) temp.append(url(r"^add/$", self.add_view,name="%s_%s_add"%(self.app_name,self.model_name))) temp.append(url(r"^(\d+)/change/$", self.change_view,name="%s_%s_change"%(self.app_name,self.model_name))) temp.append(url(r"^(\d+)/delete/$", self.delete_view,name="%s_%s_delete"%(self.app_name,self.model_name))) return temp # 路由二級分發 @property def urls2(self): return self.get_urls2(), None, None class XadminSite(object): def __init__(self, name='admin'): self._registry = {} def get_urls(self): print(self._registry) # {Book:modelAdmin(Book),.......} temp = [] for model, admin_class_obj in self._registry.items(): # 獲取當前循環的model的字符串與所在app的字符串 app_name = model._meta.app_label # "app01" model_name = model._meta.model_name # "book" temp.append(url(r'^{0}/{1}/'.format(app_name, model_name),admin_class_obj.urls2), ) ''' url(r"app01/book",ModelXadmin(Book,site).urls2) url(r"app01/publish",ModelXadmin(Publish,site).urls2) url(r"app02/order",ModelXadmin(Order,site).urls2) ''' return temp @property def urls(self): return self.get_urls(),None,None def register(self, model, admin_class=None, **options): if not admin_class: admin_class = ModelXadmin self._registry[model] = admin_class(model, self) # {Book:ModelAdmin(Book),Publish:ModelAdmin(Publish)} site=XadminSite()
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <link rel="stylesheet" href="/static/bootstrap/css/bootstrap.min.css"> <link rel="stylesheet" href="/static/css/list_view.css"> </head> <body> <h3>查看{{ model_name }}數據</h3> <div class="container"> <div class="row"> <div class="col-md-9"> <a href="{{ add_url }}" class="btn btn-info add_btn">添加</a> {% if show_list.config.search_fields %} <form action="" class="pull-right" method="get"> <input style="display: inline-block;width: 300px" type="text" name="q" class="form-control"><input type="submit" value="search" class="btn btn-success"> </form> {% endif %} <form action="" method="post"> {% csrf_token %} <div> <select name="action" id="" class="form-control" style="display: inline-block;width: 300px"> <option value="">---------------</option> {% for action_dict in show_list.new_actions %} <option value="{{ action_dict.name }}">{{ action_dict.desc }}</option> {% endfor %} </select> <input type="submit" value="Go" class="btn btn-warning"> </div> <table class="table table-bordered table-striped"> <thead> <tr> {% for item in show_list.get_header %} <th>{{ item }}</th> {% endfor %} </tr> </thead> <tbody> {% for new_data in show_list.get_body %} <tr> {% for item in new_data %} <td>{{ item }}</td> {% endfor %} </tr> {% endfor %} </tbody> </table> </form> <div class="pull-right"> {{ show_list.pagination.page_html|safe }}</div> </div> <div class="col-md-3"> {% for filter_filed,link_tag_list in show_list.get_filter_link_tags.items %} <p>By {{ filter_filed.upper }}</p> {% for link_tag in link_tag_list %} <p>{{ link_tag }}</p> {% endfor %} {% endfor %} </div> </div> </div> </body> </html>
