一 Form介绍
我们之前在HTML页面中利用form表单向后端提交数据时,都会写一些获取用户输入的标签并且用form标签把它们包起来。
与此同时我们在好多场景下都需要对用户的输入做校验,比如校验用户是否输入,输入的长度和格式等正不正确。如果用户输入的内容有错误就需要在页面上相应的位置显示对应的错误信息.。
Django form组件就实现了上面所述的功能。
总结一下,其实form组件的主要功能如下:
生成页面可用的HTML标签
对用户提交的数据进行校验
保留上次输入内容
普通方式手写注册功能
views.py
1 # 注册 2 def register(request): 3 error_msg = "" 4 if request.method == "POST": 5 username = request.POST.get("name") 6 pwd = request.POST.get("pwd") 7 # 对注册信息做校验 8 if len(username) < 6: 9 # 用户长度小于6位 10 error_msg = "用户名长度不能小于6位" 11 else: 12 # 将用户名和密码存到数据库 13 return HttpResponse("注册成功") 14 return render(request, "register.html", {"error_msg": error_msg})
login.html
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>注册页面</title> 6 </head> 7 <body> 8 <form action="/reg/" method="post"> 9 {% csrf_token %} 10 <p> 11 用户名: 12 <input type="text" name="name"> 13 </p> 14 <p> 15 密码: 16 <input type="password" name="pwd"> 17 </p> 18 <p> 19 <input type="submit" value="注册"> 20 <p style="color: red">{{ error_msg }}</p> 21 </p> 22 </form> 23 </body> 24 </html>
使用form组件实现注册功能
views.py
先定义好一个RegForm类:
1 from django import forms 2 3 # 按照Django form组件的要求自己写一个类 4 class RegForm(forms.Form): 5 name = forms.CharField(label="用户名") #form字段的名称写的是什么,那么前端生成input标签的时候,input标签的name属性的值就是什么 6 pwd = forms.CharField(label="密码")
再写一个视图函数:
1 # 使用form组件实现注册方式 2 def register2(request): 3 form_obj = RegForm() 4 if request.method == "POST": 5 # 实例化form对象的时候,把post提交过来的数据直接传进去 6 form_obj = RegForm(data=request.POST) #既然传过来的input标签的name属性值和form类对应的字段名是一样的,所以接过来后,form就取出对应的form字段名相同的数据进行form校验 7 # 调用form_obj校验数据的方法 8 if form_obj.is_valid(): 9 return HttpResponse("注册成功") 10 return render(request, "register2.html", {"form_obj": form_obj})
login2.html
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>注册2</title> 6 </head> 7 <body> 8 <form action="/reg2/" method="post" novalidate autocomplete="off"> #novalidate 告诉前端form表单,不要对输入的内容做校验 9 {% csrf_token %} 10 #{{ form_obj.as_p }} 直接写个这个,下面的用户名和密码的标签不自己写,你看看效果 11 <div> 12 <label for="{{ form_obj.name.id_for_label }}">{{ form_obj.name.label }}</label> 13 {{ form_obj.name }} {{ form_obj.name.errors.0 }} #errors是这个字段所有的错误,我就用其中一个错误提示就可以了,再错了再提示,并且不是给你生成ul标签了,单纯的是错误文本 14 {{ form_obj.errors }} #这是全局的所有错误,找对应字段的错误,就要form_obj.字段名 15 </div> 16 <div> 17 <label for="{{ form_obj.pwd.id_for_label }}">{{ form_obj.pwd.label }}</label> 18 {{ form_obj.pwd }} {{ form_obj.pwd.errors.0 }} 19 </div> 20 <div> 21 <input type="submit" class="btn btn-success" value="注册"> 22 </div> 23 </form> 24 </body> 25 </html>
看网页效果发现 也验证了form的功能:
前端页面是form类的对象生成的 -->生成HTML标签功能
当用户名和密码输入为空或输错之后 页面都会提示 -->用户提交校验功能
当用户输错之后 再次输入 上次的内容还保留在input框 -->保留上次输入内容
二 Form常用字段与插件
创建Form类时,主要涉及到 【字段】 和 【插件】,字段用于对用户请求数据的验证,插件用于自动生成HTML;
initial
初始值,input框里面的初始值。
1 class LoginForm(forms.Form): 2 username = forms.CharField( 3 min_length=8, 4 label="用户名", 5 initial="张三" # 设置默认值 6 ) 7 pwd = forms.CharField(min_length=6, label="密码")
error_messages
重写错误信息。
1 class LoginForm(forms.Form): 2 username = forms.CharField( 3 min_length=8, 4 label="用户名", 5 initial="张三", 6 error_messages={ 7 "required": "不能为空", 8 "invalid": "格式错误", 9 "min_length": "用户名最短8位" 10 } 11 ) 12 pwd = forms.CharField(min_length=6, label="密码")
password
1 class LoginForm(forms.Form): 2 ... 3 pwd = forms.CharField( 4 min_length=6, 5 label="密码", 6 widget=forms.widgets.PasswordInput(attrs={'class': 'c1'}, render_value=True) #这个密码字段和其他字段不一样,默认在前端输入数据错误的时候,点击提交之后,默认是不保存的原来数据的,但是可以通过这个render_value=True让这个字段在前端保留用户输入的数据 7 )
radioSelect
单radio值为字符串
1 class LoginForm(forms.Form): 2 username = forms.CharField( #其他选择框或者输入框,基本都是在这个CharField的基础上通过插件来搞的 3 min_length=8, 4 label="用户名", 5 initial="张三", 6 error_messages={ 7 "required": "不能为空", 8 "invalid": "格式错误", 9 "min_length": "用户名最短8位" 10 } 11 ) 12 pwd = forms.CharField(min_length=6, label="密码") 13 gender = forms.fields.ChoiceField( 14 choices=((1, "男"), (2, "女"), (3, "保密")), 15 label="性别", 16 initial=3, 17 widget=forms.widgets.RadioSelect() 18 )
单选Select
1 class LoginForm(forms.Form): 2 ... 3 hobby = forms.fields.ChoiceField( #注意,单选框用的是ChoiceField,并且里面的插件是Select,不然验证的时候会报错, Select a valid choice的错误。 4 choices=((1, "篮球"), (2, "足球"), (3, "双色球"), ), 5 label="爱好", 6 initial=3, 7 widget=forms.widgets.Select() 8 )
多选Select
1 class LoginForm(forms.Form): 2 ... 3 hobby = forms.fields.MultipleChoiceField( #多选框的时候用MultipleChoiceField,并且里面的插件用的是SelectMultiple,不然验证的时候会报错。 4 choices=((1, "篮球"), (2, "足球"), (3, "双色球"), ), 5 label="爱好", 6 initial=[1, 3], 7 widget=forms.widgets.SelectMultiple() 8 )
单选checkbox
1 class LoginForm(forms.Form): 2 ... 3 keep = forms.fields.ChoiceField( 4 label="是否记住密码", 5 initial="checked", 6 widget=forms.widgets.CheckboxInput() 7 )
单选checkbox示例:
1 #单选的checkbox 2 class TestForm2(forms.Form): 3 keep = forms.ChoiceField( 4 choices=( 5 ('True',1), 6 ('False',0), 7 ), 8 9 label="是否7天内自动登录", 10 initial="1", 11 widget=forms.widgets.CheckboxInput(), 12 ) 13 选中:'True' #form只是帮我们做校验,校验选择内容的时候,就是看在没在我们的choices里面,里面有这个值,表示合法,没有就不合法 14 没选中:'False' 15 ---保存到数据库里面 keep:'True' 16 if keep == 'True': 17 session 设置有效期7天 18 else: 19 pass
多选checkbox
1 class LoginForm(forms.Form): 2 ... 3 hobby = forms.fields.MultipleChoiceField( 4 choices=((1, "篮球"), (2, "足球"), (3, "双色球"),), 5 label="爱好", 6 initial=[1, 3], 7 widget=forms.widgets.CheckboxSelectMultiple() 8 )
date类型
1 from django import forms 2 from django.forms import widgets 3 class BookForm(forms.Form): 4 date = forms.DateField(widget=widgets.TextInput(attrs={'type':'date'})) #必须指定type,不然不能渲染成选择时间的input框
choice字段注意事项
在使用选择标签时,需要注意choices的选项可以配置从数据库中获取,但是由于是静态字段 获取的值无法实时更新,需要重写构造方法从而实现choice实时更新。
方式一:
1 from django.forms import Form 2 from django.forms import widgets 3 from django.forms import fields 4 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) #注意重写init方法的时候,*args和**kwargs一定要给人家写上,不然会出问题,并且验证总是不能通过,还不显示报错信息 16 # self.fields['user'].choices = ((1, '上海'), (2, '北京'),) 17 # 或 18 self.fields['user'].choices = models.Classes.objects.all().values_list('id','caption')
方式二:
1 from django import forms 2 from django.forms import fields 3 from django.forms import models as form_model 4 5 6 class FInfo(forms.Form): 7 8 authors = forms.ModelMultipleChoiceField(queryset=models.NNewType.objects.all()) # 多选 9 #或者下面这种方式,通过forms里面的models中提供的方法也是一样的。 10 authors = form_model.ModelMultipleChoiceField(queryset=models.NNewType.objects.all()) # 多选 11 #authors = form_model.ModelChoiceField(queryset=models.NNewType.objects.all()) # 单选 12 13 #或者,forms.ModelChoiceField(queryset=models.Publisth.objects.all(),widget=forms.widgets.Select()) 单选 14 # 15 authors = forms.ModelMultipleChoiceField( 16 queryset=models.Author.objects.all(), 17 widget = forms.widgets.Select(attrs={'class': 'form-control'} 18 )) 19 #如果用这种方式,别忘了model表中,NNEWType的__str__方法要写上,不然选择框里面是一个个的object对象
三 Form所有内置字段
内置字段:
1 Field 2 required=True, 是否允许为空 3 widget=None, HTML插件 4 label=None, 用于生成Label标签或显示内容 5 initial=None, 初始值 6 help_text='', 帮助信息(在标签旁边显示) 7 error_messages=None, 错误信息 {'required': '不能为空', 'invalid': '格式错误'} 8 validators=[], 自定义验证规则 9 localize=False, 是否支持本地化 10 disabled=False, 是否可以编辑 11 label_suffix=None Label内容后缀 12 13 14 CharField(Field) 15 max_length=None, 最大长度 16 min_length=None, 最小长度 17 strip=True 是否移除用户输入空白 18 19 IntegerField(Field) 20 max_value=None, 最大值 21 min_value=None, 最小值 22 23 FloatField(IntegerField) 24 ... 25 26 DecimalField(IntegerField) 27 max_value=None, 最大值 28 min_value=None, 最小值 29 max_digits=None, 总长度 30 decimal_places=None, 小数位长度 31 32 BaseTemporalField(Field) 33 input_formats=None 时间格式化 34 35 DateField(BaseTemporalField) 格式:2015-09-01 36 TimeField(BaseTemporalField) 格式:11:12 37 DateTimeField(BaseTemporalField)格式:2015-09-01 11:12 38 39 DurationField(Field) 时间间隔:%d %H:%M:%S.%f 40 ... 41 42 RegexField(CharField) 43 regex, 自定制正则表达式 44 max_length=None, 最大长度 45 min_length=None, 最小长度 46 error_message=None, 忽略,错误信息使用 error_messages={'invalid': '...'} 47 48 EmailField(CharField) 49 ... 50 51 FileField(Field) 52 allow_empty_file=False 是否允许空文件 53 54 ImageField(FileField) 55 ... 56 注:需要PIL模块,pip3 install Pillow 57 以上两个字典使用时,需要注意两点: 58 - form表单中 enctype="multipart/form-data" 59 - view函数中 obj = MyForm(request.POST, request.FILES) 60 61 URLField(Field) 62 ... 63 64 65 BooleanField(Field) 66 ... 67 68 NullBooleanField(BooleanField) 69 ... 70 71 ChoiceField(Field) 72 ... 73 choices=(), 选项,如:choices = ((0,'上海'),(1,'北京'),) 74 required=True, 是否必填 75 widget=None, 插件,默认select插件 76 label=None, Label内容 77 initial=None, 初始值 78 help_text='', 帮助提示 79 80 81 ModelChoiceField(ChoiceField) 82 ... django.forms.models.ModelChoiceField 83 queryset, # 查询数据库中的数据 84 empty_label="---------", # 默认空显示内容 85 to_field_name=None, # HTML中value的值对应的字段 86 limit_choices_to=None # ModelForm中对queryset二次筛选 87 88 ModelMultipleChoiceField(ModelChoiceField) 89 ... django.forms.models.ModelMultipleChoiceField 90 91 92 93 TypedChoiceField(ChoiceField) 94 coerce = lambda val: val 对选中的值进行一次转换 95 empty_value= '' 空值的默认值 96 97 MultipleChoiceField(ChoiceField) 98 ... 99 100 TypedMultipleChoiceField(MultipleChoiceField) 101 coerce = lambda val: val 对选中的每一个值进行一次转换 102 empty_value= '' 空值的默认值 103 104 ComboField(Field) 105 fields=() 使用多个验证,如下:即验证最大长度20,又验证邮箱格式 106 fields.ComboField(fields=[fields.CharField(max_length=20), fields.EmailField(),]) 107 108 MultiValueField(Field) 109 PS: 抽象类,子类中可以实现聚合多个字典去匹配一个值,要配合MultiWidget使用 110 111 SplitDateTimeField(MultiValueField) 112 input_date_formats=None, 格式列表:['%Y--%m--%d', '%m%d/%Y', '%m/%d/%y'] 113 input_time_formats=None 格式列表:['%H:%M:%S', '%H:%M:%S.%f', '%H:%M'] 114 115 FilePathField(ChoiceField) 文件选项,目录下文件显示在页面中 116 path, 文件夹路径 117 match=None, 正则匹配 118 recursive=False, 递归下面的文件夹 119 allow_files=True, 允许文件 120 allow_folders=False, 允许文件夹 121 required=True, 122 widget=None, 123 label=None, 124 initial=None, 125 help_text='' 126 127 GenericIPAddressField 128 protocol='both', both,ipv4,ipv6支持的IP格式 129 unpack_ipv4=False 解析ipv4地址,如果是::ffff:192.0.2.1时候,可解析为192.0.2.1, PS:protocol必须为both才能启用 130 131 SlugField(CharField) 数字,字母,下划线,减号(连字符) 132 ... 133 134 UUIDField(CharField) uuid类型
四 字段校验
RegexValidator验证器
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'邮箱'}))
五 Hook钩子方法
除了上面两种方式,我们还可以在Form类中定义钩子函数,来实现自定义的验证功能。
局部钩子
我们在Fom类中定义 clean_字段名() 方法,就能够实现对特定字段进行校验。
举个例子:
1 class LoginForm(forms.Form): 2 username = forms.CharField( 3 min_length=8, 4 label="用户名", 5 initial="张三", 6 error_messages={ 7 "required": "不能为空", 8 "invalid": "格式错误", 9 "min_length": "用户名最短8位" 10 }, 11 widget=forms.widgets.TextInput(attrs={"class": "form-control"}) 12 ) 13 ... 14 # 定义局部钩子,用来校验username字段,之前的校验股则还在,给你提供了一个添加一些校验功能的钩子 15 def clean_username(self): 16 value = self.cleaned_data.get("username") 17 if "666" in value: 18 raise ValidationError("光喊666是不行的") 19 else: 20 return value
全局钩子
我们在Fom类中定义 clean() 方法,就能够实现对字段进行全局校验,字段全部验证完,局部钩子也全部执行完之后,执行这个全局钩子校验。
1 class LoginForm(forms.Form): 2 ... 3 password = forms.CharField( 4 min_length=6, 5 label="密码", 6 widget=forms.widgets.PasswordInput(attrs={'class': 'form-control'}, render_value=True) 7 ) 8 re_password = forms.CharField( 9 min_length=6, 10 label="确认密码", 11 widget=forms.widgets.PasswordInput(attrs={'class': 'form-control'}, render_value=True) 12 ) 13 ... 14 # 定义全局的钩子,用来校验密码和确认密码字段是否相同,执行全局钩子的时候,cleaned_data里面肯定是有了通过前面验证的所有数据 15 def clean(self): 16 password_value = self.cleaned_data.get('password') 17 re_password_value = self.cleaned_data.get('re_password') 18 if password_value == re_password_value: 19 return self.cleaned_data #全局钩子要返回所有的数据 20 else: 21 self.add_error('re_password', '两次密码不一致') #在re_password这个字段的错误列表中加上一个错误,并且clean_data里面会自动清除这个re_password的值,所以打印clean_data的时候会看不到它 22 raise ValidationError('两次密码不一致')
六 进阶补充
应用Bootstrap样式
Django form应用Bootstrap样式简单示例:
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <meta http-equiv="x-ua-compatible" content="IE=edge"> 6 <meta name="viewport" content="width=device-width, initial-scale=1"> 7 <link rel="stylesheet" href="/static/bootstrap/css/bootstrap.min.css"> 8 <title>login</title> 9 </head> 10 <body> 11 <div class="container"> 12 <div class="row"> 13 <form action="/login2/" method="post" novalidate class="form-horizontal"> 14 {% csrf_token %} 15 <div class="form-group"> 16 <label for="{{ form_obj.username.id_for_label }}" 17 class="col-md-2 control-label">{{ form_obj.username.label }}</label> 18 <div class="col-md-10"> 19 {{ form_obj.username }} 20 <span class="help-block">{{ form_obj.username.errors.0 }}</span> 21 </div> 22 </div> 23 <div class="form-group"> 24 <label for="{{ form_obj.pwd.id_for_label }}" class="col-md-2 control-label">{{ form_obj.pwd.label }}</label> 25 <div class="col-md-10"> 26 {{ form_obj.pwd }} 27 <span class="help-block">{{ form_obj.pwd.errors.0 }}</span> 28 </div> 29 </div> 30 <div class="form-group"> 31 <label class="col-md-2 control-label">{{ form_obj.gender.label }}</label> 32 <div class="col-md-10"> 33 <div class="radio"> 34 {% for radio in form_obj.gender %} 35 <label for="{{ radio.id_for_label }}"> 36 {{ radio.tag }}{{ radio.choice_label }} 37 </label> 38 {% endfor %} 39 </div> 40 </div> 41 </div> 42 <div class="form-group"> 43 <div class="col-md-offset-2 col-md-10"> 44 <button type="submit" class="btn btn-default">注册</button> 45 </div> 46 </div> 47 </form> 48 </div> 49 </div> 50 51 <script src="/static/jquery-3.2.1.min.js"></script> 52 <script src="/static/bootstrap/js/bootstrap.min.js"></script> 53 </body> 54 </html>
批量添加样式
可通过重写form类的init方法来实现。
1 class LoginForm(forms.Form): 2 username = forms.CharField( 3 min_length=8, 4 label="用户名", 5 initial="张三", 6 error_messages={ 7 "required": "不能为空", 8 "invalid": "格式错误", 9 "min_length": "用户名最短8位" 10 } 11 ... 12 13 def __init__(self, *args, **kwargs): 14 super(LoginForm, self).__init__(*args, **kwargs) 15 for field in iter(self.fields): 16 self.fields[field].widget.attrs.update({ 17 'class': 'form-control' 18 })
简单写一个小项目:book表的添加和数据展示
views.py内容如下:
1 from django.shortcuts import render,HttpResponse,redirect 2 from app01 import models 3 from django import forms 4 # Create your views here. 5 6 class BookForm(forms.Form): 7 title = forms.CharField( 8 max_length=16, 9 label='书名:', 10 initial='zhang', 11 # widget = forms.widgets.PasswordInput(attrs={'class': 'form-control'},render_value=True), 12 widget = forms.widgets.TextInput(attrs={'class': 'form-control'},), 13 14 ) 15 # title2 = forms.CharField( 16 # max_length=16, 17 # label='书名:', 18 # initial='zhang', 19 # # widget = forms.widgets.PasswordInput(attrs={'class': 'form-control'},render_value=True), 20 # widget=forms.widgets.TextInput(attrs={'class': 'form-control'}, ), 21 # 22 # ) 23 sex = forms.ChoiceField( 24 choices=( 25 ('1','男'), 26 ('2','女'), 27 ) 28 , 29 label='性别:', 30 # widget=forms.widgets.RadioSelect(), 31 # widget=forms.widgets.RadioSelect(), 32 # widget=forms.widgets.SelectMultiple(attrs={'class':'form-control'}), 33 widget=forms.widgets.Select(attrs={'class':'form-control'}), 34 # widget=forms.widgets.CheckboxInput(), 35 # widget=forms.widgets.CheckboxSelectMultiple(), 36 ) 37 publishDate = forms.DateField( 38 label='出版日期:', 39 widget=forms.widgets.TextInput(attrs={'type':'date','class':'form-control'}), 40 ) 41 price=forms.DecimalField( 42 max_digits=5, 43 decimal_places=2, 44 label='书籍的价格:', 45 widget=forms.widgets.NumberInput(attrs={'class': 'form-control'} 46 )) 47 48 # publish = forms.ModelChoiceField( 49 # label='出版社:', 50 # queryset=models.Publish.objects.all(), 51 # widget=forms.widgets.Select(attrs={'class': 'form-control'} 52 # )) 53 publish_id = forms.ChoiceField( 54 label='出版社:', 55 widget=forms.widgets.Select(attrs={'class': 'form-control'} 56 )) 57 authors = forms.ModelMultipleChoiceField( 58 59 queryset=models.Author.objects.all(), 60 widget = forms.widgets.SelectMultiple(attrs={'class': 'form-control'} 61 )) 62 63 def __init__(self,*args, **kwargs): 64 super().__init__(*args, **kwargs) 65 self.fields['publish_id'].choices = models.Publish.objects.values_list('pk','name') 66 67 68 def index(request): 69 if request.method == 'GET': 70 form_obj = BookForm() 71 72 return render(request,'index.html',{'form_obj':form_obj}) 73 74 else: 75 print(request.POST) 76 form_obj = BookForm(request.POST) 77 print(form_obj.is_valid()) 78 print(form_obj.errors) 79 if form_obj.is_valid(): 80 print(form_obj.cleaned_data) 81 authors_obj = form_obj.cleaned_data.pop('authors') 82 new_book_obj = models.Book.objects.create(**form_obj.cleaned_data) 83 new_book_obj.authors.add(*authors_obj) 84 return redirect('show') 85 86 else: 87 print(form_obj.errors) 88 return render(request,'index.html',{'form_obj':form_obj}) 89 90 91 def show(request): 92 93 book_objs = models.Book.objects.all() 94 95 return render(request,'show.html',{'book_objs':book_objs}) 96 97 def edit_book(request,n): 98 99 100 return HttpResponse('欢迎来到编辑页面') 101 102 103 def delete_book(request,n): 104 return HttpResponse('欢迎来到删除页面')
urls.py内容如下
1 from django.conf.urls import url 2 from django.contrib import admin 3 from app01 import views 4 urlpatterns = [ 5 url(r'^admin/', admin.site.urls), 6 url(r'^index/', views.index,name='index'), 7 url(r'^show/', views.show,name='show'), 8 url(r'^edit_book/(\d+)/', views.edit_book,name='edit_book'), 9 url(r'^delete_book/(\d+)/', views.delete_book,name='delete_book'), 10 ]
index.html内容如下:
1 {% load static %} 2 <!DOCTYPE html> 3 <html lang="en"> 4 <head> 5 <meta charset="UTF-8"> 6 <title>Title</title> 7 <link rel="stylesheet" href="{% static 'bootstrap-3.3.0-dist/dist/css/bootstrap.min.css' %}"> 8 </head> 9 <body> 10 11 <h1>展示页面</h1> 12 {#<div>#} 13 {# {{ form_obj.as_p }}#} 14 {##} 15 {#</div>#} 16 17 {#{% for field in form_obj %}#} 18 {# <div>#} 19 {# {{ field }}#} 20 {# </div>#} 21 {##} 22 {#{% endfor %}#} 23 24 <div class="container-fluid"> 25 <div class="row"> 26 <div class="col-md-6 col-md-offset-3"> 27 <form action="" novalidate method="post"> 28 {% for field in form_obj %} 29 <div class="form-group {% if field.errors.0 %}has-error{% endif %}"> 30 <label for="{{ field.id_for_label }}">{{ field.label }}</label> 31 {{ field }} 32 <span class="text-danger">{{ field.errors.0 }}</span> 33 </div> 34 {% endfor %} 35 36 37 {# <div class="form-group">#} 38 {# <label for="{{ form_obj.sex.id_for_label }}">{{ form_obj.sex.label }}</label>#} 39 {# {{ form_obj.sex }}#} 40 {# {{ form_obj.sex.errors.0 }}#} 41 {# </div>#} 42 {# <div class="form-group">#} 43 {# <label for="{{ form_obj.publishDate.id_for_label }}">{{ form_obj.publishDate.label }}</label>#} 44 {# {{ form_obj.publishDate }}#} 45 {# {{ form_obj.publishDate.errors.0 }}#} 46 {# </div>#} 47 {# <div class="form-group">#} 48 {# <label for="{{ form_obj.price.id_for_label }}">{{ form_obj.price.label }}</label>#} 49 {# {{ form_obj.price }}#} 50 {# <span class="text-danger">{{ form_obj.price.errors.0 }}</span>#} 51 {# </div>#} 52 {# <div class="form-group">#} 53 {# <label for="{{ form_obj.publish.id_for_label }}">{{ form_obj.publish.label }}</label>#} 54 {# {{ form_obj.publish }}#} 55 {# <span class="text-danger">{{ form_obj.publish.errors.0 }}</span>#} 56 {# </div>#} 57 {# <div class="form-group">#} 58 {# <label for="{{ form_obj.authors.id_for_label }}">{{ form_obj.authors.label }}</label>#} 59 {# {{ form_obj.authors }}#} 60 {# <span class="text-danger">{{ form_obj.authors.errors.0 }}</span>#} 61 {# </div>#} 62 <input type="submit"> 63 </form> 64 </div> 65 </div> 66 </div> 67 68 69 <div> 70 71 72 </div> 73 74 75 </body> 76 <script src="{% static 'bootstrap-3.3.0-dist/dist/jQuery/jquery-3.1.1.js' %}"></script> 77 <script src="{% static 'bootstrap-3.3.0-dist/dist/js/bootstrap.min.js' %}"></script> 78 </html>
show.html内容如下
1 {% load static %} 2 <!DOCTYPE html> 3 <html lang="en"> 4 <head> 5 <meta charset="UTF-8"> 6 <title>Title</title> 7 <link rel="stylesheet" href="{% static 'bootstrap-3.3.0-dist/dist/css/bootstrap.min.css' %}"> 8 </head> 9 <body> 10 11 <h1>数据展示</h1> 12 <div class="container-fluid"> 13 <div class="row"> 14 <div class="col-md-6 col-md-offset-3"> 15 <table class="table"> 16 <thead> 17 <tr> 18 <th>id</th> 19 <th>title</th> 20 <th>性别</th> 21 <th>出版日期</th> 22 <th>出版社</th> 23 <th>作者</th> 24 <th>操作</th> 25 </tr> 26 </thead> 27 <tbody> 28 {% for book_obj in book_objs %} 29 <tr> 30 <td>{{ book_obj.pk }}</td> 31 <td>{{ book_obj.title }}</td> 32 <td>{{ book_obj.get_sex_display }}</td> 33 <td>{{ book_obj.publishDate|date:'Y-d-m' }}</td> 34 <td>{{ book_obj.publish.name }}</td> 35 <td> 36 {% for author in book_obj.authors.all %} 37 {{ author.name }} 38 {% endfor %} 39 40 </td> 41 <td> 42 <a class="btn btn-warning" href="{% url 'edit_book' book_obj.pk %}">编辑</a> 43 <a class="btn btn-danger" href="{% url 'delete_book' book_obj.pk %}">删除</a> 44 </td> 45 </tr> 46 {% endfor %} 47 </tbody> 48 </table> 49 </div> 50 </div> 51 52 </div> 53 54 </body> 55 <script src="{% static 'bootstrap-3.3.0-dist/dist/jQuery/jquery-3.1.1.js' %}"></script> 56 <script src="{% static 'bootstrap-3.3.0-dist/dist/js/bootstrap.min.js' %}"></script> 57 </html>
别忘了在settings.py中配置静态文件
1 STATIC_URL = '/static/' 2 STATICFILES_DIRS = [ 3 os.path.join(BASE_DIR,'statics'), 4 ]
别忘了去下载一个bootstrap和jquery然后在上面的html文件中引入一下。
models.py文件内容如下:
1 from django.db import models 2 3 # Create your models here. 4 5 6 from django.db import models 7 8 # Create your models here. 9 10 11 class Author(models.Model): 12 nid = models.AutoField(primary_key=True) 13 name=models.CharField( max_length=32) 14 age=models.IntegerField() 15 authorDetail=models.OneToOneField(to="AuthorDetail",to_field="nid") 16 def __str__(self): 17 return self.name 18 class AuthorDetail(models.Model): 19 20 nid = models.AutoField(primary_key=True) 21 birthday=models.DateField() 22 telephone=models.BigIntegerField() 23 addr=models.CharField( max_length=64) 24 25 class Publish(models.Model): 26 nid = models.AutoField(primary_key=True) 27 name=models.CharField( max_length=32) 28 city=models.CharField( max_length=32) 29 email=models.EmailField() 30 def __str__(self): 31 return self.name 32 33 class Book(models.Model): 34 35 nid = models.AutoField(primary_key=True) 36 title = models.CharField( max_length=32) 37 sex_choice = ( 38 (1,'男'), 39 (2,'女'), 40 ) 41 sex = models.IntegerField(choices=sex_choice,default=1) 42 publishDate=models.DateField() 43 price=models.DecimalField(max_digits=5,decimal_places=2) 44 publish=models.ForeignKey(to="Publish",to_field="nid") 45 authors=models.ManyToManyField(to='Author',) 46 def __str__(self): 47 return self.title
别忘了给每个表添加一些数据。
七 ModelForm
通常在Django项目中,我们编写的大部分都是与Django 的模型紧密映射的表单。 举个例子,你也许会有个Book 模型,并且你还想创建一个form表单用来添加和编辑书籍信息到这个模型中。 在这种情况下,在form表单中定义字段将是冗余的,因为我们已经在模型中定义了那些字段。
基于这个原因,Django 提供一个辅助类来让我们可以从Django 的模型创建Form,这就是ModelForm。
modelForm定义
form与model的终极结合,会根据你model中的字段转换成对应的form字段,并且并你生成标签等操作。
比如你的models中的表是下面的内容:
1 class Book(models.Model): 2 3 nid = models.AutoField(primary_key=True) 4 title = models.CharField( max_length=32) 5 publishDate=models.DateField() 6 price=models.DecimalField(max_digits=5,decimal_places=2) 7 publish=models.ForeignKey(to="Publish",to_field="nid") 8 authors=models.ManyToManyField(to='Author',) 9 def __str__(self): 10 return self.title
modelform类的写法。
1 class BookForm(forms.ModelForm): 2 3 class Meta: 4 model = models.Book 5 fields = "__all__" 6 labels = { 7 "title": "书名", 8 "price": "价格" 9 } 10 widgets = { 11 "password": forms.widgets.PasswordInput(attrs={"class": "c1"}), 12 "publishDate": forms.widgets.DateInput(attrs={"type": "date"}), 13 }
class Meta下常用参数:
1 model = models.Book # 对应的Model中的类 2 fields = "__all__" # 字段,如果是__all__,就是表示列出所有的字段 3 exclude = None # 排除的字段 4 labels = None # 提示信息 5 help_texts = None # 帮助提示信息 6 widgets = None # 自定义插件 7 error_messages = None # 自定义错误信息 8 error_messages = { 9 'title':{'required':'不能为空',...} #每个字段的所有的错误都可以写,...是省略的意思,复制黏贴我代码的时候别忘了删了... 10 }
批量添加样式:和form的一样
1 class BookForm(forms.ModelForm): 2 #password = forms.CharField(min_length=10) #可以重写字段,会覆盖modelform中的这个字段,那么modelform下面关于这个字段的设置就会被覆盖,比如果设置插件啊,error_messages啊等等, 3 r_password = forms.CharField() #想多验证一些字段可以单独拿出来写,按照form的写法,写在Meta的上面或者下面都可以 4 class Meta: 5 model = models.Book 6 # fields = ['title','price'] 7 fields = "__all__" #['title,'price'] 指定字段生成form 8 # exclude=['title',] #排除字段 9 labels = { 10 "title": "书名", 11 "price": "价格" 12 } 13 error_messages = { 14 'title':{'required':'不能为空',} #每个字段的错误都可以写 15 } 16 #如果models中的字段和咱们需要验证的字段对不齐的是,比如注册时,咱们需要验证密码和确认密码两个字段数据,但是后端数据库就保存一个数据就行,那么验证是两个,数据保存是一个,就可以再接着写form字段 17 r_password = forms.CharField()。 18 #同样的,如果想做一些特殊的验证定制,那么和form一昂,也是那两个钩子(全局和局部),写法也是form那个的写法,直接在咱们的类里面写: 19 #局部钩子: 20 def clean_title(self): 21 pass 22 #全局钩子 23 def clean(self): 24 pass 25 def __init__(self,*args,**kwargs): #批量操作 26 super().__init__(*args,**kwargs) 27 for field in self.fields: 28 #field.error_messages = {'required':'不能为空'} #批量添加错误信息,这是都一样的错误,不一样的还是要单独写。 29 self.fields[field].widget.attrs.update({'class':'form-control'})
ModelForm的验证
与普通的Form表单验证类型类似,ModelForm表单的验证在调用is_valid() 或访问errors 属性时隐式调用。
我们可以像使用Form类一样自定义局部钩子方法和全局钩子方法来实现自定义的校验规则。
如果我们不重写具体字段并设置validators属性的话,ModelForm是按照模型中字段的validators来校验的。
save()方法
每个ModelForm还具有一个save()方法。 这个方法根据表单绑定的数据创建并保存数据库对象。 ModelForm的子类可以接受现有的模型实例作为关键字参数instance;如果提供此功能,则save()将更新该实例。 如果没有提供,save() 将创建模型的一个新实例:
1 >>> from myapp.models import Book 2 >>> from myapp.forms import BookForm 3 4 # 根据POST数据创建一个新的form对象 5 >>> form_obj = BookForm(request.POST) 6 7 # 创建书籍对象 8 >>> new_ book = form_obj.save() 9 10 # 基于一个书籍对象创建form对象 11 >>> edit_obj = Book.objects.get(id=1) 12 # 使用POST提交的数据更新书籍对象 13 >>> form_obj = BookForm(request.POST, instance=edit_obj) 14 >>> form_obj.save()
之前我们通过form组件来保存书籍表数据的时候的写法:
1 def index(request): 2 if request.method == 'GET': 3 form_obj = BookForm() 4 5 return render(request,'index.html',{'form_obj':form_obj}) 6 7 else: 8 form_obj = BookForm(request.POST) 9 if form_obj.is_valid(): 10 # authors_obj = form_obj.cleaned_data.pop('authors') 11 # new_book_obj = models.Book.objects.create(**form_obj.cleaned_data) 12 # new_book_obj.authors.add(*authors_obj) 13 form_obj.save() #因为我们再Meta中指定了是哪张表,所以它会自动识别,不管是外键还是多对多等,都会自行处理保存,它完成的就是上面三句话做的事情,并且还有就是如果你验证的数据比你后端数据表中的字段多,那么他自会自动剔除多余的不需要保存的字段,比如那个重复确认密码就不要保存 14 return redirect('show') 15 16 else: 17 print(form_obj.errors) 18 return render(request,'index.html',{'form_obj':form_obj})
比如说我们图书管理系统页面之前是这样写的
1 {% load static %} 2 <!DOCTYPE html> 3 <html lang="en"> 4 <head> 5 <meta charset="UTF-8"> 6 <title>Title</title> 7 <link rel="stylesheet" href="{% static 'bootstrap-3.3.0-dist/dist/css/bootstrap.min.css' %}"> 8 </head> 9 <body> 10 11 <h1>编辑页面</h1> 12 <div class="container-fluid"> 13 <div class="row"> 14 <div class="col-md-6 col-md-offset-3"> 15 <form action=""> 16 <div class="form-group"> 17 <label for="title">书名</label> 18 <input type="text" class="form-control" id="title" placeholder="title" value="{{ book_obj.title }}"> 19 20 </div> 21 <div class="form-group"> 22 <label for="publishDate">出版日期</label> 23 <input type="text" class="form-control" id="publishDate" placeholder="publishDate" value="{{ book_obj.publishDate|date:'Y-m-d' }}"> 24 25 </div> 26 <div class="form-group"> 27 <label for="price">价格</label> 28 <input type="number" class="form-control" id="price" placeholder="price" value="{{ book_obj.price }}"> 29 30 </div> 31 <div class="form-group"> 32 <label for="publish">书名</label> 33 <select name="publish" id="publish" class="form-control"> 34 {% for publish in all_publish %} 35 {% if publish == book_obj.publish %} 36 <option value="{{ publish.id }}" selected>{{ publish.name }}</option> 37 {% else %} 38 <option value="{{ publish.id }}">{{ publish.name }}</option> 39 {% endif %} 40 {% endfor %} 41 42 </select> 43 44 </div> 45 <div class="form-group"> 46 <label for="authors">书名</label> 47 <select name="authors" id="authors" multiple class="form-control"> 48 {% for author in all_authors %} 49 {% if author in book_obj.authors.all %} 50 <option value="{{ author.id }}" selected>{{ author.name }}</option> 51 {% else %} 52 <option value="{{ author.id }}" >{{ author.name }}</option> 53 {% endif %} 54 {% endfor %} 55 56 </select> 57 58 </div> 59 </form> 60 61 </div> 62 </div> 63 </div> 64 65 66 </body> 67 <script src="{% static 'bootstrap-3.3.0-dist/dist/jQuery/jquery-3.1.1.js' %}"></script> 68 <script src="{% static 'bootstrap-3.3.0-dist/dist/js/bootstrap.min.js' %}"></script> 69 </html>
views.py是这样写的:
1 def edit_book(request,n): 2 3 book_obj = models.Book.objects.filter(pk=n).first() 4 if request.method == 'GET': 5 all_authors = models.Author.objects.all() # 6 all_publish = models.Publish.objects.all() 7 8 return render(request,'edit_book.html',{'book_obj':book_obj,'all_authors':all_authors,'all_publish':all_publish})
改成使用modelform之后,我们这样写:
1 {% load static %} 2 <!DOCTYPE html> 3 <html lang="en"> 4 <head> 5 <meta charset="UTF-8"> 6 <title>Title</title> 7 <link rel="stylesheet" href="{% static 'bootstrap-3.3.0-dist/dist/css/bootstrap.min.css' %}"> 8 </head> 9 <body> 10 11 <h1>编辑页面</h1> 12 <div class="container-fluid"> 13 <div class="row"> 14 <div class="col-md-6 col-md-offset-3"> 15 <form action="{% url 'edit_book' n %}" novalidate method="post"> 16 {% csrf_token %} 17 {% for field in form %} 18 <div class="form-group"> 19 <label for="{{ field.id_for_label }}">{{ field.label }}</label> 20 {{ field }} 21 <span class="text-danger">{{ field.errors.0 }}</span> 22 </div> 23 {% endfor %} 24 25 <div class="form-group"> 26 <input type="submit" class="btn btn-primary pull-right"> 27 </div> 28 29 </form> 30 31 </div> 32 </div> 33 </div> 34 35 36 </body> 37 <script src="{% static 'bootstrap-3.3.0-dist/dist/jQuery/jquery-3.1.1.js' %}"></script> 38 <script src="{% static 'bootstrap-3.3.0-dist/dist/js/bootstrap.min.js' %}"></script> 39 </html>
views.py这样写:
1 def edit_book(request,n): 2 3 book_obj = models.Book.objects.filter(pk=n).first() 4 if request.method == 'GET': 5 # all_authors = models.Author.objects.all() # 6 # all_publish = models.Publish.objects.all() 7 8 form = BookForm(instance=book_obj) 9 10 return render(request,'edit_book.html',{'form':form,'n':n}) #传递的这个n参数是给form表单提交数据的是的action的url用的,因为它需要一个参数来识别是更新的哪条记录 11 12 else: 13 form = BookForm(request.POST,instance=book_obj) #必须指定instance,不然我们调用save方法的是又变成了添加操作 14 if form.is_valid(): 15 form.save() 16 return redirect('show') 17 else: 18 return render(request,'edit_book.html',{'form':form,'n':n})