• 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

  • 相关阅读:
    免费素材:推荐最新的免费图标字体Sosa
    如何选择Javascript模板引擎(javascript template engine)?
    名片设计最佳实践和技巧分享
    帮助你生成翻页效果的jQuery插件 bookblock
    网页网站收集
    main xml信息
    c# winrar 打包
    c# 压缩文件
    前端工程师
    fash 3D 游戏
  • 原文地址:https://www.cnblogs.com/xiugeng/p/9532592.html
Copyright © 2020-2023  润新知