stark——pop功能(admin中添加功能)

1、pop功能須要實現的功能和問題

一、如何在一對多和多對多字段後渲染 +
二、+對應的跳轉路徑是什麼
三、保存添加記錄同時,將原頁面的對應的下拉菜單中添加該記錄javascript

  

2、window.open()方法詳解

  open() 方法用於打開一個新的瀏覽器窗口或查找一個已命名的窗口。html

一、window.open()支持環境:

  JavaScript1.0+/JScript1.0+/Nav2+/IE3+/Opera3+java

  重要事項:請不要混淆方法 Window.open() 與方法 Document.open(),這二者的功能徹底不一樣。爲了使您的代碼清楚明白,請使用 Window.open(),而不要使用 open()。python

二、基本語法: 

window.open(URL,name,features,replace)

  參數介紹:git

URL:一個可選的字符串,聲明瞭要在新窗口中顯示的文檔的 URL。若是省略了這個參數,或者它的值是空字符串,那麼新窗口就不會顯示任何文檔。
name:一個可選的字符串,該字符串是一個由逗號分隔的特徵列表,其中包括數字、字母和下劃線,該字符聲明瞭新窗口的名稱。這個名稱能夠用做標記 <a> 和 <form> 的屬性 target 的值。若是該參數指定了一個已經存在的窗口,那麼 open() 方法就再也不建立一個新窗口,而只是返回對指定窗口的引用。在這種狀況下,features 將被忽略。
features:一個可選的字符串,聲明瞭新窗口要顯示的標準瀏覽器的特徵。若是省略該參數,新窗口將具備全部標準特徵。在窗口特徵這個表格中,咱們對該字符串的格式進行了詳細的說明。
replace:一個可選的布爾值。規定了裝載到窗口的 URL 是在窗口的瀏覽歷史中建立一個新條目,仍是替換瀏覽歷史中的當前條目。支持下面的值:
        true - URL 替換瀏覽歷史中的當前條目。
        false - URL 在瀏覽歷史中建立新的條目。

三、窗口特徵(Window Features)

channelmode=yes|no|1|0	是否使用劇院模式顯示窗口。默認爲 no。
directories=yes|no|1|0	是否添加目錄按鈕。默認爲 yes。
fullscreen=yes|no|1|0	是否使用全屏模式顯示瀏覽器。默認是 no。處於全屏模式的窗口必須同時處於劇院模式。
height=pixels	窗口文檔顯示區的高度。以像素計。
left=pixels	窗口的 x 座標。以像素計。
location=yes|no|1|0	是否顯示地址字段。默認是 yes。
menubar=yes|no|1|0	是否顯示菜單欄。默認是 yes。
resizable=yes|no|1|0	窗口是否可調節尺寸。默認是 yes。
scrollbars=yes|no|1|0	是否顯示滾動條。默認是 yes。
status=yes|no|1|0	是否添加狀態欄。默認是 yes。
titlebar=yes|no|1|0	是否顯示標題欄。默認是 yes。
toolbar=yes|no|1|0	是否顯示瀏覽器的工具欄。默認是 yes。
top=pixels	窗口的 y 座標。
width=pixels	窗口的文檔顯示區的寬度。以像素計。

四、應用示例:

<body>
    <p><button class="add" onclick="foo()">+</button></p>
    <script>
        function foo() {
            window.open("/addbook/", "", "width=400,height=400,top=100,left=200")
        }
    </script>
</body>

3、在一對多和多對多字段後渲染 +

一、調整form.html模板樣式

<div class="container">
    <div class="row">
        <div class="col-md-6 col-md-offset-3">
            <form action="" method="post" novalidate>
                {% csrf_token %}
                {% for field in form %}
                    <div style="position: relative">
                        <label for="">{{ field.label }}</label>
                        {{ field }} <span class="error pull-right">{{ field.errors.0 }}</span>
                        <a href="" style="position: absolute; right: -30px; top: 20px;"><span style="font-size: 28px">+</span></a>
                    </div>
                {% endfor %}
                <button type="submit" class="btn btn-default pull-right">提交</button>
            </form>
        </div>
    </div>
</div>

注意:github

(1)絕對定位以父盒子爲參考點

  父輩元素設置相對定位,子元素設置絕對定位,那麼會以父輩元素左上角爲參考點。數據庫

  所以在這裏a標籤以父級盒子div標籤爲參考點。top屬性時,以父盒子左上角爲參考點調整位置。django

  

二、分析利用ModelForm組件構建的表單對象

  這個組件的功能就是把model和form組合起來。查看分析service/stark.py代碼以下:瀏覽器

class ModelStark(object):
    """默認類,定製配置類"""
    def get_modelform_class(self):
        """用來獲取modelform類"""
        if not self.modelform_class:
            # 若是沒有值
            from django.forms import ModelForm
            from django.forms import widgets as wid

            class ModelFormDemo(ModelForm):
                class Meta:
                    model = self.model
                    fields = "__all__"

            return ModelFormDemo
        else:
            # 若是有值說明在用戶已經本身定製過了,直接取值
            return self.modelform_class

    def add_view(self, request):
        """添加頁面視圖"""
        ModelFormDemo = self.get_modelform_class()
        form = ModelFormDemo()  # 實例化步驟提早不論是post請求仍是get請求都會傳遞到模板中

        if request.method == "POST":
            form = ModelFormDemo(request.POST)
            if form.is_valid():  # 校驗字段所有合格
                form.save()
                return redirect(self.get_list_url())  # 跳轉到當前訪問表的查看頁面
                # 校驗有錯誤返回頁面,且包含了錯誤信息

        return render(request, "add_view.html", locals())

  ModelFormDemo是ModelForm的子類,form是ModelFormDemo實例對象。分析這個form對象:app

for bound_field in form:   # 拿到每個字段
    # print(type(bound_field))   # <class 'django.forms.boundfield.BoundField'>  django封裝的數據
    # 經過這種方式查看這個數據類:from django.forms.boundfield import BoundField
    print(bound_field.field)
    print(type(bound_field.field))
    """
    <django.forms.fields.CharField object at 0x10d73a160>
    <class 'django.forms.fields.CharField'>
    <django.forms.fields.DateField object at 0x10d73a1d0>
    <class 'django.forms.fields.DateField'>
    <django.forms.fields.DecimalField object at 0x10d73a240>
    <class 'django.forms.fields.DecimalField'>
    <django.forms.models.ModelChoiceField object at 0x10d73a2b0>
    <class 'django.forms.models.ModelChoiceField'>
    <django.forms.models.ModelMultipleChoiceField object at 0x10d73a320>
    <class 'django.forms.models.ModelMultipleChoiceField'>
    """

  能夠看到它的類型是<class 'django.forms.boundfield.BoundField'>一種django的封裝數據。查看BoundField類可知它具備field屬性。

  能夠看出這個field屬性的類型是CharField、ModelChoiceField、ModelMultipleChoiceField等Django內置字段類型。

  在forms組件中,ChoiceField是負責渲染select標籤的;ModelChoiceField繼承ChoiceField經常使用於渲染一對多的select標籤;ModelMultipleChoiceField繼承ModelChoiceField經常使用於渲染多對多的select標籤。

三、調整僅給一對多和多對多字段添加 +

  service/stark.py:

class ModelStark(object):
    """默認類,定製配置類"""
    def add_view(self, request):
        """添加頁面視圖"""
        ModelFormDemo = self.get_modelform_class()
        form = ModelFormDemo()  # 實例化步驟提早不論是post請求仍是get請求都會傳遞到模板中

        for bound_field in form:   # 拿到每個字段
            print(bound_field.field)
            print(type(bound_field.field))
            from django.forms.models import ModelChoiceField  # ModelMultipleChoiceField繼承ModelChoiceField
            if isinstance(bound_field.field, ModelChoiceField):  # 經過這個判斷是不是一對多或多對多的字段對象
                bound_field.is_pop = True   # 給全部一對多、多對多對象添加is_pop這個屬性

        if request.method == "POST":
            form = ModelFormDemo(request.POST)
            if form.is_valid():  # 校驗字段所有合格
                form.save()
                return redirect(self.get_list_url())  # 跳轉到當前訪問表的查看頁面
                # 校驗有錯誤返回頁面,且包含了錯誤信息

        return render(request, "add_view.html", locals())

  注意:ModelMultipleChoiceField是ModelChoiceField的子類,所以只要引入ModelChoiceField,判斷bound_field.field是不是ModelChoiceField的對象就能夠判斷是不是一對一或一對多字段。其次經過bound_field.is_pop = True的方式爲bound_filed對象添加屬性。在form頁面中能夠經過判斷字段對象的is_pop屬性是否爲真判斷是否須要添加「+」。

  form.html:

-------代碼部分省略
<form action="" method="post" novalidate>
    {% csrf_token %}
    {% for field in form %}
        <div style="position: relative">
            <label for="">{{ field.label }}</label>
            {{ field }} <span class="error pull-right">{{ field.errors.0 }}</span>
            {% if field.is_pop %}
                {# 判斷是一對多、多對多字段 #}
                <a href="" style="position: absolute; right: -30px; top: 20px;"><span style="font-size: 28px">+</span></a>
            {% endif %}
        </div>
    {% endfor %}
    <button type="submit" class="btn btn-default pull-right">提交</button>
</form>

  顯示效果:

  

4、「+」對應的跳轉路徑

一、將「+」的a標籤href屬性換位onclick

  form.html

<div class="container">
    <div class="row">
        <div class="col-md-6 col-md-offset-3">
            <form action="" method="post" novalidate>
                {% csrf_token %}
                {% for field in form %}
                    <div style="position: relative">
                        <label for="">{{ field.label }}</label>
                        {{ field }} <span class="error pull-right">{{ field.errors.0 }}</span>
                        {% if field.is_pop %}
                            {# 判斷是一對多、多對多字段 #}
                            <a onclick="pop('{{ field.url }}')" style="position: absolute; right: -30px; top: 20px;"><span style="font-size: 28px">+</span></a>
                        {% endif %}
                    </div>
                {% endfor %}
                <button type="submit" class="btn btn-default pull-right">提交</button>
            </form>
        </div>
    </div>
</div>
<script>
    function pop(url) {
        window.open(url, "", "width=600, height=400, top=100, left=100")
    }
</script>

   注意window.open()方法的使用。注意{{ field.url }}拿到訪問路徑。

二、在add_view視圖函數中獲得訪問路徑url

class ModelStark(object):
    """默認類,定製配置類"""
    def add_view(self, request):
        """添加頁面視圖"""
        ModelFormDemo = self.get_modelform_class()
        form = ModelFormDemo()  # 實例化步驟提早不論是post請求仍是get請求都會傳遞到模板中

        for bound_field in form:   # 拿到每個字段
            # print(bound_field.field)  # 字段對象
            print(bound_field.name)   # title\publishDate\publish  字段名稱
            # print(type(bound_field.field))  # 字段類型
            from django.forms.models import ModelChoiceField  # ModelMultipleChoiceField繼承ModelChoiceField
            if isinstance(bound_field.field, ModelChoiceField):  # 經過這個判斷是不是一對多或多對多的字段對象
                bound_field.is_pop = True   # 給全部一對多、多對多對象添加is_pop這個屬性

                # 須要拿到的不是當前表而是字段關聯表
                print("===》", bound_field.field.queryset.model)
                """
                一對多或者多對多字段的關聯模型表
                <class 'app01.models.Publish'>  
                <class 'app01.models.Author'>
                """
                # 拿到模型名和應用名
                related_model_name = bound_field.field.queryset.model._meta.model_name
                related_app_label = bound_field.field.queryset.model._meta.app_label
                # 拼出添加頁面地址
                _url = reverse("%s_%s_add" % (related_app_label, related_model_name))
                bound_field.url = _url

        if request.method == "POST":
            form = ModelFormDemo(request.POST)
            if form.is_valid():  # 校驗字段所有合格
                form.save()
                return redirect(self.get_list_url())  # 跳轉到當前訪問表的查看頁面
                # 校驗有錯誤返回頁面,且包含了錯誤信息

        return render(request, "add_view.html", locals())

  注意:

(1)拿到一對多、多對多字段關聯表

  bound_field.field是字段對象;bound_filed.name拿到的是字段名稱:title、publishDate、publish、authors等字符串;

  bound_field.field是字段類型;bound_field.field.queryset.model拿到是字段關聯模型表:<class 'app01.models.Publish'> 、<class 'app01.models.Author'>。

  拿到模型表後,再經過._meta.model_name和._meta.app_label就能夠獲取模型名和應用名來拼接對應模型表的add頁面路徑。

(2)拼接添加頁面地址,讓form.html經過模板獲取

# 拼出添加頁面地址
_url = reverse("%s_%s_add" % (related_app_label, related_model_name))
bound_field.url = _url

######form.html#######
<a onclick="pop('{{ field.url }}')" style="position: absolute; right: -30px; top: 20px;"><span style="font-size: 28px">+</span></a>

(3)顯示效果

  

(4)__str__返回值改成字符串

  models.py:

class AuthorDetail(models.Model):
    nid = models.AutoField(primary_key=True)
    birthday = models.DateField()
    telephone = models.BigIntegerField()
    addr = models.CharField(max_length=64)

    def __str__(self):
        # 返回的不能是一個數字必定要強制轉爲一個字符串
        return str(self.telephone)

5、保存添加記錄同時,將原頁面的對應的下拉菜單中添加該記錄

一、get_body方法,field類型判斷調整

class ShowList(object):
    """展現頁面類"""
    def get_body(self):
        """構建表單數據"""
        new_data_list = []
        # for obj in self.data_list:
        for obj in self.page_data:   # 當前頁面的數據
            temp = []

            for field in self.config.new_list_display():  # ["__str__", ]   ["pk","name","age",edit]
                if callable(field):
                    val = field(self.config, obj)
                else:
                    try:    # 若是是普通字段
                        field_obj = self.config.model._meta.get_field(field)   # 拿到字段對象
                        if isinstance(field_obj, ManyToManyField):  # 判斷是不是多對多
                            # 反射處理  增長.all
                            # 多對多的狀況  obj.field.all()
                            ret = getattr(obj, field).all()  # <QuerySet [<Author: alex>, <Author: egon>]>
                            t = []
                            for obj in ret:
                                t.append(str(obj))
                            val = ",".join(t)   # 用join方法實現拼接   alex,egon
                        else:
                            # 非多對多的狀況
                            val = getattr(obj, field)   # 拿到的關聯對象  處理不了多對多
                            if field in self.config.list_display_links:
                                # _url = reverse("%s_%s_change" % (app_label, model_name), args=(obj.pk,))
                                _url = self.config.get_change_url(obj)
                                val = mark_safe("<a href='%s'>%s</a>" % (_url, val))
                    except Exception as e:   # 若是是__str__
                        val = getattr(obj, field)

                temp.append(val)
            new_data_list.append(temp)
        return new_data_list

  if callable()  判斷對象是否可調用,若是爲true說明是field是函數對象。當判斷爲false時,filed的值也分兩種狀況。一種是字段字符串,一種是「__str__」。若是是__str__狀況,程序會報錯(self.config.model._meta.get_field(field) 這句出錯),這裏經過try來作異常處理,經過反射拿到對象__str__函數的返回值 self.name 如:武漢大學出版社。

  顯示效果:

  

二、改寫add_view視圖POST請求處理

  改寫POST請求前,先修改add_view視圖中isinstance判斷是一對多、多對多對象後的bound_field.url:

# url拿到後,再在後面拼接字段名稱
bound_field.url = _url + "?pop_res_id=id_%s" % bound_field.name   # /?pop_res_id=id_authors

  以前的add_view視圖在處理POST請求時,若是字段校驗合格是直接頁面重定向到當前模型表的查看頁面。但如今須要區分兩種狀況:直接在頁面訪問添加頁面、經過window.open()打開網頁訪問添加頁面。

def add_view(self, request):
    """省略代碼"""
    if request.method == "POST":
        form = ModelFormDemo(request.POST)
        if form.is_valid():  # 校驗字段所有合格
            obj = form.save()   # 將數據保存到數據庫
            print(obj)   # 拿到返回值:當前生成的記錄
            pop_res_id = request.GET.get("pop_res_id")   # 拿到window.open打開頁面後面的get請求

            if pop_res_id:
                # 當屬於window.open頁面post請求
                res = {"pk": obj.pk, "text": str(obj), "pop_res_id": pop_res_id}

                return render(request, "pop.html", {"res": res})
            else:
                # 跳轉到當前訪問表的查看頁面
                return redirect(self.get_list_url())
                # 校驗有錯誤返回頁面,且包含了錯誤信息

    return render(request, "add_view.html", locals())

注意:

(1)pop_res_id判斷是不是window.open()打開窗口

  根據get請求數據(新的add頁面)中是否包含pop_res_id判斷是來自通常頁面訪問仍是來自window.open()打開的子頁面訪問。

(2)若是是window.open子頁面提交的post請求渲染pop.html

三、pop.html中的script腳本

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<script type="text/javascript">
    // opener 屬性是一個可讀可寫的屬性,可返回對建立該窗口的 Window 對象的引用。
    // 當使用window.open()打開一個窗口,您可使用此屬性返回來自目標窗口源(父)窗口的詳細信息。
    window.opener.pop_response('{{ res.pk }}','{{ res.text }}','{{ res.pop_res_id }}')
    // close() 方法用於關閉瀏覽器窗口
    window.close()
</script>
</body>
</html>

  執行pop.html中的兩條script腳本。

(1)window.opener 的用法

  返回打開當前窗口的那個窗口的引用。若是當前窗口是由另外一個窗口打開的, window.opener保留了那個窗口的引用. 若是當前窗口不是由其餘窗口打開的, 則該屬性返回 null.

  pop.html是由add_view.html中的pop函數打開的。所以會去add_view.html去找pop_response函數。

(2)window.close()方法

  該方法用於關閉瀏覽器窗口。

四、add_view.html的pop_response

<body>
<h3>添加頁面</h3>
{% include 'form.html' %}

<script>
    function pop(url) {
        window.open(url, "", "width=600, height=400, top=100, left=100")
    }

    function pop_response (pk, text, id) {
        console.log(pk, text, id);   // 10 人民郵電出版社 id_publish
        console.log(typeof text);  // string
        // 選擇哪個select標籤
        // option文本值和value值
        var $option = $('<option>');   // 建立標籤:<option></option>
        $option.html(text);      // 給標籤添加文本:<option>南京出版社</option>
        $option.val(pk);              // 給標籤添加value:<option value=111>南京出版社</option>
        $option.attr("selected", "selected");  // 添加屬性selected:<option value="111" selected="selected">南京出版社</option>
        $("#" + id).append($option);  // 將標籤添加到id="id_publish"的標籤中
    }
</script>
</body>

 注意:

(1)pop_response函數的三個參數

  這三個參數先由stark.py中的add_view視圖函數傳遞給pop.html模板

res = {"pk": obj.pk, "text": str(obj), "pop_res_id": pop_res_id}

  再在模板中經過模板語法將數據傳遞給pop_response函數:

window.opener.pop_response('{{ res.pk }}','{{ res.text }}','{{ res.pop_res_id }}')

   最終這三個參數分別是:當前關聯對象的主鍵值(11)、文本值(北京教育出版社)、get請求值(id_publish)

(2)建立option標籤並添加到對應的添加頁面select標籤中

  

五、運用pop添加顯示效果

  

6、項目代碼

  https://github.com/hqs2212586/stark_demo

相關文章
相關標籤/搜索