[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})
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})
3 wupeiqi form总结网站
http://www.cnblogs.com/wupeiqi/articles/6144178.html
4 推荐一个学习form的中文网站
http://www.yiibai.com/django/django_form_processing.html