一、多对多创建的三种方式

1.全自动

当表数据不复杂,一般情况下可以使用全自动,即Django会自动创建第三张关系表。

  • 优点:全部是orm自动操作的,可以跨表查询并且内置了四个操作方法,add、remove、set、clear

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

class Book(models.Model):
    name = models.CharField(max_length=32)
    # 使用ManyToManyField自动创建多对多关系
    authors = models.ManyToManyField(to='Authors')
class Authors(models.Model):
    name = models.CharField(max_length=32)

2.半自动

需要对第三张表做一定扩展时,推荐使用该方法创建多对多关系,用ManyToManyField关键字加through等参数

优点:可以任意添加或修改第三张表的字段,并且支持跨表查询

缺点:不支持内置的四个方法(add、remove、set、clear)

class Book(models.Model):
    title = models.CharField(max_length=32)
    # 多对多关系字段
    authors = models.ManyToManyField(
        to='Authors',through='Book2Author',through_fields=("book","authors"))
    """
    当你的ManyToManyField只有一个参数to的情况下 orm会自动帮你创建第三张表,也就是全自动
    如果你加了through和through_fields那么orm就不会自动帮你创建第三张表 但是它会在内部帮你维护关系 让你能够继续使用orm的跨表查询
    through  自己指定第三张关系表
    through_fields  自己指定第三张关系表中 到底哪两个字段维护者表与表之间的多对多关系,
                    在哪张表内建立,哪张表名关系字段放在前面
    """

class Authors(models.Model):
    name = models.CharField(max_length=32)

# 同时需要手动写第三张表
class Book2Author(models.Model):
    book = models.ForeignKey(to='Book')
    authors = models.ForeignKey(to='Authors')
                

3.纯手动

优点:第三张表的字段个数和名称可以完全自定义

缺点:不支持跨表查询,也没有内置的四个方法(add、remove、set、clear)

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)

二、from组件

  • 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对象 = 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控制字段是否必填
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框设置默认值
        error_messages={
             'max_length':'用户名最长八位',
             'min_length':'用户名最短三位',
             'required':'用户名不能为空'
                       },
        validators=[
                    RegexValidator(r'^[0-9]+$', '请输入数字'),
                    RegexValidator(r'^159[0-9]+$', '数字必须以159开头'),
                    ],  # 使用正则校验数据(前面是正则表达式,后面直接是报错信息)

        required=False,   # 控制字段是否必填(默认为True)
        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()
     )
02-10 16:45