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>
posted @ 2019-09-24 20:54  只会玩辅助  阅读(553)  评论(0编辑  收藏  举报