from组件之钩子函数、常用字段、modelform介绍、cookie与session

今日学习内容总结

from组件

from组件之钩子函数

钩子函数

    1. 全局和局部钩子都是定义在forms组件内的函数

    2. 钩子函数类似于校验的第二道关卡,能够让我们自定义校验规则

两种钩子函数

    1. 局部钩子:用于给单个字段增加校验规则

    2. 全局钩子: 用于给多个字段增加校验规则

两种钩子函数的书写方式

      局部钩子

    def clean_字段名(self):
        字段值 = self.cleaned_data.get('字段名')
        [判断逻辑代码]
        [不通过抛出 ValidationError 异常]
        [通过则返回该字段值]

      全局钩子

def clean(self):
    字段值1 = self.cleaned_data.get('字段1')
    字段值2 = self.cleaned_data.get('字段2')
    [判断逻辑代码]
    [不通过抛出 ValidationError 异常]
    [通过则 return self.cleaned_data]

forms组件常用字段

初始值 : initial

    username = forms.CharField(
            min_length=8,
            label="用户名",
            initial="shawn"  # 设置默认值
        )

自定义错误提示 : error_massages

username = forms.CharField(
        min_length=8,
        label="用户名",
        initial="shawn",
        error_messages={
            "required": "不能为空",
            "invalid": "格式错误",
            "min_length": "用户名最短8位"
        }
    )

密码框:password

# 设置render_value在输入错误的时候, 保留输入的内容value值
pwd = forms.CharField(
        min_length=4,
        label="密码",
        widget=forms.widgets.PasswordInput(attrs={'class': 'a1'}, render_value=True)
    )

反选radio

gender = forms.fields.ChoiceField(
    choices=((1, "男"), (2, "女"), (3, "未知")),
    label="性别",
    initial=3,
    widget=forms.widgets.RadioSelect()
)

单选checkbox

keep = forms.ChoiceField(
    label="是否记住密码",
    initial="checked",
    widget=forms.widgets.CheckboxInput()
)

多选checkbox

Copyhobbies1 = forms.MultipleChoiceField(
    choices=((1, "read"), (2, "run"), (3, "play"),),
    label="爱好",
    initial=[1, 3],
    widget=forms.widgets.CheckboxSelectMultiple()
)

单选Select

Copyeducation  = forms.ChoiceField(
    choices=((1, "高中"), (2, "中专"), (3, "大专"), (4, "本科"), (5, "研究生"), (6, "硕士")),
    label="学历",
    initial=3,
    widget=forms.widgets.Select(attrs={'class': 'form-control'})
)

多选Select

Copyhobbies2 = forms.MultipleChoiceField(
    choices=((1, "篮球"), (2, "足球"), (3, "双色球"),),
        label="爱好",
    initial=[1, 2],
    widget=forms.widgets.SelectMultiple(attrs={'class': 'form-control'})
)

forms组件源码分析

引入

    1. 分析forms 组件如何进行校验

    2. 为何将校验成功的数据放入 cleaned_data 中

    3. 检验失败的数据放入 errors 中

    4. 代码示例
        # 进行校验 is_valid()
        if form_data.is_valid():
            print('校验成功')
            # 获得校验成功的数据 cleaned_data
            print(form_data.cleaned_data)
        else:
            print('检验失败')
            # 获得检验失败的错误提示 errors
            print(form_data.errors)
            error = form_data.errors.get('__all__')

forms组件的检验机制

      forms组件最核心的就是 is_valid( ) 这个校验机制, 也就是我们的突破口。

      1. Ctrl + 点击 is_valid( ) 查看其内部源码

def is_valid(self):
    """Return True if the form has no errors, or False otherwise."""
    # 如果表单没有错误则返回True,否则返回False
    return self.is_bound and not self.errors

      也就是说让表单验证成功就必须保证 self.is_bound 为 True , self.errors 为 False。

      2. 点击 self.is_bound 查看其源码

self.is_bound = data is not None or files is not None

      只要数据部位空或者文件不为空, self.is_bound 就为 True, 也就是只要你传值了就为 True。

      3. 再返回上一步点击 self.errors 查看源码

@property  # 类装饰器,可以伪装成属性来使用(不需要加括号)
def errors(self):
    """Return an ErrorDict for the data provided for the form."""
    # 为表单提供的数据返回ErrorDict。
    if self._errors is None:  # 如果_errors中为空则执行full_clean()
    	self.full_clean()
    return self._errors

      4.点击进入 self._error

self._errors = None  # Stores the errors after clean() has been called.

      发现其默认就是 None, 也就是说:无论如何都会执行 self.full_clean()。

      5.在进入到 full_clean() 中查看

def full_clean(self):
    """
    Clean all of self.data and populate self._errors and self.cleaned_data.
    """
    # 对所有的self.data、self._errors、self.cleaned_data进行校验
    
    # 这里赋值了一个错误字典用来存放错误字段
    self._errors = ErrorDict()
    if not self.is_bound:  # Stop further processing(停止处理其他规则)
        return
    # 这个字典用来存正确字段
    self.cleaned_data = {}
    ......

    self._clean_fields()  # 字段校验和局部钩子方法
    self._clean_form()    # 全局钩子
    self._post_clean()    # 内部为pass,但可以自己添加

      6.我们在来看看校验字段的方法 _clean_fields()

def _clean_fields(self):
    # 循环从fields中取出name和field
    # fields实质上是一个字典,forms组件的实例化就会自动创建一个fields
    # 该字典类内部类似 : {'name':'name字段对象','pwd':'pwd字段对象'}
    for name, field in self.fields.items():

        ......
        
        try:
            # 判断是不是文件,是文件则走下面的逻辑
            if isinstance(field, FileField):
                initial = self.get_initial_for_field(field, name)
                value = field.clean(value, initial)
            else:
                # 不是文件则进行这一步,使用规则对象对传入的value进行校验(clean()才是真正的进行校验)
                value = field.clean(value)
            # 然后将值加到字典中去,不成功则最下面抛出异常,将字段和错误信息加入到错误字典中
            self.cleaned_data[name] = value
            # 判断是否有'clean_%s'格式的一个变量名,也就是我们写的局部钩子函数(解释了为什么钩子函数名要那样写)
            if hasattr(self, 'clean_%s' % name):  
                # 有的话就拿出来执行该钩子函数,得到返回数据
                value = getattr(self, 'clean_%s' % name)()
                # 将返回的数据添加到字典中,如果出错则也是走下面的except
                self.cleaned_data[name] = value
        except ValidationError as e:
            # 将错误字段名和错误信息加入到errors字典中
            # 我们使用raise ValidationError('')抛出异常,与下面这个方法的效果类似
            self.add_error(name, e)

      7.点击 add_error 查看关键信息

def add_error(self, field, error):
	......
	    ......
	    
        if field in self.cleaned_data:  # 如果field字段对象在cleaned_data
            del self.cleaned_data[field]  # 那么就将其从中删除

      8.再来看看校验全局钩子的方法_clean_form()

def _clean_form(self):
    try:
        cleaned_data = self.clean()  # 执行全局钩子并拿到返回值(父类中有clea()方法,如果你自己写了则执行你写的)
    except ValidationError as e:
        self.add_error(None, e)  # 出错则添加错误信息
    else:
        if cleaned_data is not None:  # 没出错则判断全局钩子的返回值是否为空
            self.cleaned_data = cleaned_data  # 不是空则原封不动的返回给elf.cleaned_data

      父类中的全局钩子函数:

def clean(self):
    """
    Hook for doing any extra form-wide cleaning after Field.clean() has been
    called on every field. Any ValidationError raised by this method will
    not be associated with a particular field; it will have a special-case
    association with the field named '__all__'.
    """
    # 由这个方法引发的任何验证错误将与名为'__all__'的字段关联
    # 父类中的全局钩子函数直接返回了cleaned_data
    return self.cleaned_data

      这就是为什么使用全局钩子的时候需要返回一个完整的cleaned_data, 否则将是一个不完整的数据。

ModelForm

ModelForm简介

      forms组件主要配合models里面的默写类一起使用。但是默写类里面的字段需要在forms类中相当于重写一遍,代码冗余,为了更好的结合forms与models的关系,有了一个ModelForm(基于forms组件)。

      代码案例:

  class MyUser(forms.ModelForm):
      class Meta:
          model = models.User  # 指定关联的表
          fields = '__all__'  # 所有的字段全部生成对应的forms字段
          labels = {
              'name': '用户名',
              'age': '年龄',
              'addr': '地址',
              'email': '邮箱'
          }
          widgets = {
              "name": forms.widgets.TextInput(attrs={"class": "form-control"}),
          }

  def reg(request):
      form_obj = MyUser()
      if request.method == 'POST':
          form_obj = MyUser(request.POST)
          if form_obj.is_valid():
              # form_obj.save()  # 新增数据
              edit_obj = models.User.objects.filter(pk=5).first()
              form_obj = MyUser(request.POST, instance=edit_obj)  # 是新增还是保存就取决于有没有instance参数
              form_obj.save()  # 编辑数据
      return render(request, 'reg.html', locals())

Cookie与Session

cookie简介

      早期的互联网应用程序都是不保存用户状态的,所有人发送请求返回的都是相同的页面。

       现如今几乎所有的应用程序都可以保存用户状态。如何实现的?

      HTTP协议四大特性之一:无状态。让服务端知道你是谁的方式很单一:携带用户名和密码(身份标识)。每次操作之前都需要输入用户名和密码,当你成功登录之后浏览器会在本地帮你保存用户名和密码。每次操作浏览器自动发送用户名和密码。

      cookie本质,指代服务端让客户端保存的数据(存储在客户端上与用户信息相关的数据)。

session简介

      早期的cookie是直接存储的用户明文相关信息,不安全。用户登录成功之后,服务端生成一个随机字符串,返回给客户端保存。之后客户端每次发请求携带该随机字符串,服务端获取之后比对后台数据。

      session本质,指代服务端保存的跟用户信息相关的数据。

posted @ 2022-05-23 21:07  くうはくの白  阅读(118)  评论(0)    收藏  举报