表单,在前端页面中属于最常见的一个东西了。基本上网站信息的提交都用到了表单,所以下面来学习Django中优雅的表单系统:Form
表单的主要作用是在网页上提供一个图形用户页面,用作采集和提供用户输入数据。
表单的基本结构: <form></form>
get:使用URL传参:http://服务器地址?name1 = value&name2=value2(?表示传递参数,?后面采用name=value的形式传递,多个参数之间,用&链接)URL传参不安全,所有信息可在地址栏看到,并且可以通过地址栏随机传递其他数据。URL传递数据量有限,只能传递少量数据。
post:使用HTTP请求传递数据。URL地址栏不可见,比较安全。且传递数据量没有限制。
表单提交中中get和post方式的区别
input标签是输入框,是表单中最重要的部分。
<form action="外部链接路径".method="get/post".name="#"> <input type="text">输入文本框 <input type="password">输入密码框 <input type="button">输入按钮 <input type="reset">重置 <input type="submit">提交 <input type="file">文件 <input type="checkbox">多选框 <input type="checkbox" checked>代表多选框默认选择项 <input type="radio">单选框,注意name需一样 <input type="date">时间 <input type="checkbox">多选框
name:是指名字,因为提交的是键值对,所以必须要指定名字,否则无法提交,即使提交了也没有意义。
value:文本框的内容,一般用在不能输入的类型中,如改变按钮的名字等。
placeholder:占位内容,通常用于显示
readonly:只读模式,设置后无法修改输入框的内容
disabled:禁用状态
size:由于输入框是单行的,所以只能设置宽度
maxlength:限制输入框最大输入的字符个数
开发中表单提交是很常见的,表单的提交方式也有很多种。
1,使用submit按钮提交表单
<input type="submit" value="提交">
2,使用button按钮提交表单
<input type="button" value="提交">
3,使用js进行表单提交,将form表单进行标记,将form表单中的某个元素设置成点击事件,点击时候调用js函数,再用JS。
$("#id").submit()
label元素不会向用户呈现任何特殊效果。不过,它为鼠标用户改进了可用性。如果您在label元素内点击文本,就会触发此控件。也就是说,当用户选择该标签时,浏览器就会自动将焦点转到和标签相关的表单控件上。
<label> 标签的for属性应当与相关元素的id属性相同
带有两个输入字段和相关标记的简单HTML表单:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <table> <tr> <td><label for="username">用户名: </label></td> <td><input type="text" name="username" id="username"></td> </tr> <tr> <td><label for="username">密码: </label></td> <td><input type="password" name="password" id="password"></td> </tr> <tr> <td><label for="repassword">密码确认: </label></td> <td><input type="password" name="password" id="repassword"></td> </tr> <tr> <td><label for="_basketball">爱好: </label></td> <td> <label><input type="checkbox" value="basketball" name="'hobby" id="_basketball">basketball</label> <label><input type="checkbox" value="football" name="'hobby" id="_basketball">football</label> <label><input type="checkbox" value="skating" name="'hobby" id="_basketball">skating</label> </td> </tr> <tr> <td><label for="_boy">性别:</label></td> <td> <label><input type="radio" value="boy" name="sex" id="_boy">boy</label> <label><input type="radio" value="girl" name="sex" >girl</label> </td> </tr> <tr> <td><label for="email">邮箱:</label></td> <td><input type="text" name="email" id="email"></td> </tr> <tr> <td><label for="address">住址:</label></td> <td><input type="text" name="address" id="address"></td> </tr> </table> </body> </html>
表单提交数据就是由HTML表单向后台传递信息,后台通过request.GET() 或者request.POST()获取。
Django内置了许多字段类型,他们都位于django.db.models中,例如models.CharField 。这些类型基本满足需求。如果还不够你也可以自定义字段。
recursive,如果为False(默认值),只用直接位于path 下的文件或目录作为选项。如果为True,将递归访问这个目录,其所有的子目录和文件都将作为选项。
match,正则表达式表示的一个模式;只有匹配这个表达式的名称才允许作为选项。
allow_files,可选。为True 或False。默认为True。表示是否应该包含指定位置的文件。它和allow_folders 必须有一个为True。
allow_folders,可选。为True 或False。 默认为False。表示是否应该包含指定位置的目录。 它和allow_files 必须有一个为True。
就像MultipleChoiceField,除了TypedMultipleChoiceField需要两个额外的参数,coerce和empty_value。
两个字段可用于表示模型之间的关系:ModelChoiceField和ModelMultipleChoiceField。这两个字段都需要单个queryset参数,用于创建字段的选择。在表单验证时,这些字段将把一个模型对象(在ModelChoiceField的情况下)或多个模型对象(在ModelMultipleChoiceField的情况下)放置到cleaned_data表单的字典。
如果内建的字段不能满足你的需求,你可以很容易地创建自定义的字段。你需要创建django.forms.Field 的一个子类。它只要求实现一个clean() 方法和接收上面核心参数的init() 方法(required, label, initial, widget, help_text)。
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类型 ...
字段是模型中最重要的内容之一,也是唯一必须的部分。字段在Python中表现为一个类属性,体现了数据表中的一个列,请不要使用 clean , save , delete 等Django内置的模型API名字,防止命名冲突。下面是一个展示,注意字段的写法:
from django.db import models class Musician(models.Model): first_name = models.CharField(max_length=50) last_name = models.CharField(max_length=50) instrument = models.CharField(max_length=100) class Album(models.Model): artist = models.ForeignKey(Musician, on_delete=models.CASCADE) name = models.CharField(max_length=100) release_date = models.DateField() num_stars = models.IntegerField()
Django不允许下面两种字段名:
class Example(models.Model): pass = models.IntegerField() # 'pass'是Python保留字!
class Example(models.Model): foo__bar = models.IntegerField() # 'foo__bar' 有两个下划线在一起!
由于你可以自定义表名,列名,上面的规则可能被绕开,但是请养成良好的习惯,一定不要那么起名。
SQL语言的join,where和Select 等保留字可以作为字段名,因为Django对他们都进行了转义。
如果是True,Django会在数据库中将次字段的值置为NULL,默认值为False。(null=True是允许数据库这个值是空)
如果为True时,django的Admin添加数据时可允许空值,可以不填。如果为False,则必须填。默认为False。(black=True 表示允许表单验证为空)
主键,对AutoFiled设置主键后,就会代替原来的自增ID列
auto_now 自动创建——无论添加或者修改,都是当前操作的时间
auto_now_add 自动创建——永远是创建时的时间
一个二维的元组被用作choices,如果这样定义,Django会select box代替普通的文本框,并且限制choices的值时元组中的值。
字段长度
默认值
Admin中字段的显示名称,如果不设置该参数时,则与属性名
数据库中的字段名称
不允许重复
数据库索引
在Admin里是否可以编辑
错误提示
自动创建
TextInput(Input) NumberInput(TextInput) EmailInput(TextInput) URLInput(TextInput) PasswordInput(TextInput) HiddenInput(TextInput) Textarea(Widget) DateInput(DateTimeBaseInput) DateTimeInput(DateTimeBaseInput) TimeInput(DateTimeBaseInput) CheckboxInput Select NullBooleanSelect SelectMultiple RadioSelect CheckboxSelectMultiple FileInput ClearableFileInput MultipleHiddenInput SplitDateTimeWidget SplitHiddenDateTimeWidget SelectDateWidget
widget是form表单最重要的参数之一,指定渲染Widget时使用的widget类,举个例子:就是说这个form字段在HTML页面中为文本输入框,密码输入框,单选框,多选框。。。。。
pwd = forms.CharField( min_length=6, label="密码", widget=forms.widgets.PasswordInput()
单radio值为字符串
user_type_choice = ( (0, u'普通用户'), (2, u'高级用户'), ) user_type = forms.IntegerField(initial=2, widget=forms.widgets.RadioSelect(choices=user_type_choice,))
user_type_choice = ( (0, u'普通用户'), (2, u'高级用户'), ) user_type = forms.IntegerField(initial=2, widget=forms.widgets.Select(choices=user_type_choice,))
user_type_choice = ( (0, u'普通用户'), (2, u'高级用户'), ) user_type = forms.IntegerField(initial=[1, ], widget=forms.widgets.SelectMultiple(choices=user_type_choice,))
user_type = forms.CharField(widget=forms.widgets.CheckboxInput())
值为列表
user_type_choice = ( (0, u'普通用户'), (2, u'高级用户'), ) user_type = forms.CharField( initial=[2, ], widget=forms.widgets.CheckboxSelectMultiple( choices=user_type_choice, ))
在使用选择标签的时候,需要注意choices的选项可以从数据库获取,但是由于是静态子弹,获取的值无法更新,那么需要自定义构造方法从而达到目的。
class FileField(upload_to=None, max_length=100, **options)[source]
上传文件字段(不能设置为主键)。默认情况下,该字段在HTML中表现为一个ClearableFileInput 标签。在数据库内,我们实际保存的是一个字符串类型,默认最大长度为100,可以通过max_length 参数自定义。真实的文件是保存在服务器的文件系统内的。
重要参数upload_to 用于设置上传地址的目录和文件名。如下例所示:
class MyModel(models.Model): # 文件被传至`MEDIA_ROOT/uploads`目录,MEDIA_ROOT由你在settings文件中设置 upload = models.FileField(upload_to='uploads/') # 或者 # 被传到`MEDIA_ROOT/uploads/2015/01/30`目录,增加了一个时间划分 upload = models.FileField(upload_to='uploads/%Y/%m/%d/')
Django很人性化的帮我们实现了根据日期生成目录的方式!
upload_to 参数也可以接收一个回调函数,该函数返回具体的路径字符串,如下例:
def user_directory_path(instance, filename): #文件上传到MEDIA_ROOT/user_<id>/<filename>目录中 return 'user_{0}/{1}'.format(instance.user.id, filename) class MyModel(models.Model): upload = models.FileField(upload_to=user_directory_path)
例子中,user_directory_path 这种回调函数,必须接收两个参数,然后返回一个Unix风格的路径字符串。参数instace代表一个定义了FileField的模型的实例,说白了就是当前数据记录。filename是原本的文件名。
class ImageField(upload_to=None, height_field=None, width_field=None, max_length=100, **options)[source]
用于保存图像文件袋额字段。其基本用法和特征与FileField一样,只不过多了两个属性height和width。默认清洗下,该字段在HTML中表现为一个ClearableFileInput 标签。在数据库内,我们实际保存的是一个字符串类型。默认最大长度100,可以通过max_length 参数自定义。真实的图片是保存在服务器的文件系统内的。
使用Django的ImageField需要提前安装pillow模块。
使用FileField或者ImageField字段的步骤:
安全建议
无论你如何保存上传的文件,一定要注意他们的内容和格式,避免安全漏洞!务必对所有上传文件进行安全检查,确保他们不出问题! 如果你不加任何检查就盲目的让任何人上传到你的服务器文档根目录内,比如上传了一个CGI 或者PHP脚本,很可能就会被访问的用户执行,这具有致命的危害。
class FilePathField(path=None, match=None, recursive=False, max_length=100, **options)[source]
一种用来保存文件路径信息的子段。在数据表内以字符串的形式存在,默认最大长度100,可以通过max_length参数设置。
他们包含有下面的一些参数:
比如:
FilePathField(path="/home/images", match="foo.*", recursive=True)
他只匹配/home/images/foo.png ,但是不匹配 /home/images/foo/bar.png ,因为默认情况,只匹配文件名,而不管路径怎么样的。
数据库无法为自己生成uuid,因此需要如下使用default参数:
import uuid # Python的内置模块 from django.db import models class MyUUIDModel(models.Model): id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) # 其它字段
views.py
from django.shortcuts import render, HttpResponse import os def upload(request): if request.method == 'GET': # return HttpResponse("OK") return render(request, 'user8_book/upload.html') elif request.method == 'POST': # print("OK") # return HttpResponse("OK") obj = request.FILES.get('file') print(obj.name) f = open(os.path.join('static/upload', obj.name), 'wb') for line in obj.chunks(): f.write(line) f.close() return HttpResponse('上传成功')
urls.py
from django.conf.urls import url, include from django.contrib import admin from user8_book import views from django.urls import path urlpatterns = [ path('book/', views.book), path('upload.html/', views.upload), ]
models.py
from django.db import models # Create your models here. class Upload(models.Model): text = models.CharField(max_length=100) file = models.FileField(max_length=128)
upload.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>upload</title> </head> <body> {# 文件上传的form#} {#<form method="post" enctype="multipart/form-data">#} <form method="post" action="/user8_book/upload.html/" enctype="multipart/form-data"> {% csrf_token %} <p><input id="name" type="text" name="input-name" placeholder="请输入文件名称" ></p> <p><input id="file" type="file" name="file"></p> <p><input id="submit" type="submit" value="submit"></p> </form> </body> </html>
报错内容:
解决方法:
查看自己的HTML文件中,表单的action属性值结尾是否添加了“/”,我就是因为没有添加,找了半天错误,真尴尬。粗心大意
我们可以在APP目录下新建立一个myforms.py文件,然后再建立form。为什么要新建一个myforms.py呢?其好处显而易见:
一般情况下,使用一种东西就会受制于一种东西,但是这并不是Django的意愿。并非你接受了django表单的方便就需要接受这么丑陋的页面。Django表单时可以随你心愿定制的。
我们不但可以对标签名进行定制化,还可以限制输入的位数,比如下面代码,我们限制了输入九位,第十位就无法输入,而且并非所有的信息都是强制填的,可以根据需要来抉择,同样看下面代码,即使email空着不填,表单也能正常提交。
class UserInfo(forms.Form): email = forms.EmailField(required=False) host = forms.CharField(max_length=9) port = forms.CharField(max_length=4) mobile = forms.CharField(max_length=11)
每个表单字段都有一个对应的Widget class,它对应一个HTML表单Widget,我们可以使用Forms中的widget来对我们的前端表按照我们的要求进行改造
比如当我们需要做一个长文本输入框时候:
class UserInfo(forms.Form): email = forms.EmailField(required=False) host = forms.CharField(max_length=9) port = forms.CharField(max_length=4) mobile = forms.CharField(max_length=11) comment = forms.CharField(widget=forms.Textarea)
html代码如下:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <form action="/user15_form/user_list/" method="post"> {% csrf_token %} <p>主机:{{ obj.host }}<span>{{ errors.host }}</span></p> <p>端口:{{ obj.port }}<span>{{ errors.port }}</span></p> <p>邮箱:{{ obj.email }}<span>{{ errors.email }}</span></p> <p>手机:{{ obj.mobile }}<span>{{ errors.mobile }}</span></p> <p>评论:{{ obj.comment }}<span>{{ errors.comment }}</span></p> <input type="submit" value="提交"> </form> </body> </html>
效果如下:
当我们想做一个下拉框的时候:
其views.py代码如下:
from django.shortcuts import render # Create your views here. from django import forms from django.forms.widgets import SelectDateWidget class UserInfo(forms.Form): email = forms.EmailField(required=False) host = forms.CharField(max_length=9) port = forms.CharField(max_length=4) mobile = forms.CharField(max_length=11) comment = forms.CharField(widget=forms.Textarea) years = ('2010', '2019', '2029') year = forms.DateField(widget=SelectDateWidget(years=years)) def user_list(request): obj = UserInfo() if request.method == 'POST': user_input_obj = UserInfo(request.POST) if user_input_obj.is_valid(): # 如果输入合法的话,就获取输入的值 data = user_input_obj.clean() print(data) else: # 获取错误信息 error_msg = user_input_obj.errors return render(request, 'user15_form/user_list.html', {'obj':user_input_obj, 'errors':error_msg}) return render(request, 'user15_form/user_list.html', {'obj':obj})
HTML代码如下:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <form action="/user15_form/user_list/" method="post"> {% csrf_token %} <p>主机:{{ obj.host }}<span>{{ errors.host }}</span></p> <p>端口:{{ obj.port }}<span>{{ errors.port }}</span></p> <p>邮箱:{{ obj.email }}<span>{{ errors.email }}</span></p> <p>手机:{{ obj.mobile }}<span>{{ errors.mobile }}</span></p> <p>评论:{{ obj.comment }}<span>{{ errors.comment }}</span></p> <p>年份:{{ obj.year }}<span>{{ errors.year }}</span></p> <input type="submit" value="提交"> </form> </body> </html>
结果展示如下:
对ChoiceField字段,Form默认的是集成select的widget,即使用HTML的列表形式<select>,而RadioSelect使用单选按钮。 如果想要某一个输入框看起来更特别,不一样的话,你可以这样做:
views.py 的代码如下(这里只修改了comment的代码):
comment = forms.CharField(widget=forms.TextInput(attrs={'size':'40'}))
我们为其设置了attrs,也就是HTML中的css属性。(要是想为整个表单或者更深度的定制一些css以及JavaScript,可以前往Django官方文档了解)
效果如下:
虽然字段类主要使用在表单类中,但是我们也可以直接实例化他们来使用,以便更好地了解他们是如何工作的。每个字段实例都有一个clean()方法,它接受一个参数,然后返回“清洁的”数据或者抛出一个django.forms.
>>> from django import forms >>> f = forms.EmailField() >>> f.clean('foo@example.com') 'foo@example.com' >>> f.clean('invalid email address') Traceback (most recent call last): ... ValidationError: ['Enter a valid email address.']
每个字段类的构造函数至少接受这些参数。有些字段类接受额外的,字段特有的参数,但以下参数应该总能接受:
默认情况下,每个字段都假设必须有值,所以如果你传递一个空的值——不管是None,还是空字符串(" ")——clean() 都将引发一个ValidationError异常。
>>> from django import forms >>> f = forms.CharField() >>> f.clean() Traceback (most recent call last): File "<input>", line 1, in <module> TypeError: clean() missing 1 required positional argument: 'value' >>> f.clean("") Traceback (most recent call last): File "<input>", line 1, in <module> File "C:\Users\Administrator\AppData\Local\Programs\Python\Python37\lib\site-packages\django\forms\fields.py", line 148, in clean self.validate(value) File "C:\Users\Administrator\AppData\Local\Programs\Python\Python37\lib\site-packages\django\forms\fields.py", line 126, in validate raise ValidationError(self.error_messages['required'], code='required') django.core.exceptions.ValidationError: ['This field is required.'] >>> f.clean("12") '12'
正如前面“输出表单为HTML”中解释的,字段默认label是通过将字段名中所有的下划线转换成空格并大写第一个字母生成的。如果默认的标签不合适,可以指定label。
>>> from django import forms >>> class CommentForm(forms.Form): ... name = forms.CharField(label='Your name') ... url = forms.URLField(label='Your Web site', required=False) ... comment = forms.CharField() >>> f = CommentForm(auto_id=False) >>> print(f) <tr><th>Your name:</th><td><input type="text" name="name" /></td></tr> <tr><th>Your Web site:</th><td><input type="url" name="url" /></td></tr> <tr><th>Comment:</th><td><input type="text" name="comment" /></td></tr>
label_suffix参数让你基于每个字段覆盖表单的label_suffix
>>> class ContactForm(forms.Form): ... age = forms.IntegerField() ... nationality = forms.CharField() ... captcha_answer = forms.IntegerField(label='2 + 2', label_suffix=' =') >>> f = ContactForm(label_suffix='?') >>> print(f.as_p()) <p><label for="id_age">Age?</label> <input id="id_age" name="age" type="number" /></p> <p><label for="id_nationality">Nationality?</label> <input id="id_nationality" name="nationality" type="text" /></p> <p><label for="id_captcha_answer">2 + 2 =</label> <input id="id_captcha_answer" name="captcha_answer" type="number" /></p>
initial 参数让你指定渲染未绑定的表单中的字段时使用的初始值。
widget参数让你指定渲染表单时使用的Widget类,更多信息参见上面。
error_messages 参数让你覆盖字段引发的异常中的默认信息。传递的是一个字典,其键为你想覆盖的错误信息。
validators 参数让你可以为字段提供一个验证函数的列表。
localize 参数启用表单数据的本地化,包括输入和输出。
has_changed() 方法用于决定字段的值是否从初始值发送了变化。返回True或者False。
django表单验证中比较有用的就是clean和cleaned_data了。
clean是在is_valid()内部调用的,cleaned_data主要用来检查字段是否符合定义的格式,读取表单返回的值,如果是则返回类型为字段dict型。
比如email = cleaned_data['email'] 读取name为“email”的表单提交值,并赋予email变量。
cleaned_data中的值类型与字段定义的Field类型一致。如果字段定义charfield,那么clean方法返回的cleaned_data中对应的字段值就是字符型,定义为ModelChoiceField,那么cleaned_data中字段值是某个model实例。定义为ModelMultipleChoiceField,则cleaned_data中字段值是model实例list。
首先我们看一下下面的案例:
#/usr/bin/env python #-*- coding:utf-8 -*- from django.shortcuts import render # Create your views here. def user_list(request): host = request.POST.get('host') port = request.POST.get('port') mail = request.POST.get('mail') mobile = request.POST.get('mobile') #这里有个问题,如果,这个from表单有20个input,你在这里是不是的取20次? #验证: #输入不能为空,并且有的可以为空有的不可以为空 #如果email = 11123123 这样合法吗? #如果mobile = 11123123 这样合法吗? #如果ip = 11123123 这样合法吗? ''' 你在这里是不是需要做一大堆的输入验证啊?并且有很多这种页面会存在这种情况,如果每个函数都这样做估计就累死了 ''' return render(request,'user_list.html')
针对于上面的问题,如何解决呢?——那就是Form表单
通常提交表单数据就是由HTML表单向后台传递信息,后台通过request.GET() 或者 request.POST()获取。
建一个Blog项目,并在template下新建两个html页面,一个注册页面命名为register,一个欢迎页面为welcome。
1,创建project django-admin startproject Blog 2,创建APP python manage.py startapp user1 3,修改settings配置 在INSTALLED_APPS中添加APP:user1 在TEMPLATES中查看“DIRS”内容,如果有template,请保持原样,如果没有,则添加 'DIRS': [os.path.join(BASE_DIR, 'templates')]
项目目录如下:
给template下两个html页面register和welcome填充内容。
register.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>register</title> </head> <body> <form action="/user/register/" method="post"> {% csrf_token %} <p>用户名:<input type="text" name="username"></p> <p>密码: <input type="password" name="password"></p> <input type="submit" value="submit"> </form> </body> </html>
welcome.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>welcome</title> </head> <body> <h1>welcome to this page</h1> </body> </html>
在user1/models.py中创建表结构,代码如下:
from django.db import models # Create your models here. class BlogUser(models.Model): username = models.CharField(max_length=200, unique=True) password = models.CharField(max_length=200)
ChariField 字符串字段,用于较短的字符串,需要max_length来指定VARCHAR数据库字段的大小。
同时,修改Bolg/settings.py的内容:
找到下面这段代码,并注释掉: DATABASES = { 'default': { 'ENGINE': 'django.db.backends.sqlite3', 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), } } 然后写入下面代码: import pymysql pymysql.install_as_MySQLdb() DATABASES = { 'default': { # 这里可以指定使用的数据库类型,例如mysql 'ENGINE': 'django.db.backends.mysql', 'NAME': 'blog_demo', 'USER':'root', 'PASSWORD':'******', 'HOST':'localhost', 'PORT':'3306', } }
如果这里不熟悉请参考:Django连接MySQL数据库
并且,映射数据库,这一步不能忘记:
在终端创建表 1,生成同步数据库的代码: python manage.py makemigrations 2,同步数据库(也就是对数据库执行真正的迁移动作): python manage.py migrate
我们重构views.py中的代码
from django.shortcuts import render # Create your views here. from user1.models import BlogUser def register(request): if request.method =='GET': return render(request, 'register.html') elif request.method == 'POST': bloguser = BlogUser() bloguser.username = request.POST.get('username') bloguser.password = request.POST.get('password') bloguser.save() return render(request, 'welcome.html')
get() 中的username和password是取的register 里的username的值。以获取(request)注册的信息,保存为save()。
然后运行项目,并注册信息,代码如下:
1, 运行项目 python manage.py runserver 2,在浏览器中输入: http://127.0.0.1:8000/user/register/
然后我们注册信息,并提交。我们可以看到上面的register.html中用户名是唯一的,所以当我们注册相同的名称时候回报错500,并且传输失败。
下面在数据库看一下我们的注册信息
其中,3,5不显示,因为我输入了相同的用户名。如果输入不重复,则显示下面界面。
在上面的基础上,我们再实现一个表单流程,继续了解form表单的知识。
from django.shortcuts import render, HttpResponse # Create your views here. from django import forms class LoginForm(forms.Form): account = forms.CharField() password = forms.CharField() email = forms.CharField() def login(request): if request.method == "POST": form = LoginForm(request.POST) if form.is_valid(): return HttpResponse("登录成功") else: form = LoginForm() return render(request, 'user6/login.html',{'form':form})
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <div align="center" style="height: 400px; width: 100px;"> <form action="#" method="post"> {% csrf_token %} {{ form }} <button type="submit" id="sum">submit</button> </form> </div> </body> </html>
然后,去配置urls一些基本的配置(比如模板,路由等)。
我们直接点开login.html 内容如下:
我们打开Django项目,从127.0.0.1:8000/user6/login/ 进入,如下:
直接访问地址就显示出这样一个简单的界面,由HTML文件可以看到并没有js代码对数据有效性进行验证,我们随机输入账号,密码,邮箱,则提交,显示登陆成功。如下:
从上面的代码我们发现,前端一个 {{ form }} 就能做出一个完整强大的表单。但是我们只能用account password 做名称吗?
不是的,这里我们可以定制其名字,并且可以限制输入位数等等各种操作。
在form里有一个参数:error_messages 在他这里就可以定义报错内容
class UserInfo(forms.Form): # required是否可以为空,如果为False,则说明可以为空 email = forms.EmailField(required=False,error_messages={'required':u'邮箱不能为空'}) # 如果required 不写默认为Ture host = forms.CharField(error_messages={'required':u'主机不能为空'}) port = forms.CharField(error_messages={'required':u'端口不能为空'}) mobile = forms.CharField(error_messages={'required':u'手机不能为空'})
class UserInfo(forms.Form): # required是否可以为空,如果为False,则说明可以为空 email = forms.EmailField(required=False,error_messages={'required':u'邮箱不能为空'}) # 如果required 不写默认为Ture host = forms.CharField(error_messages={'required':u'主机不能为空'}) port = forms.CharField(error_messages={'required':u'端口不能为空'}) mobile = forms.CharField(error_messages={'required':u'手机不能为空'}, # 这里默认是TextInput 标签 widget = forms.TextInput(attrs={'class':'form-control', 'placeholder':u'手机号码'}))
class UserInfo(forms.Form): # required是否可以为空,如果为False,则说明可以为空 email = forms.EmailField(required=False,error_messages={'required':u'邮箱不能为空'}) # 如果required 不写默认为Ture host = forms.CharField(error_messages={'required':u'主机不能为空'}) port = forms.CharField(error_messages={'required':u'端口不能为空'}) mobile = forms.CharField(error_messages={'required':u'手机不能为空'}, # 这里默认是TextInput 标签 widget = forms.TextInput(attrs={'class':'form-control', 'placeholder':u'手机号码'})) # 我们再这里新增一个备份 memo = forms.CharField(required=False, widget=forms.Textarea(attrs={'class':'form-control','placeholder':u'备份'}))
同样的HTML代码也要新增:
<form action="/user_list/" method="post"> <p>主机 : {{ obj.host }}<span>{{ errors.host }}</span></p> <p>端口 : {{ obj.port }}<span>{{ errors.port }}</span></p> <p>邮箱 : {{ obj.email }}<span>{{ errors.email }}</span></p> <p>手机 : {{ obj.mobile }}<span>{{ errors.mobile }}</span></p> <p>备注:{{ obj.memo }}<span>{{ errors.memo }}</span></p> <input type="submit" value="submit"> </form>
import re from django import forms from django.core.exceptions import ValidationError def mobile_validate(value): # 正则匹配 mobile_re = re.compile(r'^(13[0-9]|15[0123456789]|18[0-9]|14[57])[0-9]{8}$') if not mobile_re.match(value): raise ValidationError("手机号码格式错误") class UserInfo(forms.Form): # required是否可以为空,如果为False,则说明可以为空 email = forms.EmailField(required=False, error_messages={'required': u'邮箱不能为空'}) host = forms.CharField(error_messages={'required': u'主机不能为空'}) port = forms.CharField(error_messages={'required': u'端口不能为空'}) mobile = forms.CharField(# 应用我们自己定义的规则 validators=[mobile_validate,], error_messages={'required': u'手机不能为空'}, # 这里默认使用TextInput 标签 widget=forms.TextInput(attrs={'class': 'form-control', 'placeholder': u'手机号码'})) # 我们新增一个备份 memo = forms.CharField(required=False, widget=forms.TextInput(attrs={'class': 'form-control', 'placeholder': u'备份'})) def user_list(request): # 创建了这个对象 obj = UserInfo() if request.method == 'POST': # 获取用户输入一句话就搞定 user_input_obj = UserInfo(request.POST) # 判断用户输入是否合法 if user_input_obj.is_valid(): # 获取用户输入 data = user_input_obj.clean() print(data) else: # 如果发生错误,捕获异常 # 这里原来什么都没写,默认是ul的样式,默认是as_ul(), # 如果我们写成as_data()返回的就是一个原生的字符串 # 还有一个as_json error_msg = user_input_obj.errors.as_data() # 然后把错误信息返回 print(error_msg) # 然后把对象传给html,在把错误信息传递过去 return render(request, 'user6/register.html', {'obj': obj, 'errors': error_msg,}) # 将对象传给html return render(request, 'user6/register.html', {'obj':obj,})
当我们输入不合法的时候,或者不是email格式的时候,会出现如下错误:
这样后端我们就有一套验证的机制了,就是通过is_valid()来判断用户输入是否合法!如果不合法就把返回的信息发送出去,如果合法获取数据,继续操作即可!
class UserInfo(forms.Form): user_type_choice = ( (0,u'普通用户'), (1,u'高级用户'), ) user_type = forms.IntegerField(widget=forms.widgets.Select(choices=user_type_choice, attrs={'class':'form-control'})) # required是否可以为空,如果为False,则说明可以为空 email = forms.EmailField(required=False,error_messages={'required':u'邮箱不能为空'}) # 如果required 不写默认为Ture host = forms.CharField(error_messages={'required':u'主机不能为空'}) port = forms.CharField(error_messages={'required':u'端口不能为空'}) mobile = forms.CharField(error_messages={'required':u'手机不能为空'}, # 这里默认是TextInput 标签 widget = forms.TextInput(attrs={'class':'form-control', 'placeholder':u'手机号码'})) # 我们再这里新增一个备份 memo = forms.CharField(required=False, widget=forms.Textarea(attrs={'class':'form-control','placeholder':u'备份'}))
HTML内更改如下:
<form action="/user_list/" method="post"> <p>用户类型:{{ obj.user_type }}<span>{{ errors.user_type }}</span></p> <p>主机 : {{ obj.host }}<span>{{ errors.host }}</span></p> <p>端口 : {{ obj.port }}<span>{{ errors.port }}</span></p> <p>邮箱 : {{ obj.email }}<span>{{ errors.email }}</span></p> <p>手机 : {{ obj.mobile }}<span>{{ errors.mobile }}</span></p> <p>备注:{{ obj.memo }}<span>{{ errors.memo }}</span></p> <input type="submit" value="submit"> </form>
这个后端验证是必须要有验证机制的,前端可以不写但是后端必须写,前端的JS是可以被禁用掉的,在实际的生产环境中比如登录和验证的时候,我们一般都使用 JQuery+AJAX 来判断用户的输入是否为空,假如JS被禁用(浏览器端可以设置禁用JS效果)的话,我们这个认证屏障是不是就消失了呢?(虽然一般不会禁用但是还是存在风险),所以我们一般做两种认证,在前端做一遍认证,在后端做一遍认证。
import re from django import forms from django.core.exceptions import ValidationError #自定义方法 def mobile_validate(value): mobile_re = re.compile(r'^(13[0-9]|15[012356789]|17[678]|18[0-9]|14[57])[0-9]{8}$') #正则匹配 if not mobile_re.match(value): raise ValidationError('手机号码格式错误') #如果没有匹配到主动出发一个错误 class UserInfo(forms.Form): user_type_choice = ( (0, u'普通用户'), (1, u'高级用户'),) user_type = forms.IntegerField(widget=forms.widgets.Select(choices=user_type_choice,attrs={'class':'form-control'})) email = forms.EmailField(required=True,error_messages={'required':u'邮箱不能为空'}) #required是否可以为空,如果为False说明可以为空 host = forms.CharField(error_messages={'required':u'主机不能为空'}) #如果required不写默认为Ture port = forms.CharField(error_messages={'required':u'端口不能为空'}) #默认mobile里有一个默认为空的机制,我们在原有的参数里增加怎们自定义的方法 mobile = forms.CharField(validators=[mobile_validate,],#应用咱们自己定义的规则 error_messages={'required':u'手机不能为空'}, widget=forms.TextInput(attrs={'class':'form-control','placeholder':u'手机号码'}) #这里默认是TextInput,标签 ) #咱们在新增一个备注 memo = forms.CharField(required=False, widget=forms.Textarea(attrs={'class':'form-control','placeholder':u'备注'})) def user_list(request): obj = UserInfo() #创建了这个对象 if request.method == 'POST': #获取用户输入一句话就搞定 user_input_obj = UserInfo(request.POST) if user_input_obj.is_valid(): #判断用户输入是否合法 data = user_input_obj.clean() #获取用户输入 print data else: #如果发生错误,捕捉错误 error_msg = user_input_obj.errors.as_data()#这里原来什么都没写,默认是ul的样式,默认是as_ul(),如果我们写成as_data()返回的就是一个原生的字符串 #还有一个as_json print error_msg #打印一下然后看下他的类型 #然后把错误信息返回 return render(request,'user_list.html',{'obj':obj,'errors':error_msg,})#然后把对象传给html,在把错误信息传递过去 return render(request,'user_list.html',{'obj':obj,})#然后把对象传给html
def clean_username(self): # 函数名必须已clean_字段名的格式 user = self.cleaned_data.get("username") if not User.objects.filter(username=user): if not user.isdigit(): if re.findall(r"^[A-Za-z0-9_\-\u4e00-\u9fa5]+$",user): return user # 通过检测,原数据返回 self.cleaned_data.get("username") else: raise ValidationError('用户名存在非法字符') # 没通过检测抛出错误,必须用ValidationError else: raise ValidationError("用户名不能为纯数字") else: raise ValidationError("该用户名已存在")
def clean(self): # 必须命名为clean # 判断是否都通过检测,都不为None if self.cleaned_data.get("password") and self.cleaned_data.get("check_pwd"): if self.cleaned_data.get("password") == self.cleaned_data.get("check_pwd"): return self.cleaned_data # 如果两次密码相同,返回干净的字典数据 else: raise ValidationError("输入密码不一致") # 没通过检测返回异常信息 else: return self.cleaned_data
参考文献:https://blog.csdn.net/qq_42068900/article/details/80904596
https://blog.csdn.net/qq_37049781/article/details/79283547
本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系我们删除。