尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

Django Form 组件详解:从表单生成到 ModelForm 数据校验

Django Form 组件详解:从表单生成到 ModelForm 数据校验 一、为什么需要 Form 组件在没有 Form 组件时我们需要手写 HTML 表单、读取request.POST并为每个字段编写长度、格式和必填校验。字段一多模板和视图就会重复很多逻辑。Django 的 Form 组件把“字段定义、HTML 生成、数据清洗、错误提示”集中到一个 Python 类中。它既可以生成表单控件也可以在服务端统一校验用户提交的数据。需要注意浏览器端的校验只能改善交互体验真正可靠的校验必须在服务端执行。下面是手写 HTML 表单的例子。它能展示页面但不会自动完成 Django 的数据校验。formactionmethodpostdivclassform-grouplabelforinputUsername用户名/labelinputtypetextnameusernameidinputUsername/divdivclassform-grouplabelforinputPassword密码/labelinputtypepasswordnamepasswordidinputPassword/divbuttontypesubmit提交/button/form二、创建并使用一个 Form通常将表单类放在应用的forms.py中。forms.Form不会自动创建数据库表它只描述当前页面需要的字段及校验规则。1. 定义表单类# users/forms.pyfromdjangoimportformsclassRegisterForm(forms.Form):usernameforms.CharField(max_length8,min_length3,label用户名)passwordforms.CharField(max_length6,min_length2,label密码,widgetforms.PasswordInput,)emailforms.EmailField(label邮箱)代码中的每个字段都会对应一个 HTML 控件。max_length和min_length限制的是用户提交到表单中的字符长度它们与模型字段的数据库长度约束不是同一个概念。2. 在视图中绑定数据# users/views.pyfromdjango.shortcutsimportrenderfromdjango.viewsimportViewfrom.formsimportRegisterFormclassRegisterView(View):defget(self,request,*args,**kwargs):# 未提交数据时创建未绑定表单用于展示空表单。formRegisterForm()returnrender(request,register.html,{form:form})defpost(self,request,*args,**kwargs):# 传入 request.POST 后表单成为“已绑定”状态可以执行校验。formRegisterForm(request.POST)ifform.is_valid():# cleaned_data 是经过类型转换和校验后的可信数据。dataform.cleaned_data# 在这里执行创建用户等业务操作。returnrender(request,register_success.html,{data:data})# 校验失败时把同一个 form 返回模板用户输入和错误信息都会保留。returnrender(request,register.html,{form:form})is_valid()会触发所有字段的内置校验和自定义校验。只有返回True时才应该使用cleaned_data失败时应将表单重新渲染让用户看到具体错误。3. 在模板中输出表单formactionmethodpost{% csrf_token %} {{ form.as_p }}buttontypesubmit提交/button/formPOST表单通常需要{% csrf_token %}否则 Django 的 CSRF 中间件会拒绝请求。{{ form.as_p }}只是 Django 提供的一种快速布局方式实际项目也可以逐字段编写模板以获得更精细的样式和错误展示。三、常用字段参数与控件字段参数决定了表单的显示方式和校验行为常用参数如下fromdjangoimportformsclassProfileForm(forms.Form):usernameforms.CharField(max_length20,min_length3,requiredTrue,# 是否必填默认 Trueinitialdream,# 未绑定表单的初始值label用户名,# 标签文字label_suffix:,# 标签后缀help_text320 个字符,error_messages{required:用户名不能为空,max_length:用户名不能超过 20 个字符,},widgetforms.TextInput(attrs{class:form-control,placeholder:请输入用户名,}),)passwordforms.CharField(label密码,widgetforms.PasswordInput(attrs{class:form-control}),)genderforms.ChoiceField(label性别,choices((0,女),(1,男)),widgetforms.RadioSelect,)cityforms.ChoiceField(label城市,choices((sh,上海),(bj,北京)),)hobbiesforms.MultipleChoiceField(label爱好,choices((read,阅读),(sport,运动)),widgetforms.CheckboxSelectMultiple,requiredFalse,)widget负责生成 HTML 控件attrs可以为控件添加class、placeholder等属性。ChoiceField适合单选或下拉框MultipleChoiceField适合多选。提交的数据通常是字符串业务代码需要根据实际需求进行类型转换。常见字段速查CharField 文本支持 max_length、min_length IntegerField 整数支持 min_value、max_value FloatField / DecimalField 小数DecimalField 可限制总位数和小数位 DateField / TimeField 日期或时间 EmailField 邮箱格式 URLField URL 格式 BooleanField 布尔值 ChoiceField 单选或下拉选项 MultipleChoiceField 多选选项 FileField 文件上传 ImageField 图片上传需要 Pillow ModelChoiceField 从 QuerySet 中选择一个模型对象 ModelMultipleChoiceField 从 QuerySet 中选择多个模型对象文件上传必须同时满足两个条件HTML 表单设置enctypemultipart/form-data视图绑定request.FILES。classUploadForm(forms.Form):fileforms.FileField()formUploadForm(request.POST,request.FILES)四、Form 的几种渲染方式Django 可以快速把整个表单渲染成不同的 HTML 结构{{ form }}!-- 默认结构通常是 div 或段落结构 --{{ form.as_p }}!-- 每个字段放入 p 标签 --{{ form.as_ul }}!-- 每个字段放入 li需要 ul/ol 包裹 --{{ form.as_table }}!-- 每个字段放入 tr需要 table 包裹 --需要完全控制布局时可以遍历字段。这样既能输出控件也能分别处理标签、帮助文本和错误信息。{% for field in form %}divclassfield{{ field.label_tag }} {{ field }} {% if field.help_text %}small{{ field.help_text }}/small{% endif %} {% for error in field.errors %}pclasserror{{ error }}/p{% endfor %}/div{% endfor %}如果需要暂时关闭浏览器原生校验可以在form上添加novalidate。这不会关闭 Django 的服务端校验formmethodpostnovalidate五、钩子函数补充业务校验字段的max_length、EmailField等规则只能解决通用问题。对于“用户名必须以nb_开头”“两次密码必须一致”等业务规则可以使用清洗钩子clean_字段名()局部钩子只校验一个字段。clean()全局钩子适合比较多个字段。不要把onBlur、onSubmit等前端事件称为 Django Form 钩子它们属于 JavaScript 交互与服务端的clean_*、clean不同。fromdjangoimportformsclassLoginForm(forms.Form):usernameforms.CharField(max_length20,label用户名)passwordforms.CharField(widgetforms.PasswordInput,label密码)confirm_passwordforms.CharField(widgetforms.PasswordInput,label确认密码,)defclean_username(self):usernameself.cleaned_data[username]ifnotusername.startswith(nb_):raiseforms.ValidationError(用户名必须以 nb_ 开头)returnusernamedefclean(self):cleaned_datasuper().clean()passwordcleaned_data.get(password)confirm_passwordcleaned_data.get(confirm_password)ifpasswordandconfirm_passwordandpassword!confirm_password:self.add_error(confirm_password,两次密码不一致)returncleaned_data局部钩子返回字段值才能继续把它放入cleaned_data。全局钩子应先调用super().clean()这样才能拿到已经通过字段校验的数据。字段不存在或字段校验失败时使用.get()可以避免额外的KeyError。六、ModelForm让表单复用模型定义ModelForm是Form的扩展。它可以根据模型字段自动生成表单字段并在校验通过后直接保存模型适合新增和编辑数据的场景。模型字段的数据库约束仍然需要正确设置表单校验不能替代数据库约束。1. 模型与 ModelForm# users/models.pyfromdjango.dbimportmodelsclassUser(models.Model):usernamemodels.CharField(max_length32)passwordmodels.CharField(max_length128)# users/forms.pyfromdjangoimportformsfrom.modelsimportUserclassUserModelForm(forms.ModelForm):# 手动声明的字段会覆盖模型自动生成的同名字段。usernameforms.CharField(max_length20,min_length3,label用户名,widgetforms.TextInput(attrs{class:form-control}),)classMeta:modelUser# fields 与 exclude 二选一不能同时配置。fields[username,password]fields __all__会包含模型的全部字段但不建议在用户注册等敏感场景中滥用否则模型新增字段后可能被意外暴露。更稳妥的做法是显式列出允许提交的字段。2. 校验、保存与编辑classUserCreateView(View):defpost(self,request,*args,**kwargs):formUserModelForm(request.POST)ifform.is_valid():userform.save()# 校验后的数据写入数据库returnrender(request,success.html,{user:user})returnrender(request,user_form.html,{form:form})# 编辑已有对象时传入 instance提交后 form.save() 会执行更新而不是新增。formUserModelForm(request.POSTorNone,instanceuser)如果需要先补充字段或执行额外业务逻辑可以使用commitFalse完成修改后再保存fromdjango.contrib.auth.hashersimportmake_password userform.save(commitFalse)user.passwordmake_password(user.password)user.save()七、总结Form适合独立于数据库的表单ModelForm适合直接对应模型的数据录入和编辑。未绑定表单用于展示绑定request.POST后才能校验文件上传还要绑定request.FILES。is_valid()成功后再读取cleaned_data失败时把原表单返回模板以显示错误。clean_字段名()用于单字段规则clean()用于跨字段规则。快速渲染适合简单页面复杂页面应逐字段渲染并明确展示错误信息。
返回列表