Django框架(十)--ORM多对多关联关系三种创建方式、form组件
多对多的三种创建方式
1.全自动(就是平常我们创建表多对多关系的方式)
class Book(models.Model): title = models.CharField(max_length=32) price = models.DecimalField(max_digits=8,decimal_places=2) authors = models.ManyToManyField(to='Author') class Author(models.Model): name = models.CharField(max_length=32)
优势:不需要你手动创建第三张表
不足:由于第三张表不是你手动创建的,也就意味着第三张表字段是固定的无法做扩展
2.纯手动(手动建关系表)
class Book(models.Model): title = models.CharField(max_length=32) price = models.DecimalField(max_digits=8,decimal_places=2) class Author(models.Model): name = models.CharField(max_length=32) class Book2Author(models.Model): book = models.ForeignKey(to='Book') author = models.ForeignKey(to='Author') create_time = models.DateField(auto_now_add=True)
优势:第三张可以任意的扩展字段
不足:orm查询不方便
3.半自动(推荐使用)
class Book(models.Model): title = models.CharField(max_length=32) price = models.DecimalField(max_digits=8,decimal_places=2) authors = models.ManyToManyField(to='Author',through='Book2Author',through_fields=('book','author')) # through 告诉django orm 书籍表和作者表的多对多关系是通过Book2Author来记录的 # through_fields 告诉django orm记录关系时用过Book2Author表中的book字段和author字段来记录的 """ 多对多字段的 add set remove clear不支持 """ class Author(models.Model): name = models.CharField(max_length=32) # books = models.ManyToManyField(to='Book', through='Book2Author', through_fields=('author', 'book')) #手动创建的第三张表 class Book2Author(models.Model): book = models.ForeignKey(to='Book') author = models.ForeignKey(to='Author') create_time = models.DateField(auto_now_add=True)
优点:即不用自己创建第三张表了,也可以任意扩展字段了 缺点:这种多对多关系中不能使用add/set/remove/clear这些方法了
form组件
一、什么是form组件,可以干什么
1.forms组件就是一个类,可以检测前端传来的数据,是否合法。
例如,前端传来的邮箱数据,判断邮件格式对不对,用户名中不能以什么开头,等等 >>>校验数据
2.还可以前端页面搭建 >>> 渲染页面
3.展示错误信息 >>> 渲染错误信息
二、form组件的使用
1.使用语法
from django.shortcuts import render, HttpResponse from django import forms #1.先写一个类,继承Form class MyForm(forms.Form): #定义属性,用来校验 #限制最大长度为8,最小长度为3 name = forms.CharField(max_length=8,min_length=3) pwd = forms.CharField(max_length=8,min_length=3) #校验邮箱 email = forms.EmailField() #2.在视图函数中使用MyForm来校验数据 #实例化产生对象,将需要校验的数据传入(可以传字典,可以不传),这个数据是前端传递过来的数据 myform = MyForm(request.POST) #3.校验,is_valid如果是true表示校验成功(满足myform里面的所有条件),反之,验证失败 if myform.is_valid(): print(myform.cleaned_data) #打印校验通过的数据 return HttpResponse('校验成功') else: #校验失败的信息 是一个字典,它是所有错误信息 {错误字段名:[错误原因]} print(myform.errors) #errors是一个列表,表示每个字段的错误信息 return HttpResponse('校验失败')
方法总结:
clean_data 验证通过的数据
errors 错误数据的对象
errors.as_data 错误数据的信息
注意事项
1.自定义的类中所有的字段默认都是必须要传值的
2.可以额外传入类中没有定义的字段名,forms组件不会去校验,也就是多传没有关系
3.实例化时,传入必须是字典,也可以不传
form_obj = views.LoginForm({'username':'jason','password':'123456','email':'123@qq.com'}) form_obj.is_valid() True form_obj = views.LoginForm({'username':'jason','password':'123456'}) form_obj.is_valid() False form_obj = views.LoginForm({'username':'jason','password':'123456','email':'123@qq.com','hobby':'read'}) form_obj.is_valid() True
2.组件的参数及其他操作方式
max_length #代表该字段最长可以为多少 min_length #代表该字段最短可以为多少 error_messages #设置错误信息的属性 required #默认值为True,意思是你前端传来的字段必须有它,没有的话校验失败 label #注释信息label ='用户名' 在前端渲染可以直接对象.label获取值 initial #设置默认值
widget #控制标签属性和样式 widget = widgets.PasswordInput() #你在模板渲染的时候,就会渲染成Input框,password样式 控制标签属性 widget = widgets.PasswordInput(attrs={'class':'form-control c1 c2','username':'jason'})
#例子 pwd = forms.CharField(max_length=8,min_length=3,required=True,label='密码', error_messages = {'max_length':'最长是8','min_length':'最短是3','required':'这个必须填'} )
其他操作方式
# 单选的radio框 gender = forms.ChoiceField( choices=((1, "男"), (2, "女"), (3, "保密")), label="性别", initial=3, widget=forms.widgets.RadioSelect() ) # 单选select hobby = forms.ChoiceField( choices=((1, "篮球"), (2, "足球"), (3, "双色球"),), label="爱好", initial=3, widget=forms.widgets.Select() ) # 多选的select框 hobby1 = forms.MultipleChoiceField( choices=((1, "篮球"), (2, "足球"), (3, "双色球"),), label="爱好", initial=[1, 3], widget=forms.widgets.SelectMultiple() ) # 单选的checkbox keep = forms.ChoiceField( label="是否记住密码", initial="checked", widget=forms.widgets.CheckboxInput() ) # 多选的checkbox hobby2 = forms.MultipleChoiceField( choices=((1, "篮球"), (2, "足球"), (3, "双色球"),), label="爱好", initial=[1, 3], widget=forms.widgets.CheckboxSelectMultiple() )
三、渲染页面、渲染错误信息
#form组件可以在视图函数中使用,也可以在模板中使用 #视图层 def reg(request): myform = MyForm()
if request.method == 'POST':
myform = MyForm(request.POST)
print(request.POST) #<QueryDict: {'name': ['小张'], 'pwd': ['123123'], 'email': ['119@qq.com']}> return render(request,'reg.html',locals()) #把myform对象传到前端页面了
#模板层 #第一种渲染方式(封装程度太高,一般只用于本地测试,通常不适用) <form action='' method='post'> {{ myform.as_p }} {{ myform.as_ul }} {{ myform.as_table}} <input type='submit' value = '提交'></input> </form> #第二种渲染页面的方式(可扩展性高,书写麻烦) <form action='' method='post'> {{myform.name.label}}:{{myform:name}} {{myform.pwd.label}}:{{myform:pwd}} {{myform.email.label}}:{{myform:email}} <input type='submit' value = '提交'></input> </form> #第三种渲染方式(推荐) <form action='' method='post'> {% for foo in myform %} <p> {{ foo.lable }} : {{ foo }} <span>{{ foo.errors.0 }}</span> //注意后面加0 错误信息 </p> <input type='submit' value = '提交'></input> </form>
注意事项:
1.forms组件在帮你渲染页面的时候 只会渲染获取用户输入的标签 提交按钮需要你手动添加
2.input框的label注释 不指定的情况下 默认用的类中字段的首字母大写
四、钩子函数(HOOK)
forms组件暴露给用户,可以自定义的校验规则 用法:在自定义的form类中定义一个函数即可
1.局部钩子(针对某一个字段做额外的校验)
名字叫:clean_字段名,内部,取出该字段进行校验,如果通过,将该字段返回,如果失败,抛异常
#函数名:clean_字段名字 def clean_name(self): #self是当前form对象 name = self.cleaned_data.get('name') #从验证通过的数据中再次筛选 if '666' in username: #失败,抛异常,把异常信息以('key','value')形式写入add_error中 self.add_error('name','不能包含666') #正常,把name返回到clean_data,将name写入clean_data字典中 return name
注意点:
- 这些函数是写在自定义类form里面的,需要校验哪个字段就以 clean_字段名写一个校验函数
- 校验失败,通过add_error('字段名','错误信息'),以{key:value}形式写入到errors字典中
- 校验成功,返回name到clean_data,写入clean_data字典中
2.全局钩子(针对多个字段做额外的校验)
#重写clean方法 def clean(self): #能走到这步,说明前面的校验已经通过了,下面校验两次两次密码是否输入一致 pwd = self.clean_data.get('pwd') re_pwd = self.clean_data.get('re_pwd') if pwd == re_pwd: #校验成功,直接返回clean_data return self.clean_data else: #校验失败,把错误信息放入errors中 self.add_error('re_pwd','两次密码输入不一致') 或者 #抛出异常 raise ValidationError('两次密码不一致')
注意点:
- 校验失败,有两种写法,抛异常,将异常信息以{'__all__':[value,]}写入errors字典中
- 校验成功,返回clean_data字典
- 抛出异常类型为 ValidationError, from django.core.exceptions import ValidationError导入
钩子错误信息渲染注意点:
- 局部钩子抛出的异常会添加到该字段中的错误信息中,获取错误信息
前端: for循环对象 {{ foo.errors.0}}
- 全局钩子抛出的异常会添加到__all__中,获取错误信息:
后端: myform.errors.get('__all__')[0] 注意先判断myform.errors.get('__all__')是否存在
前端:{{ myform.errors.__all__.0}}
- 能走到这些函数,说明前面的form组件的那些校验都成功了,此时就可以从clean_data中取数据,因为此时clean_data中的数据全部符合,都找的到,而且clean_data是字典比较好取值。
五、完整的form组件校验
1.视图层
from django.shortcuts import render, HttpResponse, redirect # forms组件数据校验的功能 # 第一步:先要继承Form from django import forms from django.forms import widgets from django.core.exceptions import ValidationError # 写一个类 class MyForm(forms.Form): # 定义一个属性,可以用来校验字符串类型 # 限制最大长度是8,最小长度是3 name = forms.CharField(max_length=8, min_length=3, label='用户名' error_messages={'max_length': '最长是8', 'min_length': '最短是3', 'required': '这个必须填'}, widget=widgets.TextInput(attrs={'class': 'form-control'})) pwd = forms.CharField(max_length=8, min_length=3, required=True, label='密码', error_messages={'max_length': '最长是8', 'min_length': '最短是3', 'required': '这个必须填'}, widget=widgets.PasswordInput()) re_pwd = forms.CharField(max_length=8, min_length=3, required=True, label='确认密码', error_messages={'max_length': '最长是8', 'min_length': '最短是3', 'required': '这个必须填'}, widget=widgets.PasswordInput()) # 校验是否是邮箱格式 email = forms.EmailField(label='邮箱', error_messages={'required': '这个必须填', 'invalid': '不符合邮箱格式'}) # aa = forms.CharField(label='选择', error_messages={'required': '这个必须填', 'invalid': '不符合邮箱格式'},widget=widgets.CheckboxInput()) def clean_name(self): # self:当前form对象 name = self.cleaned_data.get('name') if name.startswith('sb'): # 失败,抛异常 raise ValidationError('不能以傻逼开头') # 正常,把name返回 return name def clean(self): pwd = self.cleaned_data.get('pwd') re_pwd = self.cleaned_data.get('re_pwd') if pwd == re_pwd: return self.cleaned_data else: raise ValidationError('两次密码不一致') def index_form(request): # 生成对象时(实例化),需要传入要校验的数据(字典) if request.method == 'GET': myform = MyForm() return render(request,'indxe2.html',locals()) elif request.method == 'POST': myform = MyForm(request.POST) if myform.is_valid(): # print(myform.cleaned_data) # 验证通过的数据 # models.User.objects.create(name='lqz',pwd='123',re_pwd='123) myform.cleaned_data.pop('re_pwd') models.User.objects.create(**myform.cleaned_data) return redirect('http://www.baidu.com') else: all_error = myform.errors.get('__all__') if all_error: all_error = all_error[0] # print(myform.errors.as_data) return render(request, 'indxe3.html', locals())
2.模板层
<form action="" method="post" novalidate> {% for foo in myform %} <p>{{ foo.label }}:{{ foo }} <span>{{ foo.errors.0 }}</span></p> {% endfor %} <input type="submit" value="提交"><span>{{ all_error }}</span> </form>