[oldboy-django][2深入django]Form总结

1 form总结

# Form数据格式验证
        - 原理:
            - 流程
            a.写类LoginForm(Form):
                字段名 = fields.xxFields() # 验证规则,本质是正则表达式(fields.xxFields()是一个正则表达式)
                字段名 = fields.xxFields() # 验证规则,本质是正则表达式
            b. obj = LoginForm(request.POST)
            c. 验证数据result = obj.is_valid()
            d. 拿到符合格式的数据 obj.cleaned_data
            e. 不符合格式,获取错误信息 obj.errors

        - Form提交数据验证程序(没有实现保留上次输入的值)
            # 前端
                <form action="/app02/login" method="POST">
                    {% csrf_token %}
                    <p>
                        <input type="text" name="user" placeholder="用户名">
                        <span style="color:red;">{{ error.user.0 }}</span>
                    </p>
                    <p>
                        <input type="password" name="pwd" placeholder="密码">
                        <span style="color:red;">{{ error.pwd.0 }}</span>
                    </p>
                    <p><input type="submit" value="提交"></p>
                </form>

            # LoginForm类
            class LoginForm(Form):
                user = fields.CharField(required=True,
                                        error_messages={
                                                'required': '不能为空',
                                            })
                pwd = fields.CharField(required=True,
                                       min_length=8,
                                       error_messages={
                                                'required': '不能为空',
                                                'min_length': '长度必须大于8'
                                            })


            # 视图
            def login(request):
                if request.method == 'GET':
                    return render(request, 'app02_login.html')
                else:
                    obj = LoginForm(request.POST)
                    # 检验提交数据是否符合规则
                    if obj.is_valid():
                        print(obj.cleaned_data)
                        # obj.cleaned_data是一个字典,form表单提交的数据
                        #{'password': 'aaaaaaaaaa', 'username': 'alexadfdda'}
                        return redirect('/app02/login')
                    else:
                        return render(request, 'app02_login.html', {'error': obj.errors})

# Form保留上一次输入数据
    - 原理
        b.Form提交,验证数据同时保留上次输入的值
        1.生成html标签操作
            - widget  # 选择input的类型,可为Textarea,select
            只要,前端:{{ obj.t1 }}或者 {{obj.as_p}}
                  视图:无论get还是Post都将obj传给前端

        2 利用上面的几个参数保留上次输入的内容
            a.原理: 利用Form组件可以生成标签
                GET:
                    obj = TestForm()
                    {{ obj.t1 }} 等效成 <input type="text" name="t1">
                POST:
                    obj = TestForm(request.POST)
                    {{ obj.t1}} 等效成<input type="text" name="t1" value = "你提交的数据">
                    {{ obj.errors.t1.0 }} 显示错误信息

                 总之, 前端:{{ obj.t1 }}或者 {{obj.as_p}}
                        视图:无论get还是Post都将obj传给前端
    - 实例

        # FormL类
        class TestForm(Form):
            t1 = fields.CharField(
                required=True,
                min_length=4,
                max_length=8,
                widget=widgets.TextInput,
                label='用户名',
                label_suffix=':',
                help_text='4-8个字符',
                initial='root'
            )
            t2 = fields.CharField(
                required=True,
                min_length=8,
                widget=widgets.PasswordInput,
                label='密码',
                label_suffix=':',
                initial='password'
            )
        #视图
            def test(request):
                if request.method == 'GET':
                    obj = TestForm()
                    return render(request, 'app02_test.html', {'obj': obj})
                else:
                    obj = TestForm(request.POST)
                    if obj.is_valid():
                        # 数据库添加数据
                        return redirect("/app02_index.html")
                    else:
                        return render(request, 'app02_test.html', {'obj': obj})

        # 前端
            <form action="/app02/test" method="POST" novalidate>
                {% csrf_token %}
                {{ obj.as_p }}
                <p><input type="submit" value="提交"></p>
            </form>

# ajax实现验证数据 + 保留上次输入的值
    - ajax提交(能验证数据 + 保留上次输入的值)
        # 模板
            <form action="/app02/ajax_login" method="POST" id="form1">
                {% csrf_token %}
                <p>
                    <input type="text" name="user" placeholder="用户名">
                    <span style="color:red;">{{ error.user.0 }}</span>
                </p>
                <p>
                    <input type="password" name="pwd" placeholder="密码">
                    <span style="color:red;">{{ error.pwd.0 }}</span>
                </p>
                <p><a onclick="submitForm();">ajax提交数据</a></p>
            </form>
            <script src="/static/js/jquery-1.12.4.js"></script>
            <script>
                function submitForm() {
                    $(".temp").remove();
                    $.ajax({
                        url: '/app02/ajax_login',
                        type: 'POST',
                        data: $('#form1').serialize(),
                        dataType: 'JSON',
                        success: function (arg) {
                            console.log(arg);
                            if(arg.status){

                            }else{
                                $.each(arg.msg, function (index, value) {
                                    tag = document.createElement('span');
                                    tag.innerHTML = value[0];
                                    tag.style.color = 'red';
                                    tag.className = "temp";
                                    $('#form1').find('input[name="' + index + '"]').after(tag);

                                })

                            }
                        }
                    })
                }
            </script>

        # 视图
        def ajax_login(request):
            if requset.method = 'GET':
                return render(request, 'ajax_login.html')
            else:
                ret = {'status': True, 'msg': None}
                obj = LoginForm(request.POST)
                if obj.is_valid():
                    print(obj.cleaned_data)
                else:
                    ret['status'] = False
                    ret['msg'] = obj.errors
                import json
                return HttpResponse(json.dumps(ret))
                # 可以返回render, 因为render实际就是调用HttpResponse


# Form生成html标签原理
    a. 通过Form生成Input输入框,Form标签,以及submit标签还是要在前端写的,
        但是Form标签内的Input标签可以在后台实现;只需要按以下步骤
        - views定义StudentForm(Form)类
        - views视图函数将Form实例化对象传递给前端
        - 前端{{ obj.段 }}即可

    b. 通过Form设置前端Input的type属性,即设置不同类型的输入框
        # 设置name为text, cls_id为下拉框
        class StudentForm(Form):
            name = fields.CharField(widget= widgets.InputText())
            cls_id = fields.IntegerField(widget = widgets.Select)

    c. 设置下拉框的内容choices属性
        class StudentForm(Form):
            cls_id = fields.IntegerField(
                widget=widgets.Select(choices=models.Classes.objects.values_list('id', 'title'))
            )
        注意: choices的值必须[元组,(), ()]类型
        widget=widgets.Select(choices=[(1, '上海'), (2,'北京')])

    d.设置input输入框的class属性 -- attrs
        name = fields.CharField(max_length=8, min_length=2,
                        widget=widgets.TextInput(attrs={'class': 'form-control'})
                        )
        cls_id = fields.IntegerField(
                widget=widgets.Select(
                                        choices=models.Classes.objects.values_list('id', 'title'),
                                        attrs={'class': 'form-control'}
                                    )
            )

        注意: attrs参数必须放在TextInput或者Select等内部,而且值必须为字典

#通过Form设置前端Input的默认显示值原理
        只要在视图函数将实例化一个Form对象,并且设置initial值即可,但对单选和多选有区别
        - 单选
            student_dict = models.Student.objects.filter(id=nid).values('name', 'age', 'email', 'cls_id').first()
            obj = StudentForm(initial=student_dict)
            # initial必须是字典
            return render(request, "a.html", {'obj': obj}

# 多对多时,如何将表单数据插入到数据库中
    - 新增老师的时候,如何将提交的数据插入到数据库两张表上(老师表和第三张表)
        class_list = obj.cleaned_data.pop('cls')
        # 将老师任教班级数据pop出来,此时cleaned_data= {'name': xxx}
        teacher = models.Teacher.objects.create(**obj.cleaned_data)
        teacher.cls.add(*class_list)

# 多对多时,页面如何显示第三张表的数据
    - 添加老师成功后,跳转到teachers页面,如何显示老师任教班级的数据(老师和班级的关联信息是放在第三张表app01_teacher_cls上)
        item.cls是一个objects对象,后面可以接values, all, values_list
                {% for item in teacher_list %}
                    <tr>
                        <td>{{ item.id }}</td>
                        <td>{{ item.name }}</td>
                        <td>
                            {% for x in item.cls.values_list %}
                                {{ x.1}}
                            {% endfor %}
                        </td>

                {% endfor %}


# BUG:页面刷新时,无法动态显示数据库内容
    分别打开添加老师(或者学生)的页面,和添加班级的页面, 然后再添加班级页面新添加一个班级。
    刷选添加老师(或者学生)页面,发现班级下拉框并没有动态增加刚才新增加的班级。

    原因分析:出现在class TeacherForm和StudentForm定义上,以TeacherForm为例
    class TeacherForm(Form):
        name = fields.CharField(max_length=16,
                                widget=widgets.TextInput(attrs={'class': 'form-control'})
                                )
        cls = fields.MultipleChoiceField(
            choices=models.Classes.objects.values_list('id', 'title'),
            widget=widgets.SelectMultiple(attrs={'class': 'form-control'})
        )

    在实例化一个TeacherForm对象时,由于name, cls为类变量,所以这两个类变量只要第一次生成后,
    后面实例化对象时,这两个变量是不会改变的。
    在调用父类init函数时,会将cls, name放到父类的self.fields里面
    self.fields = {'name': name, 'cls': cls}

    因此解决办法出来了,在每一次实例化对象时,再获取数据库的值给cls,
    重新刷新self.fields里面的cls字段内容
    class TeacherForm(Form):
        name = fields.CharField(max_length=16,
                                widget=widgets.TextInput(attrs={'class': 'form-control'})
                                )
        cls = fields.MultipleChoiceField(
            choices=models.Classes.objects.values_list('id', 'title'),
            # 多选这个可不能删,因为下面的init修改的不是这里
            widget=widgets.SelectMultiple(attrs={'class': 'form-control'})
        )

        def __init__(self, *args, **kwargs):
            super(TeacherForm, self).__init__(*args, **kwargs)
            self.fields['cls'].widget.choices = models.Classes.objects.values_list('id', 'title')


# form生成下拉框多选 + 动态刷新
    - 多选
        class TeacherForm(Form):
            name = fields.CharField(max_length=16,
                                    widget=widgets.TextInput(attrs={'class': 'form-control'})
                                    )
            cls = fields.MultipleChoiceField(
                choices=models.Classes.objects.values_list('id', 'title'),
                widget=widgets.SelectMultiple(attrs={'class': 'form-control'})
            )

            def __init__(self, *args, **kwargs):
                super(TeacherForm, self).__init__(*args, **kwargs)
                self.fields['cls'].choices = models.Classes.objects.values_list('id', 'title')

# form生成下拉框单选 + 动态刷新
    class StudentForm(Form):
        name = fields.CharField(max_length=16,
                                widget=widgets.TextInput(attrs={'class': 'form-control'})
                                )
        cls = fields.ChoiceField(
            choices=models.Classes.objects.values_list('id', 'title'),
            widget=widgets.Select(attrs={'class': 'form-control'})

        def __init__(self, *args, **kwargs):
            super(StudentForm, self).__init__(*args, **kwargs)
            self.fields['cls'].choices = models.Classes.objects.values_list('id', 'title')


# form设置下拉框多选的默认值
    - initial参数,值必须为字典。
        row = models.teacher.objects.filter(id=nid).first()
        obj = TeacherForm(initial={'name': row.name, 'cls': [1,2]})

        # 在渲染编辑页面的时候,将obj传递给前端即可
        # 前端 {{obj.name}} {{obj.cls}}

# form设置下拉框单选的默认值
    row = models.student.objects.filter(id=nid).first()
    obj = StudentForm(
        initial={
                'name': row.name,
                'age': row.age,
                'email': row.email,
                'cls_id': row.cls_id
        }

     字段太多,可以用另外一种比较好的方式:
     row_dict = models.student.objects.filter(id=nid).values('id','age','email','cls_id')
     obj = StudentForm(initial=row_dict)

# Form生成常见的标签
    class TestFieldForm(Form):
        t1 = fields.CharField(
            widget=widgets.TextInput
        )
        t2 = fields.CharField(widget=widgets.Textarea)

        # 结合起来就是,下拉框的单选, 单选用ChoiceField,
        # widget是下拉框,radio, checkbox等
        t3 = fields.ChoiceField(
            choices=[(1, ''), (2, '')],
            widget=widgets.Select()
        )

        # 下拉框的多选
        t4 = fields.MultipleChoiceField(
            choices=[(1, '篮球'), (2, '足球'), (3, '控球')],
            widget=widgets.SelectMultiple(attrs={})

        )

        # radio单选
        t5 = fields.ChoiceField(
            choices=[(1, ''), (2, '')],
            widget=widgets.RadioSelect)

        # checkbox多选
        t6 = fields.MultipleChoiceField(
            choices=[(1, '篮球'), (2, '足球'), (3, '控球')],
            widget=widgets.CheckboxSelectMultiple)

        # 上传文件类型
        t7 = fields.FileField(
            widget=widgets.FileInput
        )


    def test_fields(request):
        obj = TestFieldForm(initial={'t1': '姓名'})
        return render(request, 'app01_test_fields.html', {'obj': obj})

# Form扩展--自定义数据验证
    - RegxField
    - 字段 = xxField(validators=)
    - 钩子(增加一些高级验证)
        a clean_xx  -- 对特定的字段进行验证
        b clean     -- 对所有字段(或者两个或以上字段)进行验证

    - 实例
        # 自定义数据验证
        from django.core.exceptions import ValidationError
        class TestForm(Form):
            user = fields.CharField(
                widget=widgets.TextInput
            )
            pwd = fields.CharField(widget=widgets.PasswordInput)
            email = fields.EmailField()

            age = fields.IntegerField(min_value=18,max_value=30)

            def clean_user(self):
                # 增加验证用户名不能重复
                # 这里不能获取密码的值即不能self.cleaned_data['pwd']
                user_post = self.cleaned_data['user']
                print(user_post)
                if models.Student.objects.filter(name=user_post).count():
                    raise ValidationError('用户名已经存在')
                else:
                    return self.cleaned_data['user']

            def clean_pwd(self):
                # 也可以自定制其他的验证
                return self.cleaned_data['pwd']

            def clean(self):
                # 对字段间进行验证: 比如用户名和email不能重复
                # 到了这里不能说明已经通过clean_xx验证,所以要用get

                age_post = self.cleaned_data.get('age')
                print(age_post)
                email_post = self.cleaned_data.get('email')
                print(email_post)
                if models.Student.objects.filter(age=age_post, email=email_post).count():
                    raise ValidationError('年龄和邮箱组合已经存在', code='com')

                return self.cleaned_data
        # 视图
        def test(request):
            if request.method == 'GET':
                obj = TestForm()
                return render(request, 'app01_test.html',{'obj': obj} )
            else:
                obj = TestForm(request.POST)
                if obj.is_valid():
                    print('合格')
                else:
                    print('不合格')
                    com = obj.errors['__all__']
                return render(request, 'app01_test.html',{'obj': obj, 'com':com})

        # 前端
            <!DOCTYPE html>
                <html lang="en">
                <head>
                    <meta charset="UTF-8">
                    <title>Title</title>
                </head>
                <body>
                <form action="/app01/test" method="POST">
                    {% csrf_token %}
                    {{ obj.user }}{{ obj.errors.user.0 }}
                    {{ obj.pwd }} {{ obj.errors.pwd.0 }}
                    {{ obj.email }} {{ obj.errors.email.0 }}
                    {{ obj.age }} {{ obj.errors.age.0 }}
                    {{ com }}
                    <input type="submit" value="提交">
                </form>
                </body>
                </html>


总结:
    1 使用
        class FooForm(Form):
            xx = fields.xxFields()

            def clean_xx(self):

                return self.cleaned_data['xx']

            def clean()
                return self.cleaned_data
    2 页面展示
        {{ obj.xx}}

    3 后台
        表单渲染
            obj = FooForm(initial={'name':xx})
            return render(request, 'xx.html', {'obj': obj})
        表单提交
            obj = FooForm()
            if obj.is_valid():
                # 插入数据库
                return redirect()
            else:
                return render(request, 'xx.html', {'obj': obj})
View Code

2 补充自定义form里面的clean,__init__, clean_xx

def login_view(request):
    #http://127.0.0.1:8000/alice/login/?next=/p/
    redirect_to = request.POST.get("next", request.GET.get("next", ""))
    print "alice: redirect to: "; redirect_to
    
    if request.method == 'POST':
        form = LoginForm(request, data=request.POST)

        if form.is_valid():
            mobile = form.cleaned_data['mobile']
            #password = form.cleaned_data['password']
            profile = Profile.objects.get(mobile=mobile)
            login(request, form.get_user())
            #redirect_to = '/p/' #alice: TBD: str(profile.id)
            request.session['profile_id'] = profile.id
            if redirect_to != '':
                # Ensure the user-originating redirection url is safe.
                if not is_safe_url(url=redirect_to, host=request.get_host()):
                    redirect_to = resolve_url(settings.LOGIN_REDIRECT_URL)                
                return HttpResponseRedirect(redirect_to)
            else:
                return JsonResponse({'status':'1','id':profile.id, 'name':profile.personal_info.name})                
        else:
            #print(form.errors.as_json())
            json_str = form.errors.as_json()
            return JsonResponse({'status':'0', 'error': json_str})
            
    
    else:
        form = LoginForm()
        return render(request, 'registration/login.html', {'form':form, 'next':redirect_to})
View Code

3 wupeiqi form总结网站

http://www.cnblogs.com/wupeiqi/articles/6144178.html

4 推荐一个学习form的中文网站

http://www.yiibai.com/django/django_form_processing.html

原文地址:https://www.cnblogs.com/liuzhipenglove/p/7874004.html