Django的model form组件
简介:这个组件的功能就是把model和form组合起来,先来一个简单的例子来看一下这个东西怎么用:比如我们的数据库中有这样一张学生表,字段有姓名,年龄,爱好,邮箱,电话,住址,等等一大堆信息,现在让你写一个创建学生的页面,你应该怎么写呢?首先我们会在前端一个一个罗列出这些字段,让用户去填写,然后我们从后台一个一个接收用户的输入,创建一个新的学生对象,保存其实,重点不是这些,而是合法性验证,我们需要在前端判断用户输入是否合法,比如姓名必须在多少字符以内,电话号码必须是多少位的数字,邮箱必须是邮箱的格式,这些当然可以一点一点手动写限制,各种判断,这毫无问题,除了麻烦我们现在有个更优雅的方法:那就是通过ModelForm来实现。
model form的使用
下面将使用model form实现用户注册功能:
models.py
from django.contrib.auth.models import AbstractUser class UserInfo(AbstractUser): #相当于继承了django的auth_user表 tel = models.CharField(max_length=32)
注意settings里面要配置:
AUTH_USER_MODEL = 'app01.UserInfo'
form.py
1 from django import forms 2 from app01.models import UserInfo 3 from django.forms import widgets as wid 4 from django.core.exceptions import ValidationError 5 6 class UserModelForm(forms.ModelForm): 7 8 r_pwd = forms.CharField(label='确认密码',widget=wid.PasswordInput(attrs={'class':'input','id':'r_pwd'}),error_messages={'required': '不能为空'}) 9 class Meta: 10 model = UserInfo #对应的model中的类 11 #fields = '__all__' #表示列出所有的字段 12 fields = ['username', 'password','r_pwd', 'email', ] #选择性列出列表中选中的字段 13 exclude = None #排除的字段 14 15 #自定义在前段显示的文本 16 labels = { 17 'password': '密码', 18 'username': '用户名', 19 'email': '邮箱' 20 } 21 22 #自定义属性 23 widgets = { 24 'username': wid.TextInput(attrs={'class': 'input', 'id': 'username'}), 25 'password': wid.PasswordInput(attrs={'class': 'input', 'id': 'password'}), 26 'email': wid.EmailInput(attrs={'class': 'input', 'id': 'email'}) 27 } 28 29 #自定义错误信息 30 error_messages = { 31 "username":{"required":"不能为空"}, 32 "password":{"required":"不能为空"}, 33 "email":{"required":"不能为空",'invalid':'邮箱格式错误'} 34 } 35 36 37 # 局部钩子,进行第二次过滤 38 def clean_username(self): 39 val = self.cleaned_data.get('username') 40 ret = UserInfo.objects.filter(username=val).first() 41 if not ret: # 数据库找不到这个用户 42 return val 43 else: 44 raise ValidationError('用户已存在!') 45 46 def clean_password(self): 47 val = self.cleaned_data.get('password') 48 if val.isdigit(): 49 raise ValidationError('密码不能是纯数字!') 50 else: 51 return val 52 53 def clean_email(self): 54 val = self.cleaned_data.get('email') 55 print('邮箱',val) 56 if not val.endswith('163.com'): 57 raise ValidationError('邮箱必须是163邮箱!') 58 else: 59 return val 60 61 #定义全局钩子 62 def clean(self): 63 pwd=self.cleaned_data.get('password') 64 r_pwd=self.cleaned_data.get('r_pwd') 65 if pwd and r_pwd and pwd != r_pwd: 66 self.add_error('r_pwd',ValidationError('两次密码不一致!')) #给全局钩子自定义一个名称,不在是统一的__all__ 67 else: 68 return self.cleaned_data
字段类型
生成的 Form
类将按照 fields
属性中指定的顺序为每个指定的模型字段设置一个表单字段。
每个模型字段都有一个对应的默认表单字段。例如,模型中的 CharField
在表单中被表现为 CharField
。 ManyToManyField
则表现为 MultipleChoiceField
。以下是完整的转化清单:
模型字段 | 表单字段 |
---|---|
AutoField |
不呈现在表单中 |
BigAutoField |
不呈现在表单中 |
BigIntegerField |
IntegerField 将 min_value 设置为-9223372036854775808,将 max_value 设置为9223372036854775807。 |
BooleanField |
BooleanField |
CharField |
CharField 将 max_length 设置为模型字段的 max_length ,如果模型中设置了 null=True ,会将 empty_value 设置为 None 。 |
DateField |
DateField |
DateTimeField |
DateTimeField |
DecimalField |
DecimalField |
EmailField |
EmailField |
FileField |
FileField |
FilePathField |
FilePathField |
FloatField |
FloatField |
ForeignKey |
ModelChoiceField (见下文) |
ImageField |
ImageField |
IntegerField |
IntegerField |
IPAddressField |
IPAddressField |
GenericIPAddressField |
GenericIPAddressField |
ManyToManyField |
ModelMultipleChoiceField (见下文) |
NullBooleanField |
NullBooleanField |
PositiveIntegerField |
IntegerField |
PositiveSmallIntegerField |
IntegerField |
SlugField |
SlugField |
SmallIntegerField |
IntegerField |
TextField |
CharField 设置中 widget=forms.Textarea |
TimeField |
TimeField |
URLField |
URLField |
如您所料, ForeignKey
和 ManyToManyField
模型字段类型是特殊情况:
ForeignKey
由django.forms.ModelChoiceField
表示, 它是一个ChoiceField
,其选项是一个模型的QuerySet
。ManyToManyField
由django.forms.ModelMultipleChoiceField
表示,它是一个MultipleChoiceField
,其选项为一个模型QuerySet
。
另外,每个生成的表单字段的属性设置如下:
- 如果模型字段设置了
blank=True
,那么表单字段的required
属性被设置为False
,否则required=True
。 - 表单字段的
label
设置为模型字段的verbose_name
,并且首字母大写。 - 表单字段的
help_text
设置为模型字段的help_text
。 - 如果模型字段设置了
choices
,那么表单字段的widget
会被设置为Select
,其选项来自模型字段的choices
。这些选项通常包含一个默认选中的空选项。如果字段设置了必填,则会强制用户进行选择。如果模型字段设置了blank=False
以及一个明确的default
值,则表单字段中不会包含空选项(默认会选中default
值)。
views.py 逻辑代码
1 from django.http import JsonResponse 2 from app01.form import UserModelForm 3 4 def register(request): 5 6 7 if request.method == 'GET': 8 form = UserModelForm() #form为UserModelForm的实例化对象 9 return render(request, 'reg.html',locals()) 10 11 12 else: 13 form = UserModelForm(request.POST) 14 res = {'user':None,'err_msg':''} 15 16 if form.is_valid(): #对数据进行校验 17 user = form.cleaned_data.get('username') #form.cleaned_data为一个字典,里面存储着符合条件的字段 18 res['user'] = user 19 user_obj = form.save() #相当于create操作 20 21 else: 22 errors=form.errors #form.errors为一个字典,键是错误的字段,值是相关的错误的信息 23 res['err_msg'] = errors 24 return JsonResponse(res)
模板:reg.html
{% for field in form %} <div class="group"> <label>{{ field.label }}</label> {{ field }}<span class="error"></span> </div> {% endfor %} <div class="group"> <input type="submit" class="button" value="注册"id="zhuce"> </div> <script src="/static/js/jquery.js"></script> <script> $('#zhuce').click(function () { $.ajax({ url: '/register/', type: 'post', data: { username: $('#username').val(), password: $('#password').val(), r_pwd: $('#r_pwd').val(), email: $('#email').val() }, success: function (response) { if (response.user) { location.href = '/books/' } else { $('.error').html(''); $.each(response.err_msg,function (i,j) { $('#'+i).next().html(j[0]) }); } } }) }); </script>
总结: 从上边可以看到ModelForm用起来是非常方便的,比如增加修改之类的操作。但是也带来额外不好的地方,model和form之间耦合了。如果不耦合的话,mf.save()方法也无法直接提交保存。 但是耦合的话使用场景通常局限用于小程序,写大程序就最好不用了。
添加记录方法:
form = UserModelForm(request.POST) if form.is_valid(): #对数据进行校验 user_obj = form.save() #相当于create操作
编辑记录方法:
form=BookModelForm(request.POST,instance=edit_book) if form.is_valid(): form.save() # update操作 ; edit_book.update(**cleandata) return redirect("/books/")
如果不用ModelForm,编辑的时候得显示之前的数据吧,还得挨个取一遍值,如果ModelForm,只需要加一个instance=obj(obj是要修改的数据库的一条数据的对象)就可以得到同样的效果
保存的时候要注意,一定要注意有这个对象(instance=obj),否则不知道更新哪一个数据