重庆分公司,新征程启航
为企业提供网站建设、域名注册、服务器等服务
上一节我们讲完了ModelAdmin的使用, 但是在操作中也发现, 新增编辑会员时, 我们无法验证数据是否正确, 比如
五通桥ssl适用于网站、小程序/APP、API接口等需要进行数据传输应用场景,ssl证书未来市场广阔!成为创新互联的ssl证书销售渠道,可以享受市场价格4-6折优惠!如果有意向欢迎电话联系或者加微信:13518219792(备注:SSL证书合作)期待与您的合作!
这些要求, 我们就必须得使用自定义的表单来完成了
项目地址:https://gitee.com/ccnv07/django_example
通过表单, 我们可以实现以下的功能
关于表单的代码我们一般放在每个模块的forms.py中
CharField
单行文本输入字段, 对应模型的CharField字段
表单中的样式就是input type=textmax_length
: 最大长度min_length
: 最小长度strip
: 是否过滤左右的空格empty_value
: 为空时的值, 默认是空字符串
EmailField
邮箱输入文本字段, 对应模型的EmailField字段
标案中是input type=text
但是会自动增加一个邮箱格式的校验
ChoiceField
下拉单选字段, 这个在模型中是没有的
对应表单的select标签choices
参数也是二维元组的格式
BooleanField
选择字段, 对应表单中的checkbox
DateField
日期选择字段input_formats
: 定义时间格式, 默认是:
['%Y-%m-%d', # '2006-10-25'
'%m/%d/%Y', # '10/25/2006'
'%m/%d/%y'] # '10/25/06'
DateTimeField
日期时间字段, 同DateField
TimeField
时间字段, 同DateField
DecimalField
十进制数字字段max_value
: 最大值min_value
: 最小值max_digits
: 前置0被去除后的最大位数decimal_places
: 允许的小数位的长度
FileField
文件上传字段
IntegerField
整数字段max_value
: 最大值min_value
: 最小值
剩下还有很多表单字段类型, 在之后的教程中再继续介绍, 目前这些, 就算是比较常见的字段了
通过介绍以后, 大家发现, 我没说最常见的PasswordField, 其实django的表单中就没有这个字段类型, 那么, 如何实现密码字段呢?
password = forms.CharField(widget=forms.PasswordInput(),max_length=12,min_length=6, strip=True,
help_text='编辑时为空则不更改密码')
通过制定widget参数为forms.PasswordInput(), 就可以实现密码字段了
所以其实并不是Form类来规定表单字段的类型, 而是widget来实现的表单字段的类型, 对于每种表单字段的类型, django都有对应的模板, 通过字段的参数, 生成对应的html代码
label
: 表单字段的label标签的名称widget
: 指定此字段采用的字段样式类型help_text
: 字段的帮助文本
只有ModelForm才有元类, Form是不需要元类的
以下是一个元类的例子
from django.forms import ModelForm
class Meta:
model = Account
# 使用自定义的Form, 就必须指定fields or exclude属性, 否则报错
fields = ('account', 'password', 'email', 'phone', 'status')
error_messages = {
NON_FIELD_ERRORS: {
'unique_together': "%(model_name)s's %(field_labels)s are not unique.",
}
}
model
: 指定ModelForm绑定的模型fields
: 指定后台新增编辑时要显示的字段error_messages
: 指定通用的错误信息文案
其他还有一些复杂的操作, 会在之后的教程中详细讲解。现在我们的一个表单就完成了
但是如果想让表单在后台中生效, 就需要把表单绑定到ModelAdmin上
from django.contrib import admin
from .forms import AccountForm
@admin.register(Account)
class AccountAdmin(admin.ModelAdmin):
form = AccountForm
在ModelAdmin中指定form参数, 就可以把表单绑定上去了。 点击新增/编辑页面, 也就可以看到表单生效了
接下来, 我们就需要完成表单提交数据以后的验证的方法了
其实在定义表单字段时, 我们就已经完成了一部分的验证了
比如
account = forms.CharField(
required=True, error_messages={
'required': '请输入用户名',
}, label='用户名')
就定义了account字段必须填写, 如果出错则返回“请输入用户名”的提示
但是这个并不能完成我们所有的验证, 所以我们也可以根据字段进行自定义的验证
比如, 我要实现account用户名字段是唯一的
from django import forms
from django.core.exceptions import ValidationError
from .models import Account
class AccountForm(forms.ModelForm):
...省略代码
def clean_account(self):
_info = Account.objects.filter(account=self.cleaned_data['account'],
is_deleted=0).values('id')
if _info:
raise ValidationError('用户已存在')
return self.cleaned_data['account']
当我们执行form.is_valid()方法进行验证时, django的form类会依次执行clean_字段名的自定义验证方法, 如果有抛出异常(raise ValidationError('用户已存在')), 则中断并返回错误, 否则读取到clean_字段名的方法的返回值, 并且写入到cleaned_data这个字典中
根据同样的方法, 我们也可以写出来对email和phone字段的验证
以上已经基本能实现我们的功能了, 但是在后台的操作中, 新增和编辑用的是同一个表单, 如果我们需要针对新增和编辑的不同场景, 进行一些不同的操作, 就比较麻烦了。
所以我们需要通过在ModelAdmin中, 对Form进行一定的操作, 似的Form可以完成更多的判断
# account/admin.py
class AccountAdmin(admin.ModelAdmin):
def get_form(self, request, obj=None, **kwargs):
form = super(AccountAdmin, self).get_form(request, obj=obj, **kwargs)
# obj 保存的是models.Account的信息
# 根据是否有pk, 来赋予form不同的场景, 根据不同的场景可以进行不同的验证
if (obj is not None):
form.id = obj.pk
form.scene = 'update'
else:
form.scene = 'insert'
return form
get_form方法的参数request
保存的是HttpRequest操作对象
而obj是当前操作的数据模型对象(Model), 而且在新增的操作时, obj是None, 只有在编辑时, 才存在obj
form = super(AccountAdmin, self).get_form(request, obj=obj, **kwargs)
会返回当前操作的form对象
如果obj不是None, 则当前操作是编辑, 我们就可以给form增加一个自定义的属性scene(场景) = 'update', 否则就是新增
并且我们在Form表单中, 可以针对不同的场景, 进行不同的验证判断
# account/forms.py
class AccountForm(forms.ModelForm):
def clean_account(self):
# 自动验证account字段
if self.scene == 'insert':
_info = Account.objects.filter(
account=self.cleaned_data['account'],
is_deleted=0).values('id')
elif self.scene == 'update':
_info = Account.objects.filter(~Q(id=self.id) & Q(
account=self.cleaned_data['account']) & Q(
is_deleted=0)).values('id')
if _info:
raise ValidationError('用户已存在')
return self.cleaned_data['account']
这个验证的意思是, 如果当前场景(self.scene)是insert, 就只按照account查询, 如果是update, 则增加id不为当前操作id的过滤条件
django.contrib.auth.hashers 有两个关于密码加密的操作方法
make_password和check_password
make_password 是将指定的明文密码加密
check_password 是校验给出的明文密码是否正确
当新增用户时, 密码框必填, 当编辑时, 密码框非必填。如果填了, 则修改密码, 如果没填, 则不更改密码
第一步, 我们在校验输入的密码时, 同时也需要实现对密码的加密(毕竟数据库被人破解了, 后台还是很严重的)
from django import forms
class AccountForm(forms.ModelForm):
def clean_password(self):
# 自动验证密码字段
if self.scene == 'insert':
if not self.cleaned_data['password']:
raise ValidationError('请输入密码')
elif self.scene == 'update':
if not self.cleaned_data['password']:
return None
else:
return self.cleaned_data['password']
return make_password(self.cleaned_data['password'])
这个也很好理解, 如果新增用户时, 未输入密码, 则返回错误
更新时没有输入密码, 则返回None
如果输入, 就返回make_password加密后的密码字符串
根据之前教程的scene场景参数的指定, 就可以跟容易的实现这个功能, 但是在编辑时, 如果密码为空不填的话, 密码居然也会被设置为空。
这个是因为, 如果不填写密码, 的model对象会把password=None一直带着, 转换为sql执行时, 就变成password=''了
所以, 如果password没有输入值, 我们就要在执行保存之前, 干掉model携带的password, 这样才正确。
重写ModelAdmin的保存方法
class AccountAdmin(admin.ModelAdmin):
def save_model(self, request, obj, form, change):
if form.cleaned_data['password'] is None:
del obj.password
super().save_model(request, obj, form, change)
form.cleaned_data中就是表单提交后验证过的数据, 如果password是None, 就del掉, 然后调用父类的save_model方法, 继续执行保存操作。