多对多第三张表的创建方式 和 forms组件的使用

时间:2021-08-11 05:20:19

一、多对多第三张表的创建

  • 共有三种创建方式:全自动,纯手撸,半自动
  • 推荐使用半自动方式

1. 全自动方式

(1)实现代码

class Book(models.Model):
title = models.CharField(max_length=32)
# 多对多关系字段
authors = models.ManyToManyField(to='Authors') class Authors(models.Model):
name = models.CharField(max_length=32)

(2)优点和不足

  • 优点

    • 全部都是由orm自动帮你创建的
    • 还内置了四个操作第三张表的方法:add, remove, set, clear
  • 不足:

    • 自动创建的第三张表无法扩展个修改字段,表的扩展性较差

2. 纯手撸方式(了解)

(1)实现代码

class Book(models.Model):
title = models.CharField(max_length=32) class Authors(models.Model):
name = models.CharField(max_length=32) class Book2Authors(models.Model):
book = models.ForeignKey(to="Book")
author = models.ForeignKey(to="Authors")
create_time = models.DateField(auto_now_add = True)

(2)优点和不足

  • 优点:

    • 第三张表中字段个数和字段名称全都可以自己定义
  • 不足:

    • 不再支持orm跨表查询
    • 不再有正反向的概念
    • 不支持内置了四个操作第三张表的方法:add, remove, set, clear

3. 半自动方式(推荐使用)

(1)实现代码

  • 注意:代码中的多行注释部分
class Book(models.Model):
title = models.CharField(max_length=32)
# 多对多关系字段
authors = models.ManyToManyField(to='Authors',through='Book_Author',through_fields=("book","authors"))
"""
当你的ManyToManyField只有一个参数to的情况下 orm会自动帮你创建第三张表
如果你加了through和through_fields那么orm就不会自动帮你创建第三张表 但是它会在内部帮你维护关系 让你能够继续使用orm的跨表查询
through 自己指定第三张关系表
through_fields 自己指定第三张关系表中,到底哪两个字段维护者表与表之间的多对多关系,且through_fields在哪个类中,其括号内的第一个参数就是对应该类的第三张表中的哪个外键字段。(如Book类中,through_field括号内的第一个参数就是第三张表的book外键字段)
""" class Authors(models.Model):
name = models.CharField(max_length=32)
# 多对多关系字段 等价与Book表中的authors字段
# books = models.ManyToManyField(to='Book',through='Book_Author', through_fields=("authors","book")) # 该表中可以由任意多的外键字段
# 可以扩展任意的字段
class Book_Author(models.Model):
book = models.ForeignKey(to='Book')
authors = models.ForeignKey(to='Authors')

(2)优点和不足

  • 优点:
    • 可以任意的添加和修改第三张表中的字段
    • 并且支持orm跨表查询
  • 不足:
    • 不支持内置了四个操作第三张表的方法:add, remove, set, clear

二、forms组件

  • forms组件是django自带的一个组件,用来校验前端form表单中用户提交的数据是否符合我们指定的格式。

1. forms组件的3大作用

  • 渲染标签:通过forms组件语法,创建input标签

  • 校验数据:将数据转递给后端做数据校验。(当使用forms组件时,前端浏览器会自动识别,并在前端会自动做一次校验。但是前端的校验基本没有意义,一般关闭前端校验。方法是:在form表单中加一个novalidate参数即可)

  • 展示信息:如果数据有错误,展示错误信息

2. 渲染标签

  • 继承forms.Form的类,通过实例化出一个对象,传到前端后,在前端可以将其内定义的属性直接渲染成用户输入的标签。但是不会帮你生成一个提交按钮,因此需要你手动创建。

(1)前端渲染标签的3种方式

  • 前端中渲染标签的方式推荐使用第三种
<!--forms组件渲染标签方式1:封装程度态高 不推荐使用 但是可以用在本地测试-->
{{ form_obj.as_p }} <!--自动渲染所有input框 -->
{{ form_obj.as_ul }}
{{ form_obj.as_table }} <!-- forms组件渲染标签方式2:不推荐使用 写起来太烦了-->
{{ form_obj.username.label }}{{ form_obj.username }}
{{ form_obj.username.label }}{{ form_obj.password }}
{{ form_obj.username.label }}{{ form_obj.email }} <!--**********forms组件渲染标签方式3:推荐使用 *******-->
{% for form in form_obj %}
<p>{{ form.label }}{{ form }}</p> <!--form 等价于你方式2中的对象点字段名-->
{% endfor %}

(2)后端写法

  • 步骤
    1. 先导入forms组件
    2. 创建一个继承forms.Form的类
    3. 在功能函数中实例化一个空的forms类的对象form_obj = MyForm()
    4. 判断前端的提交方式为POST时,再生成一个必须与空对象同名的的forms类的对象form_obj = MyForm(request.POST)
    5. 给前端返回数据
  • 后端实例
from django import forms
class MyForm(forms.Form):
# username字段 最少三位 最多八位
username = forms.CharField(max_length=8,min_length=3)
# password字段 最少三位 最多八位
password = forms.CharField(max_length=8,min_length=3)
# email字段 必须是邮箱格式
email = forms.EmailField() def index(request):
# 渲染标签 第一步 需要生成一个空的forms类的对象
form_obj = MyForm()
# 如何校验前端用户传入的数据
if request.method == 'POST':
# 获取用户的数据 request.POST中 forms组件校验数据
form_obj = MyForm(request.POST) # 改变量名一定要跟上面的form_obj变量名一致
if form_obj.is_valid(): # 研究forms组件入口就是is_valid()
print(form_obj.cleaned_data)
return HttpResponse('数据全部OK')
# 直接将生成的对象 传递给前端页面
return render(request,'index.html',locals())

(3)forms基本使用实例:

# **************后端:*****************

from django import forms
class MyForm(forms.Form):
# username字段 最少三位 最多八位
username = forms.CharField(max_length=8,min_length=3)
# password字段 最少三位 最多八位
password = forms.CharField(max_length=8,min_length=3)
# email字段 必须是邮箱格式
email = forms.EmailField() def index(request):
# 渲染标签 第一步 需要生成一个空的forms类的对象
form_obj = MyForm()
# 如何校验前端用户传入的数据
if request.method == 'POST':
# 获取用户的数据 request.POST中 forms组件校验数据
form_obj = MyForm(request.POST) # 改变量名一定要跟上面的form_obj变量名一致
if form_obj.is_valid(): # 研究forms组件入口就是is_valid()
print(form_obj.cleaned_data)
return HttpResponse('数据全部OK')
# 直接将生成的对象 传递给前端页面
return render(request,'index.html',locals()) # *******************前端***************** # index.html文件中
<body>
<form action="" method="post" novalidate>
{% for forms in form_obj %}
<p>
<!--渲染标签-->
{{ forms.label }}{{ forms }}
<!--展示错误信息-->
<span>{{ forms.errors.0 }}</span>
</p>
{% endfor %}
<input type="submit">
</form>
</body>

3. 校验数据

  • 数据的校验通常前后端都必须有,但是前端的校验可有可无,并且弱不禁。后端的校验必须要有,并且必须非常的全面

  • 如何告诉浏览器不做校验 form表单中加一个novalidate参数即可

  • 这里的校验数据是我们查看forms组件校验数据的原理。真正的校验数据其实在定义类时,各字段括号里的属性就是对其的校验条件。有了校验条件后,forms组件内部会自动帮我们校验。

  • forms组件的校验数据中有几个方法

    • forms对象 = MyForm({'username':'jason','password':'12','email':'123'}):实例化forms对象。在实际开发中,括号里直接放request.POST即可。
    • forms对象.is_valid():查看校验的数据是否合法。只有当你的数据全部符合校验规则的情况下,结果才是True,否则都为False
    • forms对象.errors:查看错误信息,不加括号(错误信息包含不符合规则的字段及其错误的理由)(数据格式:{字段名:[错误信息]}
    • forms对象.cleaned_data:查看符合校验规则的数据,不加括号。(数据格式:{字段名:字段值}
    • 在实例化forms对象时,给类传的参数是个大字典,大字典内必须包含类中定义的那些属性字段。

4. 展示错误信息

  • 即在渲染标签时,一起书写代码
<form action="" method="post" novalidate>
{% for forms in form_obj %}
<p>
<!--渲染标签-->
{{ forms.label }}{{ forms }}
<!--展示错误信息-->
<span>{{ forms.errors.0 }}</span>
</p>
{% endfor %}
<input type="submit">
</form>

三、forms组件的校验条件和错误信息

  • forms组件的对前端发送来的数据的进行校验的条件和向前端发送的中文的错误信息都是在定义forms类时书写的。

1. 校验条件和错误信息的书写

  • 必须掌握的参数
label       # input对应的提示信息,不指定该参数,则默认是首字母大写的字段名
initial # input框默认值
required # 默认为True控制字段是否必填
error_messages # 等于一个大字典。里面填写对应的校验条件的自定义错误信息
validators # 等于一个列表。里面放的是正则校验条件
widget # 给input框设置样式及属性,不写则默认input框的type属性值是text # 等价于下面的写法
widget=forms.widgets.TextInput({'class':'form-control c1 c2','username':'jason'}) # 等价于上面的写法
widget=forms.widgets.TextInput(attrs={'class':'form-control c1 c2','username':'jason'})
  • 实例
from django import forms
from django.core.validators import RegexValidator # 使用正则校验数据
from django.forms import widgets # 给input框设置样式及属性
class MyForm(forms.Form):
# username字段 最少三位 最多八位
username = forms.CharField(
max_length=8,
min_length=3,
label='用户名', # input框对应的提示信息
initial='默认值', # 给input框设置默认值
required=False, # 控制字段是否必填(默认为True)
error_messages={
'max_length':'用户名最长八位',
'min_length':'用户名最短三位',
'required':'用户名不能为空'
},
validators=[
RegexValidator(r'^[0-9]+$', '请输入数字'),
RegexValidator(r'^159[0-9]+$', '数字必须以159开头'),
], # 使用正则校验数据(前面是正则表达式,后面直接是报错信息) widget=forms.widgets.TextInput({'class':'form-control c1 c2','username':'jason'}) # 给input框设置样式及属性,并且input框的type设为text
)
# password字段 最少三位 最多八位
password = forms.CharField(
max_length=8,
min_length=3,
label='密码',
error_messages={
'max_length': '密码最长八位',
'min_length': '密码最短三位',
'required': '密码不能为空'
},
widget=forms.widgets.PasswordInput() # 给input框设置样式及属性,并且input框的type设为password
)
# 确认密码
confirm_password = forms.CharField(
max_length=8,
min_length=3,
label='确认密码',
error_messages={
'max_length': '确认密码最长八位',
'min_length': '确认密码最短三位',
'required': '确认密码不能为空'
},
)
# email字段 必须是邮箱格式
email = forms.EmailField(
label='邮箱',
error_messages={
'required':'邮箱不能为空',
'invalid':'邮箱格式错误'
})

2. 其他校验数据的方式

(1)HOOK(钩子函数)

  • 注意:钩子函数写在自定义的forms类中

  • 当你觉得1中的校验条件还不能够满足你的需求,你可以考虑使用钩子函数

  • 是一个函数,函数体内你可以写任意的校验代码

  • 分为局部钩子和全局钩子

    • 局部钩子:只能钩取单个字段进行校验
    • 全局钩子:可同时钩取多个字段进行校验

1. 局部钩子

  • 语法:

    def clean_字段1(self):
    # 拿到要校验的字段数据
    变量(接收字段数据) = self.cleaned_data.get('字段1') # 书写校验代码code # 最后一定要返回字段(有钩有还),不返回也没事,后端会自动检验,帮你返回。建议规范写法
    return 字段1
  • 局部钩子实例

def clean_username(self):
username = self.cleaned_data.get('username')
if '666' in username:
# 给username所对应的框展示错误信息
# self.add_error('username','光喊666是不行的')
raise ValidationError('光喊666是不行的') # 直接向前端发送错误信息,等价于上一行的self.add_error('username','光喊666是不行的')
# 最后一定要将字段返回
return username

2. 全局钩子

  • 语法加实例:

    # 校验密码 确认密码是否一致     全局钩子
    def clean(self):
    password = self.cleaned_data.get("password")
    confirm_password = self.cleaned_data.get("confirm_password")
    if not password == confirm_password:
    # 给username所对应的框展示错误信息
    self.add_error('confirm_password','两次密码不一致')
    # 将全局的数据返回
    return self.cleaned_data

(2)选择类的input框

# 1. type=radio
gender = forms.ChoiceField(
choices=((1, "男"), (2, "女"), (3, "保密")),
label="性别",
initial=3,
widget=forms.widgets.RadioSelect()
) # 2. type=checkbox(单选)
keep = forms.ChoiceField(
label="是否记住密码",
initial="checked",
widget=forms.widgets.CheckboxInput()
) # 3. type=checkbox(多选)
hobby2 = forms.MultipleChoiceField(
choices=((1, "篮球"), (2, "足球"), (3, "双色球"),),
label="爱好",
initial=[1, 3],
widget=forms.widgets.CheckboxSelectMultiple()
) # 4. select选择框(单选)
hobby = forms.ChoiceField(
choices=((1, "篮球"), (2, "足球"), (3, "双色球"),),
label="爱好",
initial=3,
widget=forms.widgets.Select()
) # 5. select选择框(多选)
hobby1 = forms.MultipleChoiceField(
choices=((1, "篮球"), (2, "足球"), (3, "双色球"),),
label="爱好",
initial=[1, 3],
widget=forms.widgets.SelectMultiple()
)

(3)其他约束条件

详细的forms组件介绍