Form组件认证

 能够帮助我们做用户认证。

以前写我们自己写用户认证的时候,我们自己写HTML的form表单,点击提交,数据就被发送到后台,后台进行验证。在验证过程中我们就需要自己去写正则表达式去匹配相关的数据信息。

但是

django中有一个叫做form组件的东西,他是专门做这个验证的,我们可以利用form创造一个模板,接下来如果请求发过来,我们把request.post里面的东西发给这个模板,那这个模板就能够一个一个的进行请求验证。他会产生三个结果:是否验证成功,所有的正确信息,所有的错误信息。

那这个神奇的form模板是什么?怎么创建的呢?

 1 from django import forms
 2 class FM(forms.Form):
 3     #字段名必须要和HTML中的name值一样,否则拿不到数据
 4     user = forms.CharField()
 5     pwd = forms.CharField()
 6     email = forms.EmailField()
 7 
 8 
 9 def fm(request):
10     if request.method == 'GET':
11         return render(request,'fm.html')
12     elif request.method == 'POST':
13         obj = FM(request.POST)
14         r1 = obj.is_valid()
15         if r1:
16             print(obj.cleaned_data)
17         else:
18             print(obj.errors.as_json())
19         return redirect('/fm/')

首先我们要创建一个类,这个类要继承form.Form。然后在里面接受数据做判断。注意:字段名要和html中的name值一样,否则拿不到数据

在form 验证的函数中,我们要传入的就是request.POST,用is_valid()就可以接收返回的结果,obj.cleaned_data返回的是正确的结果,obj.errors是返回错误的结果。as_json()设置返回数据以json形式返回

那我们看错误信息返回的都是英文,我们能不能定义一下错误信息的形式呢?

1 class FM(forms.Form):
2     #字段名必须要和HTML中的name值一样,否则拿不到数据
3     user = forms.CharField(error_messages={'required':'用户名不能为空'})
4     pwd = forms.CharField(
5         max_length=12,
6         min_length=6,
7         error_messages={'required':'用户名不能为空','min_length':'密码长度不能小于6','max_length':'密码长度不能大于12'}
8     )
9     email = forms.EmailField(error_messages={'required':'邮箱不能为空','invalid':'邮箱格式错误'})

 

这样就显示了我们也想要的错误信息。

那我们可以把这些显示错误信息传到前端:(通过字典的形式去拿)

return render(request, 'fm.html',{'obj':obj})
1 <form action="/fm/" method="post">
2         {% csrf_token %}
3         <p>用户名:<input type="text" name="user" >{{ obj.errors.user.0 }}</p>
4         <p>密码:<input type="password" name="pwd" >{{ obj.errors.pwd.0 }}</p>
5         <p>邮箱:<input type="text" name="email" >{{ obj.errors.email.0 }}</p>
6         <input type="submit" value="提交" >
7     </form>

 这样就能够显示出错误信息

 

但是现在还有一个问题是,点击了提交之后,错误信息是显示出来了,但是input里面的数据不见了。那怎么解决呢?

其实django不光能够生成这个错误,还能够生成html:

<form action="/fm/" method="post">
        {% csrf_token %}
        <p>用户名:{{ obj.user }}{{ obj.errors.user.0 }}</p>
        <p>密码:{{ obj.pwd }}{{ obj.errors.pwd.0 }}</p>
        <p>邮箱:{{ obj.email }}{{ obj.errors.email.0 }}</p>
        <input type="submit" value="提交" >
</form>

views.py:

 1 from app01 import models
 2 def fm(request):
 3     if request.method == 'GET':
 4         obj = FM()
 5         return render(request,'fm.html',{'obj':obj})
 6     elif request.method == 'POST':
 7         obj = FM(request.POST)
 8         r1 = obj.is_valid()
 9         if r1:
10             models.UserInfo.objects.create(**obj.cleaned_data)
11         else:
12             print(obj.errors)
13             return render(request, 'fm.html',{'obj':obj})

只要我们在get的时候也生成一个对象,这时候不需要传入什么数据,然后把这个对象传过去就可以了。最后我们如果验证成功了,直接create(**obj.cleaned_data)就可以了。

当然全段的生成还有一些简单的方法:as_p,as_ul,as_table

1 <form action="/fm/" method="post">
2         {% csrf_token %}
3         {{ obj.as_p }}
4         <input type="submit" value="提交" >
5 </form>

1 <form action="/fm/" method="post">
2         {% csrf_token %}
3         {{ obj.as_ul }}
4         <input type="submit" value="提交" >
5 </form>

1 <form action="/fm/" method="post">
2         {% csrf_token %}
3         <table>
4             {{ obj.as_table }}
5         </table>
6         <input type="submit" value="提交" >
7 </form>

但是这种方式,虽然方便,但是可定制性不高!

下面我们来看看怎么样设置form里面的样式:

 1 from django import forms
 2 from django.forms import widgets
 3 from django.forms import fields
 4 class FM(forms.Form):
 5     #字段本身只做验证
 6     #字段名必须要和HTML中的name值一样,否则拿不到数据
 7     user = fields.CharField(
 8         error_messages={'required':'用户名不能为空'},
 9         widget=widgets.Input(attrs={'class':'c1'})
10     )
11     pwd = fields.CharField(
12         max_length=12,
13         min_length=6,
14         error_messages={'required':'用户名不能为空','min_length':'密码长度不能小于6','max_length':'密码长度不能大于12'},
15         widget=widgets.PasswordInput
16     )
17     email = fields.EmailField(error_messages={'required':'邮箱不能为空','invalid':'邮箱格式错误'})

 

 字段本身只做插件,在CharField中继承了一个Field类,然后查看里面:

其实就是做了一些字符串的拼接操作,

我们就可以通过widgets进行设置input的类型,如果要设置里面的样式,可以使用attr()方法,在里面添加

 

django内置字段:

Field
    required=True,               是否允许为空
    widget=None,                 HTML插件
    label=None,                  用于生成Label标签或显示内容
    initial=None,                初始值
    help_text='',                帮助信息(在标签旁边显示)
    error_messages=None,         错误信息 {'required': '不能为空', 'invalid': '格式错误'}
    show_hidden_initial=False,   是否在当前插件后面再加一个隐藏的且具有默认值的插件(可用于检验两次输入是否一直)
    validators=[],               自定义验证规则
    localize=False,              是否支持本地化
    disabled=False,              是否可以编辑
    label_suffix=None            Label内容后缀
 
对数字的验证 
CharField(Field)
    max_length=None,             最大长度
    min_length=None,             最小长度
    strip=True                   是否移除用户输入空白
 
IntegerField(Field)
    max_value=None,              最大值
    min_value=None,              最小值
 
FloatField(IntegerField)
    ...
 
DecimalField(IntegerField)
    max_value=None,              最大值
    min_value=None,              最小值
    max_digits=None,             总长度
    decimal_places=None,         小数位长度
 
BaseTemporalField(Field)
    input_formats=None          时间格式化   
 
DateField(BaseTemporalField)    格式:2015-09-01
TimeField(BaseTemporalField)    格式:11:12
DateTimeField(BaseTemporalField)格式:2015-09-01 11:12
 
DurationField(Field)            时间间隔:%d %H:%M:%S.%f
    ...
 
RegexField(CharField)
    regex,                      自定制正则表达式
    max_length=None,            最大长度
    min_length=None,            最小长度
    error_message=None,         忽略,错误信息使用 error_messages={'invalid': '...'}
 
EmailField(CharField)      
    ...
上传文件用的: 
FileField(Field)
    allow_empty_file=False     是否允许空文件
 
ImageField(FileField)      
    ...
    注:需要PIL模块,pip3 install Pillow
    以上两个字典使用时,需要注意两点:
        - form表单中 enctype="multipart/form-data"
        - view函数中 obj = MyForm(request.POST, request.FILES)
 
URLField(Field)
    ...
 
BooleanField(Field)  
    ...
 
NullBooleanField(BooleanField)
    ...
 
ChoiceField(Field)
    ...
    choices=(),                选项,如:choices = ((0,'上海'),(1,'北京'),)
    required=True,             是否必填
    widget=None,               插件,默认select插件
    label=None,                Label内容
    initial=None,              初始值
    help_text='',              帮助提示
 
 
ModelChoiceField(ChoiceField)
    ...                        django.forms.models.ModelChoiceField
    queryset,                  # 查询数据库中的数据
    empty_label="---------",   # 默认空显示内容
    to_field_name=None,        # HTML中value的值对应的字段
    limit_choices_to=None      # ModelForm中对queryset二次筛选
     
ModelMultipleChoiceField(ModelChoiceField)
    ...                        django.forms.models.ModelMultipleChoiceField
 
 
     
TypedChoiceField(ChoiceField)
    coerce = lambda val: val   对选中的值进行一次转换
    empty_value= ''            空值的默认值
 
MultipleChoiceField(ChoiceField)
    ...
 
TypedMultipleChoiceField(MultipleChoiceField)
    coerce = lambda val: val   对选中的每一个值进行一次转换
    empty_value= ''            空值的默认值
 
ComboField(Field)
    fields=()                  使用多个验证,如下:即验证最大长度20,又验证邮箱格式
                               fields.ComboField(fields=[fields.CharField(max_length=20), fields.EmailField(),])
 
MultiValueField(Field)
    PS: 抽象类,子类中可以实现聚合多个字典去匹配一个值,要配合MultiWidget使用
 
SplitDateTimeField(MultiValueField)
    input_date_formats=None,   格式列表:['%Y--%m--%d', '%m%d/%Y', '%m/%d/%y']
    input_time_formats=None    格式列表:['%H:%M:%S', '%H:%M:%S.%f', '%H:%M']
 
FilePathField(ChoiceField)     文件选项,目录下文件显示在页面中
    path,                      文件夹路径
    match=None,                正则匹配
    recursive=False,           递归下面的文件夹
    allow_files=True,          允许文件
    allow_folders=False,       允许文件夹
    required=True,
    widget=None,
    label=None,
    initial=None,
    help_text=''
 
GenericIPAddressField
    protocol='both',           both,ipv4,ipv6支持的IP格式
    unpack_ipv4=False          解析ipv4地址,如果是::ffff:192.0.2.1时候,可解析为192.0.2.1, PS:protocol必须为both才能启用
 
SlugField(CharField)           数字,字母,下划线,减号(连字符)
    ...
 
UUIDField(CharField)           uuid类型
    ...

 

 我们看一下label标签:

class FM(forms.Form):
    label="用户名",
    )
    pwd = fields.CharField(
       label="密码",

    )
    email = fields.EmailField(
        label="邮箱",
    )
<form action="/fm/" method="post">
        {% csrf_token %}
        <p>{{ obj.user.label }}{{ obj.user }}{{ obj.errors.user.0 }}</p>
        <p>{{ obj.pwd.label }}{{ obj.pwd }}{{ obj.errors.pwd.0 }}</p>
        <p>{{ obj.email.label }}{{ obj.email }}{{ obj.errors.email.0 }}</p>
        <input type="submit" value="提交" >
</form>

自定义验证规则:

方式一:

1 from django.forms import Form
2 from django.forms import widgets
3 from django.forms import fields
4 from django.core.validators import RegexValidator
5  
6 class MyForm(Form):
7     user = fields.CharField(
8         validators=[RegexValidator(r'^[0-9]+$', '请输入数字'), RegexValidator(r'^159[0-9]+$', '数字必须以159开头')],
9     )

 

方式二:

 1 import re
 2 from django.forms import Form
 3 from django.forms import widgets
 4 from django.forms import fields
 5 from django.core.exceptions import ValidationError
 6  
 7  
 8 # 自定义验证规则
 9 def mobile_validate(value):
10     mobile_re = re.compile(r'^(13[0-9]|15[012356789]|17[678]|18[0-9]|14[57])[0-9]{8}$')
11     if not mobile_re.match(value):
12         raise ValidationError('手机号码格式错误')
13  
14  
15 class PublishForm(Form):
16  
17  
18     title = fields.CharField(max_length=20,
19                             min_length=5,
20                             error_messages={'required': '标题不能为空',
21                                             'min_length': '标题最少为5个字符',
22                                             'max_length': '标题最多为20个字符'},
23                             widget=widgets.TextInput(attrs={'class': "form-control",
24                                                           'placeholder': '标题5-20个字符'}))
25  
26  
27     # 使用自定义验证规则
28     phone = fields.CharField(validators=[mobile_validate, ],
29                             error_messages={'required': '手机不能为空'},
30                             widget=widgets.TextInput(attrs={'class': "form-control",
31                                                           'placeholder': u'手机号码'}))
32  
33     email = fields.EmailField(required=False,
34                             error_messages={'required': u'邮箱不能为空','invalid': u'邮箱格式错误'},
35                             widget=widgets.TextInput(attrs={'class': "form-control", 'placeholder': u'邮箱'}))
View Code

 

方式三:自定义方法

 1 from django import forms
 2     from django.forms import fields
 3     from django.forms import widgets
 4     from django.core.exceptions import ValidationError
 5     from django.core.validators import RegexValidator
 6  
 7     class FInfo(forms.Form):
 8         username = fields.CharField(max_length=5,
 9                                     validators=[RegexValidator(r'^[0-9]+$', 'Enter a valid extension.', 'invalid')], )
10         email = fields.EmailField()
11  
12         def clean_username(self):
13             """
14             Form中字段中定义的格式匹配完之后,执行此方法进行验证
15             :return:
16             """
17             value = self.cleaned_data['username']
18             if "666" in value:
19                 raise ValidationError('666已经被玩烂了...', 'invalid')
20             return value
View Code

 

方式四:同时生成多个标签进行验证

 1 from django.forms import Form
 2 from django.forms import widgets
 3 from django.forms import fields
 4  
 5 from django.core.validators import RegexValidator
 6  
 7  
 8 ############## 自定义字段 ##############
 9 class PhoneField(fields.MultiValueField):
10     def __init__(self, *args, **kwargs):
11         # Define one message for all fields.
12         error_messages = {
13             'incomplete': 'Enter a country calling code and a phone number.',
14         }
15         # Or define a different message for each field.
16         f = (
17             fields.CharField(
18                 error_messages={'incomplete': 'Enter a country calling code.'},
19                 validators=[
20                     RegexValidator(r'^[0-9]+$', 'Enter a valid country calling code.'),
21                 ],
22             ),
23             fields.CharField(
24                 error_messages={'incomplete': 'Enter a phone number.'},
25                 validators=[RegexValidator(r'^[0-9]+$', 'Enter a valid phone number.')],
26             ),
27             fields.CharField(
28                 validators=[RegexValidator(r'^[0-9]+$', 'Enter a valid extension.')],
29                 required=False,
30             ),
31         )
32         super(PhoneField, self).__init__(error_messages=error_messages, fields=f, require_all_fields=False, *args,
33                                          **kwargs)
34  
35     def compress(self, data_list):
36         """
37         当用户验证都通过后,该值返回给用户
38         :param data_list:
39         :return:
40         """
41         return data_list
42  
43 ############## 自定义插件 ##############
44 class SplitPhoneWidget(widgets.MultiWidget):
45     def __init__(self):
46         ws = (
47             widgets.TextInput(),
48             widgets.TextInput(),
49             widgets.TextInput(),
50         )
51         super(SplitPhoneWidget, self).__init__(ws)
52  
53     def decompress(self, value):
54         """
55         处理初始值,当初始值initial不是列表时,调用该方法
56         :param value:
57         :return:
58         """
59         if value:
60             return value.split(',')
61         return [None, None, None]
View Code

 

 

常用选择插件:

单radio,值为字符串
user = fields.CharField(
    initial=2,
    widget=widgets.RadioSelect(choices=((1, '上海'), (2, '北京'),))
)

单radio,值为字符串
user = fields.ChoiceField(
    choices=((1, '上海'), (2, '北京'),),
    initial=2,
    widget=widgets.RadioSelect
)

单select,值为字符串
user = fields.CharField(
    initial=2,
    widget=widgets.Select(choices=((1, '上海'), (2, '北京'),))
)

单select,值为字符串
user = fields.ChoiceField(
    choices=((1, '上海'), (2, '北京'),),
    initial=2,
    widget=widgets.Select
)

多选select,值为列表
user = fields.MultipleChoiceField(
    choices=((1, '上海'), (2, '北京'),),
    initial=[1, ],
    widget=widgets.SelectMultiple
)

单checkbox
user = fields.CharField(
    widget=widgets.CheckboxInput()
)

多选checkbox, 值为列表
user = fields.MultipleChoiceField(
    initial=[2, ],
    choices=((1, '上海'), (2, '北京'),),
    widget=widgets.CheckboxSelectMultiple
)

在使用选择标签时,需要注意choices的选项可以从数据库中获取,但是由于是静态字段 ***获取的值无法实时更新***,那么需要自定义构造方法从而达到此目的

方式一:

 1 from django.forms import Form
 2 from django.forms import widgets
 3 from django.forms import fields
 4 from django.core.validators import RegexValidator
 5  
 6 class MyForm(Form):
 7  
 8     user = fields.ChoiceField(
 9         # choices=((1, '上海'), (2, '北京'),),
10         initial=2,
11         widget=widgets.Select
12     )
13  
14     def __init__(self, *args, **kwargs):
15         super(MyForm,self).__init__(*args, **kwargs)
16         # self.fields['user'].widget.choices = ((1, '上海'), (2, '北京'),)
17         #
18         self.fields['user'].widget.choices = models.Classes.objects.all().value_list('id','caption')
View Code

方式二:

使用django提供的ModelChoiceField和ModelMultipleChoiceField字段来实现

 1 from django import forms
 2 from django.forms import fields
 3 from django.forms import widgets
 4 from django.forms import models as form_model
 5 from django.core.exceptions import ValidationError
 6 from django.core.validators import RegexValidator
 7  
 8 class FInfo(forms.Form):
 9     authors = form_model.ModelMultipleChoiceField(queryset=models.NNewType.objects.all())
10     # authors = form_model.ModelChoiceField(queryset=models.NNewType.objects.all())
View Code

 

初始化操作:

我们一般做get的请求的时候都是查询到数据之后再页面上显示的,那么我们如何为表单设置初值呢?

html:

1 <form action="/fm/" method="post">
2         {% csrf_token %}
3         <p>{{ obj.user.label }}{{ obj.user }}{{ obj.errors.user.0 }}</p>
4         <p>{{ obj.pwd.label }}{{ obj.pwd }}{{ obj.errors.pwd.0 }}</p>
5         <p>{{ obj.email.label }}{{ obj.email }}{{ obj.errors.email.0 }}</p>
6         {{ obj.city }}
7         {{ obj.city1 }}
8         <input type="submit" value="提交" >
9     </form>

 

Form类:

 1 class FM(forms.Form):
 2     #字段本身只做验证
 3     #字段名必须要和HTML中的name值一样,否则拿不到数据
 4     user = fields.CharField(
 5         error_messages={'required':'用户名不能为空'},
 6         widget=widgets.Input(attrs={'class':'c1'}),
 7         label="用户名",
 8     )
 9     pwd = fields.CharField(
10         max_length=12,
11         min_length=6,
12         error_messages={'required':'用户名不能为空','min_length':'密码长度不能小于6','max_length':'密码长度不能大于12'},
13         widget=widgets.PasswordInput,
14         label="密码",
15 
16     )
17     email = fields.EmailField(
18         error_messages={'required':'邮箱不能为空','invalid':'邮箱格式错误'},
19         label="邮箱",
20     )
21     p = fields.FileField()
22     city = fields.ChoiceField(
23         choices=((1,'上海'),(2,'北京')),
24     )
25     city1 = fields.MultipleChoiceField(
26         choices=((1, '上海'), (2, '北京')),
27     )

 

函数:

 1     if request.method == 'GET':
 2         #从数据库中获得数据
 3         dic = {
 4             'user':'root',
 5             'pwd':'123',
 6             'email':'123@123.com',
 7             'city':1,
 8             'city1':[1,2],
 9         }
10         obj = FM(initial=dic)
11         return render(request,'fm.html',{'obj':obj})

 很多时候,我们想要自行设计校验的手段,该如果做了,Django给我们提供了钩子这一手段

先来看一段源码:

if hasattr(self, 'clean_%s' % name):
      value = getattr(self, 'clean_%s' % name)()
      self.cleaned_data[name] = value

 

这段源码是能够设置钩子的来源。

#局部钩子
    def clean_user(self):
        val1 = self.cleaned_data.get("user")
        #如果这个字符串全部都是由数组组成
        if not val1.isdigit():
            return val1
        else:
            # 注意这个报错信息已经确定了
            raise ValidationError("用户名不能全部是数字组成")

 

还可以设置全局钩子:

    def clean(self):
        pwd=self.cleaned_data.get("pwd")
        repeat_pwd=self.cleaned_data.get("repeat_pwd")
        if pwd==repeat_pwd:
            print("yes")
            return self.cleaned_data
        else:
            print("no")
            raise ValidationError("两次密码不一致!")

 

为什么有全局钩子了,因为每一个钩子都是和某个具体的字段绑定,只能获取自己的字段值,不能获取其他的值,所以需要全局钩子。

案例:

  1 from django.shortcuts import render,HttpResponse
  2 
  3 # Create your views here.
  4 
  5 from django import forms
  6 from django.core.exceptions import NON_FIELD_ERRORS, ValidationError
  7 from django.forms import widgets
  8 import re
  9 from appo1 import models
 10 
 11 class LoginForm(forms.Form):
 12     user = forms.CharField(min_length=6,max_length=12,
 13                            error_messages={
 14                                "required":"用户名不能为空",
 15                                "min_length":"最小长度为5",
 16                                "max_length":"最大长度为12"
 17                            },
 18                            widget=widgets.TextInput(attrs={"id":"s1"})
 19                            )
 20     
 21     pwd = forms.CharField(min_length=6,
 22         error_messages={
 23             "required":"密码不能为空",
 24             "min_length":"最小长度为6"
 25         }
 26     )
 27 
 28     repeat_pwd = forms.CharField(
 29         error_messages={
 30             "required": "必须输入",
 31         }
 32     )
 33     
 34     email = forms.EmailField(
 35         error_messages={
 36             "required": "",
 37             "invalid":"格式错误"
 38         }
 39     )
 40     
 41     phone = forms.CharField(
 42         error_messages={
 43             "required": "必须输入",
 44         }
 45     )
 46 
 47     #必须以clear起手,否则不能尽心检测
 48     def clean_user(self):
 49         import re
 50         val1 = self.cleaned_data.get("user")
 51         ret = re.findall(r'^_', val1)
 52 
 53         if not val1.isdigit():
 54             if ret:
 55                 return val1
 56             else:
 57                 raise ValidationError("必须以下划线开头")
 58         else:
 59             raise ValidationError("用户名不能全部为数字组成")
 60 
 61     def clean_pwd(self):
 62         val2=self.cleaned_data.get("pwd")
 63         if not val2.isdigit():
 64             return val2
 65         else:
 66             raise ValidationError("密码不能全部为数字!")
 67 
 68     def clean_phone(self):
 69         val3 = self.cleaned_data.get("phone")
 70         if val3.isdigit():
 71             if len(val3) != 11:
 72                 raise ValidationError("亲,号码是11位!")
 73             else:
 74                 return val3
 75         else:
 76             raise ValidationError("号码必须是数字!")
 77 
 78     def clean(self):
 79         pwd=self.cleaned_data.get("pwd")
 80         repeat_pwd=self.cleaned_data.get("repeat_pwd")
 81         if pwd==repeat_pwd:
 82             print("yes")
 83             return self.cleaned_data
 84         else:
 85             print("no")
 86             raise ValidationError("两次密码不一致!")
 87 
 88 
 89 
 90 
 91 def login(request):
 92     if request.method == "POST":
 93         
 94         form_obj = LoginForm(request.POST)
 95         if form_obj.is_valid():
 96             user = request.POST.get("user")
 97             pwd  = request.POST.get("pwd")
 98             email = request.POST.get("email")
 99             phone = request.POST.get("phone")
100             ret = models.user_info.objects.create(user=user,pwd=pwd,email=email,phone=phone)
101             return HttpResponse("你好,欢迎回来!")
102         else:
103 
104             ret = form_obj.errors.get("__all__")
105             #{{form_obj.repwd}} < span > {{form_obj.errors.repwd.0}}{{form_obj.non_field_errors.0}} < / span >
106             #还可以在前端直接传
107             return render(request, "login.html", {"form_obj": form_obj,"ret":ret})
108     
109     
110     form_obj = LoginForm()
111     return render(request,"login.html",{"form_obj":form_obj})
112 
113 import json
114 def user_check(request):
115     response = {"is_reg": False}
116     user = request.POST.get("user")
117     ret = models.user_info.objects.filter(user=user)
118     print(ret)
119     if ret:
120         response["is_reg"] = True
View Code

 

ModelForm:

之前我们建立表单,通过建立Form类来创建,当然我们也可以直接通过model表来进行创建:

model.py:

from django.db import models

# Create your models here.
class UserType(models.Model):
    caption = models.CharField(max_length=32)

class UserInfo(models.Model):
    username = models.CharField(max_length=32)
    email = models.EmailField()
    user_type = models.ForeignKey(to='UserType',to_field='id',on_delete=models.CASCADE)

 

views.py:

from django.shortcuts import render,redirect,HttpResponse
from django import forms
from django.forms import fields
from app01 import models

class UserInfoModelForm(forms.ModelForm):
    class Meta:
        model = models.UserInfo#去哪个类中获取字段
        fields = '__all__'     #展现所有字段,也可以制定# Create your views here.
def index(request):
    if request.method=='GET':
        obj = UserInfoModelForm()
        return render(request,'index.html',{'obj':obj})
    elif request.method=='POST':
        obj = UserInfoForm(request.POST)
        obj.is_valid()

        models.UserType.objects.create(**obj.cleaned_data)
        return render(request, 'index.html', {'obj': obj})

html:

<form action="/index/" method="post">
        {% csrf_token %}
        {{ obj.as_p }}
        <input type="submit" value="提交">
</form>

这个就相当于直接从表中拿出字段名称,进行创建表单。当然,我们在form类中能够用设置字段的中文,我们只需要在model.py中设置就可以了。

username = models.CharField(max_length=32,verbose_name="用户名")

在创建表单的时候有一个字段verbose_name, 这个在django_admin中显示中文,同样的在form中也是用的这个:

fields = '__all__':代指获得所有的字段,

  • 当然也可以是列表,如:fields = ['username'],那只会把username展示出来
  • 如果字段过多的话还有排除法:exclude=['username']这样就把除了username的字段都显示出来

 modelForm验证:

ModelForm
    a.  class Meta:
            model,                           # 对应Model的
            fields=None,                     # 字段
            exclude=None,                    # 排除字段
            labels=None,                     # 提示信息
            help_texts=None,                 # 帮助提示信息
            widgets=None,                    # 自定义插件
            error_messages=None,             # 自定义错误信息(整体错误信息from django.core.exceptions import NON_FIELD_ERRORS)
            field_classes=None               # 自定义字段类 (也可以自定义字段)
            localized_fields=('birth_date',) # 本地化,如:根据不同时区显示数据
            如:
                数据库中
                    2016-12-27 04:10:57
                setting中的配置
                    TIME_ZONE = 'Asia/Shanghai'
                    USE_TZ = True
                则显示:
                    2016-12-27 12:10:57
    b. 验证执行过程
        is_valid -> full_clean -> 钩子 -> 整体错误
 
    c. 字典字段验证
        def clean_字段名(self):
            # 可以抛出异常
            # from django.core.exceptions import ValidationError
            return "新值"
    d. 用于验证
        model_form_obj = XXOOModelForm()
        model_form_obj.is_valid()
        model_form_obj.errors.as_json()
        model_form_obj.clean()
        model_form_obj.cleaned_data
    e. 用于创建
        model_form_obj = XXOOModelForm(request.POST)
        #### 页面显示,并提交 #####
        # 默认保存多对多
            obj = form.save(commit=True)
        # 不做任何操作,内部定义 save_m2m(用于保存多对多)
            obj = form.save(commit=False)
            obj.save()      # 保存单表信息
            obj.save_m2m()  # 保存关联多对多信息
 
    f. 用于更新和初始化
        obj = model.tb.objects.get(id=1)
        model_form_obj = XXOOModelForm(request.POST,instance=obj)
        ...
 
        PS: 单纯初始化
            model_form_obj = XXOOModelForm(initial={...})

 

 labels:我们在不使用verbose_name下就可以把input的字段名变成中文:

labels = {
            'username':'用户名',
            'email' : '邮箱'
}

help_text:提示要填写的内容格式等,可以使用这个:

help_texts = {
            'username':'....'
        }

widgits:自定义组件:

  默认username字段用的是charfield,如果想要改变也是可以的

from django.forms import widgets as Fwidgets

        widgets = {
            'username':Fwidgets.Textarea(attrs={'class':'c1'})
        }

 

error_message:错误信息

默认的:

error_messages = {
        '__all__' :{}#定义整体的错误信息
'username':{ "required": "用户名不能为空"} }

field_classes:字段类

field_classes = {
            'email':Ffields.URLField,
        }

 

这样email里面就只能够填写url的格式,而不是邮箱格式 

现在想要用modelForm保存表单提交的数据到数据库中:

 obj = UserInfoModelForm(request.POST)
 if obj.is_valid():
      obj.save()

 

这样就可以提交保存到数据库

 

下面我们来看一个表单读取和修改的小例子:

view.py:

def user_list(request):
    li = models.UserInfo.objects.all().select_related('user_type')
    return render(request,'user_list.html',{'li':li})
def user_edit(request,nid):
    #获取当前id对应的用户信息
    #显示用户已经存在的数据
    if request.method=="GET":
        user_obj = models.UserInfo.objects.filter(id=nid).first()
        mf = UserInfoModelForm(instance=user_obj)
        return render(request,'user_edit.html',{'mf':mf,'nid':nid})
    elif request.method == 'POST':
        user_obj = models.UserInfo.objects.filter(id=nid).first()
        mf = UserInfoModelForm(request.POST,instance=user_obj)
        if mf.is_valid():
            mf.save()
        else:
            print(mf.errors.as_json())
        return redirect('/user_list/')

 

user_list.html:

<ul>
        {% for row in li %}
            <li>{{ row.username }}-{{ row.user_type.caption }}-<a href="/edit-{{ row.id }}/">编辑</a></li>
        {% endfor %}
</ul>

 

 user_edit.html:

<form action="/edit-{{ nid }}/" method="post">
        {% csrf_token %}
        {{ mf.as_p }}
        <input type="submit" value="提交">
    </form>

 

这里注意的是:

  • 外键的联合查询可以用select_related('外键名'),但是这个只是适用于1对多,不适合多对多的查询
  • 修改数据之前get获得数据,只需要在ModelForm中用参数instance就接受查询到的参数就可以了
  • 在修改之后如果要保存数据,也要先查询一下,然后把查询到的数据通过instance参数传入