zoukankan      html  css  js  c++  java
  • stark——pop功能(admin中添加功能)

    一、pop功能需要实现的功能和问题

    1、如何在一对多和多对多字段后渲染 +
    2、+对应的跳转路径是什么
    3、保存添加记录同时,将原页面的对应的下拉菜单中添加该记录

      

    二、window.open()方法详解

      open() 方法用于打开一个新的浏览器窗口或查找一个已命名的窗口。

    1、window.open()支持环境:

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

      重要事项:请不要混淆方法 Window.open() 与方法 Document.open(),这两者的功能完全不同。为了使您的代码清楚明白,请使用 Window.open(),而不要使用 open()。

    2、基本语法: 

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

      参数介绍:

    URL:一个可选的字符串,声明了要在新窗口中显示的文档的 URL。如果省略了这个参数,或者它的值是空字符串,那么新窗口就不会显示任何文档。
    name:一个可选的字符串,该字符串是一个由逗号分隔的特征列表,其中包括数字、字母和下划线,该字符声明了新窗口的名称。这个名称可以用作标记 <a> 和 <form> 的属性 target 的值。如果该参数指定了一个已经存在的窗口,那么 open() 方法就不再创建一个新窗口,而只是返回对指定窗口的引用。在这种情况下,features 将被忽略。
    features:一个可选的字符串,声明了新窗口要显示的标准浏览器的特征。如果省略该参数,新窗口将具有所有标准特征。在窗口特征这个表格中,我们对该字符串的格式进行了详细的说明。
    replace:一个可选的布尔值。规定了装载到窗口的 URL 是在窗口的浏览历史中创建一个新条目,还是替换浏览历史中的当前条目。支持下面的值:
            true - URL 替换浏览历史中的当前条目。
            false - URL 在浏览历史中创建新的条目。
    

    3、窗口特征(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	窗口的文档显示区的宽度。以像素计。
    

    4、应用示例:

    <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>
    

    三、在一对多和多对多字段后渲染 +

    1、调整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>
    

    注意:

    (1)绝对定位以父盒子为参考点

      父辈元素设置相对定位,子元素设置绝对定位,那么会以父辈元素左上角为参考点。

      因此在这里a标签以父级盒子div标签为参考点。top属性时,以父盒子左上角为参考点调整位置。

      

    2、分析利用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对象:

    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标签。

    3、调整仅给一对多和多对多字段添加 +

      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>
    

      显示效果:

      

    四、“+”对应的跳转路径

    1、将“+”的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 }}拿到访问路径。

    2、在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)   # titlepublishDatepublish  字段名称
                # 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)
    

    五、保存添加记录同时,将原页面的对应的下拉菜单中添加该记录

    1、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 如:武汉大学出版社。

      显示效果:

      

    2、改写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

    3、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()方法

      该方法用于关闭浏览器窗口。

    4、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标签中

      

    5、运用pop添加显示效果

      

    六、项目代码

      https://github.com/hqs2212586/stark_demo

  • 相关阅读:
    P1121 环状最大两段子段和
    无题
    cdoj 1485 柱爷搞子串 sam treap
    自然数幂和
    Gym 100341C AVL Trees NTT
    线性筛分解质因子
    codeforces 366 Ant Man dp
    UVALive 6914 Maze Mayhem 轮廓线dp
    hdu 5790 Prefix 字典树 主席树
    莫比乌斯反演个人小结
  • 原文地址:https://www.cnblogs.com/xiugeng/p/9532592.html
Copyright © 2011-2022 走看看