Xadmin相關實現


一,保存搜索條件(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. 實現的一個基本思路:

  1. 首先,在默認的基礎配置類中一定要有一個可設置批量操作的屬性,並規定一個實現的方式,自定義的一個格式。
  2. 在后端做一個基礎的處理,如果用戶為配置該屬性,則默認實現批量刪除的功能,如果用戶自定義了,則優先顯示用戶自定義的屬性,再在最后展示默認的批量刪除操作
  3. 構建在前端的展示方式:使用select的下拉框展示option中value為批量操作的方法名(函數名)(備注:因為在前端中不能使用__name__這個屬性,所以在后端構建),文本內容為對這個方法的描述內容
  4. 將所有的有效控件放置在一個form表單中,發送post請求,(有效控件包括:select標簽中的option標簽:所有的批量操作的方法action,表單中的每行的checkbox,代表該行數據的pk值)
  5. 取到對應的方法名(字符串形式的)和對應的數據,通過反射執行該方法,進行相應批量操作
  6. 返回處理結果,或者直接展示當前頁面

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.過濾器實現的基本思路:

  1. 基礎配置類中配置過濾的相關屬性,以及適用方式(輸入要過濾的字段即可,一般輸入FK字段,M2M字段,其他字段沒有意義)
  2. 處理配置信息,構建展示方式(后端生成a標簽,實現保存搜索條件和相應條件對應的對象顯示突出,即用戶點那個那個條件變色)
  3. 接收過濾對象,后端進行篩選數據,同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.實現的基本思路:

  1. 如何在一對多、多對多或者一對一的字段后顯示添加按鈕
  2. 如何彈出一個窗口
  3. 如何將新窗口中添加的數據綁定到老窗口

基本思路的相關實現:如何辨別這個字段是一個普通的字段還是一個特殊的字段?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>
前端展示頁面

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM