• django学习第十四天--Forms和ModelForm


    Forms和ModelForm

    • 进行数据校验,先看数据校验的过程
          注册页面图解:
          前端为了用户体验会做一些校验,不满足校验要求会报错
          服务端也会对数据进行一些校验,不满足校验要求会报错
          数据库也会对数据进行一些校验,不满足校验要求会报错
    
          form组件和modleform组件就是让我们的数据校验过程更加简单一些,功能非常强大      
    

    Forms组件

    • 提供了三个功能
          1.能够帮我们生成HTML标签
          2.标签中保留之前用户输入的数据
          3.数据校验
    

    forms组件的使用流程

    • 1.在views.py文件中创建一个自定义的form类
          from django import forms   #需要先导入这个forms
    
          class RegisterForm(forms.Form):
                phone = forms.CharField()
                username = forms.CharField()
                password = forms.CharFied()
    
                #用label参数可以将HTML中的label标签中内容显示为中文
                phone = forms.CharField(label='手机号')
                username = forms.CharField(label='用户名')
                password = forms.CharField(label='密码')
    
    • 2.在views.py文件中写上视图函数
          def register(request):
                if request.method == 'GET':
                      form = RegisterForm() #将上面的form类进行实例化
                      return render(request,'register.html',{'form':form})#将实例化后的对象返回到html中生成对应的属性的标签
    
    • 3.创建一个html文件,比如叫作register.html,内容如下
          <!DOCTYPE html>
          <html lang='en'>
          <head>
                <meta charset="UTF-8">
                <title>Title</title>
          </head>
          <body>
    
                <h1>注册页面</h1>
                <form action='/register/' method='post' novalidate> #novalidate取消浏览器自带的错误提示
                      {% csrf_token %}
                <div>
                      <!--{{ form.phone.id_for_label }}相当于绑定下面生成的input标签中的id属性  -->
                      <!-- {{ form.phone.label }}相当于把后台自定义的中文渲染到标签中  -->
                      <label for="{{ form.phone.id_for_label }}">{{ form.phone.label }}</label>
                      <!-- {{form.phone}}相当于生成这个input标签<input type="text" name="phone" required="" id="id_phone"> -->
                      {{ form.phone }}
                </div>
    
                <div>
                      <label for="{{ form.username.id_for_label }}">{{ form.username.label }}</label>
                      {{ form.username }}
                </div>
    
                <div>
                      <label for="{{ form.password.id_for_label }}">{{ form.password.label }}</label>
                      {{ form.password }}
                </div>
    
                <input type='submit'>
                </form>
          </body>
          </html>
    

    保留原数据和校验功能

    • forms组件代码
          class RegisterForm(forms.Form):
                #每个字段,默认都有一个required=True的参数,表示该字段数据不能为空
                #phone = form.CharField(label='手机号',required=True)
                phone = forms.CharField(
                      label='手机号',
                      required=True,
                      #错误提示信息的自定制
                      error_messages={
                            'required':'小敏敏提示您,不能为空!',
                      }
                )
                username = forms.CharField(label='用户名')
                password = forms.CharField(label='密码')
    
    • views.py内容如下
          def register(request):
                if request.method == 'GET':
                      form = RegisterForm()
                      return render(request,'register.html',{'form':form})
                else:
                      print(request.POST)
                      form = RegisterForm(data=request.POST)
                      #把数据交给了RegisterForm,那么在通过form来渲染标签时,就将原来的的数据以默认值的形式(value='值')生成在了对应标签上
                      if form.is_valid(): #执行校验,如果所有数据都校验通过了,返回True,但凡有一个数据没有通过校验,返回False
                            print('通过校验的数据',form.cleaned_data)
                            return HttpResponse('ok')
                      print('错误信息>>>',form.errors)
                      return render(request,'register.html',{'form':form})
    
    • register.html内容
          <!DOCTYPE html>
          <html lang="en">
          <head>
                <meta charset="UTF-8">
                <title>Title</title>
                <style>
                      <!-- 给错误信息定制一些css样式 -->
                      .error_msg{
                            font-size:12px;
                            color:red;
                      }
                </style>
          </head>
          <body>
    
          <h1>注册页面</h1>
          <!-- form标签的这个novalidate属性,就是把浏览器自带的必填提示信息去掉 -->
          <form action='/register/' method='post' novalidate>
                {% csrf_token %}
             <!--   {{ form.errors }} 表示存放着所有字段的错误信息 -->
          <div>
                <label for='{{ form.phone.id_for_label }}'>{{ form.phone.label }}</label>
                {{ form.phone }}
                <!-- form.phone.errors.0 表示展示一个错误信息  -->
                <span class='error_msg'>{{ form.phone.errors.0 }}</span>
           </div>
    
          <div>
                <label for='{{ form.username.id_for_label }}'>{{ form.username.label }}</label>
                {{ form.username }}
                <span class="error_msg">{{ form.username.errors.0 }}</span>
          </div>
    
          <div>
                <label for='{{ form.password.id_for_label }}'>{{ form.password.label }}</label>
                {{ form.password }}
                <span class='error_msg'>{{ form.password.errors.0 }}</span>
          </div>
    
          <input type='submit'>
    
          </body>
          </html>
    

    Form常用字段属性与插件

    • initial 初始值
          #生成input标签有个默认值
          username = fomrs.CharField(label='用户名',initial='小红')
    
    • widget 插件的意思,能够定制我们的标签显示效果
          示例1  密文输入框
          password = forms.CharField(
                label='密码',
                #CharField默认插件是TextInput相当于type='text',下面相当于修改type='password',完整写法forms.widgets.PasswordInput,下面是简写
                widget = forms.PasswordInput,
                )
    
          示例2 给标签加上一些其他属性
          password = forms.CharField(
                label='密码',
                #attrs={'标签属性':'属性值'}
                widget=forms.PasswordInput(attrs={'class':'c1 c2','placeholder':'请输入密码'}),
                )
    
    • 生成单选下拉框 ChoiceField默认插件是widget=forms.Select
          #sex = forms.fields.ChoiceField()  fields可以不用写
          sex = forms.ChoiceField(
                choices =((1,'女'),(2,'男')),
                #(1,'女')生成一个option标签,value值为1,文本内容为女
                label ='性别',
                # initial =2  #初始值
                )
    
    • 单选radio框 widget=forms.RadioSelect
          sex = forms.ChoiceField(
                choices=((1,'女'),(2,'男')),
                label='性别',
                #initial =2,
                #widget=forms.Select,  #ChoiceField的默认插件
                #input,type='radio'的单选框
                #widget=forms.RadioSelect,
                #修改插件,并设置属性
                widget=forms.RadioSelect(attrs={'class':'c1'})
                )
    
    • 多选下拉框 MultipleChoiceField
          hobby = forms.MultipleChoiceField(
                choices=((1,'喝酒'),(2,'抽烟'),(3,'励志')),
                label='爱好'
                )
    
    • 多选checkbox框 widget=forms.CheckboxSelectMultiple
          hobby = forms.MultipleChoiceField(
                choices=((1,'喝酒'),(2,'抽烟'),(3,'励志')),
                label = '爱好',
                #widget=forms.CheckboxInput, #做单选功能的复(多)选框形式,必须勾选协议的那个选框
                widget=forms.CheckboxSelectMultiple,
                )
    
    • 单选功能的复选框形式,像那种勾选同意协议的那个选框 widget=forms.CheckboxInput 里面choices只有true或false
          status = forms.MultipleChoiceField(
                choices=(('True',同意),('Flase',不同意))
                label='同意是否勾选协议',
                widget=forms.CheckboxInput,
                )
    
    • date类型
          bday = forms.CharField(
                label='生日',
                widget=forms.TextInput(attrs={'type':'date'}),  只要设置一个date属性就可以了
                )
    

    单选或者多选框使用数据库中的数据

    • 方式1 forms.ModelChoiceField
          models中的模型类
          class Publish(models.Model):
                name = models.CharField(max_length=32)
          
                def __str__(self):
                      return self.name
    
          form类中的字段写法
          #生成选择框,并使用数据库中的数据  必须要用这个ModelChoiceField
          publish = forms.ModelChoiceField(
                queryset=models.Publish.objects.all(),  #必须是queryset
                )
          会自动生成单选下拉框,并且option标签value属性对应的值,是queryset参数对应的queryset集合里面的models模型类对象的id值,option标签的文本内容是每个models模型类对象。
          #生成标签的时候,页面显示第一层会有个自带的'--------',第二个往后才是数据
    
    • 方式2 forms.ChoiceField
          publish = forms.ChoiceField(
                choices=models.Publish.objects.all().values_list('id','name')
          #quertset[(1,'xx出版社'),]
                )
          #生成标签的时候,没有上面那个自带的'--------',直接就显示的是数据
    

    Form所有内置字段

    • Field
          required=True,            是否不为空
          widget=None,              HTML插件,设置插件相当于可以更改标签并设置属性
          label=None,               用于生成label标签或显示内容
          initial=None,             初始值
          help_text='',             帮助信息(在标签旁边显示)
          error_messages=None,      错误信息{'required':'不能为空',}
          validators=[],            自定义验证规则
          disabled=False,           是否可以编辑,默认False,可编辑
    
    • CharField(Field)
          max_length=None,      最大长度
          min_length=None,      最小长度
          strip=True,           是否移除用户输入空白
    
    • IntegerField(Field)
          max_value=None,      最大值
          min_value=None,      最小值
    
    • FloatField(Field)
          ...
    
    • DecimalField(IntegerField)
          max_value=None,        最大值
          min_value=None,        最小值
          max_digits=None,       总长度
          decimal_places=None,   小数位长度
    
    • BaseTemporalField(Field)
          input_formats=None      时间格式化
          DateField(BaseTemporalField)    格式:2015-09-01
          TimeField(BaseTemporalField)    格式:11:12
          DateTimeField(BaseTemporalField)格式:2015-09-01 11:12
    
    • DurationField(Field) 时间间隔:%d %H:%M:%S.%f
          ...
    
    • RegexField(CharField)
          regex,            自定义正则表达式
          max_length=None      最大长度
          min_length=None      最小长度
          error_message=None,  忽略,错误信息使用 error_messages={'invalid':'...'}
    
    • EmailField(CharField)
          error_messages={'invalid':'邮箱格式不正确'}
    
    • FileField(Field)
          allow_empty_file=False     是否允许空文件
    
    • ImageField(FileField)
          注:需要PIL模块,pip3 install Pillow
          以上两个字典使用时,需要注意两点
                -form表单中 enctype='multipart/form-data'
                -view函数中 obj=MyForm(request.POST,request.FILES)
    
    • URLField(Field)
    • BooleanField(Field)
    • NullBooleanField(BooleanField)
    • ChoiceField(Field)
          choices=(),      选项,如:choices=((0,'上海'),(1,'北京'))
          required=True,   是否必填
          widget=None,     插件,默认select插件
          label=None,      label内容
          initial=None,    初始值
          help_text='',    帮助提示   
    
    • ModelChoiceField(ChoiceField) django.forms.models.ModelChoiceField
          queryset,                #查询数据库中的数据
          empty_label='--------',  #默认空显示内容
          to_field_name=None,      #html中value的值对应的字段
          limit_choices_to=None    #ModelForm中对queryset二次筛选
    
    • ModelMultipleChoiceField(ModelChoiceField) django.forms.models.ModelMultipleChoiceField
    • TypedChoiceField(ChoiceField)
          coerce = lambda val:val   对选中的值进行一次转换
          empty_value=''            空值的默认值
    
    • MultipleChoiceField(ChoiceField)
    • TypedMultipleChoiceField(MultipleChoiceField)
          coerce=lambda val:val      对选中的每一个值进行一次转换
          empty_value=''            空值的默认值
    
    • ComboField(Field)
          fields=()                  使用多个验证,如下:即验证最大长度20,又验证邮箱格式
                                   fields.ComboField(fields=[fields.CharField(max_length=20), fields.EmailField(),])
    
    • MultiValueField(Field)
          PS: 抽象类,子类中可以实现聚合多个字典去匹配一个值,要配合MultiWidget使用
    
    • SplitDateTimeField(MultiValueField)
          input_date_formats=None,   格式列表:['%Y--%m--%d', '%m%d/%Y', '%m/%d/%y']
        input_time_formats=None    格式列表:['%H:%M:%S', '%H:%M:%S.%f', '%H:%M']
    
    • FilePathField(ChoiceField) 文件选项,目录下文件显示在页面中
        path,                      文件夹路径
        match=None,                正则匹配
        recursive=False,           递归下面的文件夹
        allow_files=True,          允许文件
        allow_folders=False,       允许文件夹
        required=True,
        widget=None,
        label=None,
        initial=None,
        help_text=''
     
    
    • GenericIPAddressField
        protocol='both',           both,ipv4,ipv6支持的IP格式
        unpack_ipv4=False          解析ipv4地址,如果是::ffff:192.0.2.1时候,可解析为192.0.2.1, PS:protocol必须为both才能启用
    
    • SlugField(CharField) 数字,字母,下划线,减号(连字符)
    • UUIDField(CharField) uuid类型 复制代码

    数据校验功能

          格式不对的错误信息定制
          'initial':'邮箱格式不对',
    

    简单校验示例

          form类
          class DataForm(forms.Form):
                #不能为空,长度不能超过10位,不能短于4位
                username = forms.CharField(
                      label='用户名',
                      max_length=10,
                      min_length=4,
                      error_messages={
                            'required':'不能为空',
                            'max_length':'太长啦,受不了',
                            'min_length':'太短啦,不舒服',
                      }
                )
          
                #可以为空
                phone = forms.CharField(
                      label='手机号',
                      required=False,
                      )
                #要满足邮箱格式,不能为空
                email = forms.EmailField(
                      label='邮箱',
                      error_messages={
                            'invalid':'邮箱格式不对',   #invalid验证邮箱格式的
                      }
                )
                #提交的数据不能超出选项
                sex = forms.ChoiceField(
                      label='性别',
                      choices=((1,'女'),(2,'男')),
                      widget=forms.RadioSelect,
                      )
                #form只校验类中写的属性对应的那些数据
                #向csrf_token这些不会校验
    
    • views.py
          def data(request):
                if request.method == 'GET':
                      form = DataForm()
                      return render(request,'data.html',{'form':form})
                else:
                      form = DataForm(request.POST)
                      if form.is_valid():  #做校验,都通过返回True
                            #校验成功之后的数据form.cleaned_data里面不包含没有校验的数据,也就是不会校验form类中没有指定的属性对应的数据
                            print(form.cleaned_data)# {'username': 'chao', 'phone': '', 'email': 'asdf@xx.com', 'sex': '1'}
                            return HttpResponse('ok')
                      else:
                            print(form.errors)
                            return render(request,'data.html',{'form':form})
    
    • data.html文件
          <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    
    </head>
    <body>
    
    <form action="/data/" method="post" novalidate>
        {% csrf_token %}
        <div>
            <label for="{{ form.username.id_for_label }}">{{ form.username.label }}</label>
            {{ form.username }}
            {{ form.username.errors.0 }}
        </div>
        <div>
            <label for="{{ form.phone.id_for_label }}">{{ form.phone.label }}</label>
            {{ form.phone }}
            {{ form.phone.errors.0 }}
        </div>
        <div>
            <label for="{{ form.email.id_for_label }}">{{ form.email.label }}</label>
            {{ form.email }}
            {{ form.email.errors.0 }}
        </div>
        <div>
            <label for="{{ form.sex.id_for_label }}">{{ form.sex.label }}</label>
            {{ form.sex }}
            {{ form.sex.errors.0 }}
        </div>
    
        <input type="submit">
    
    </form>
    
    
    </body>
    
    </html>
    

    RegexValidator验证器

    • 示例代码
          from django.core.validators import RegexValidator
          class DataForm(forms.Form):
                #不能为空,长度不能超过10位,不能短于4位
                username = forms.CharField(
                      ...
                      #RegexValidator('正则','不符合正则时的错误信息')
                      validators=[RegexValidator(r'^a','用户名必须以a开头'),RegexValidator(r'b$','用户名必须以b结尾')]
                      )
    

    校验函数

    • 示例
          import re
          from django.core.validators import RegexValidator
          from django.core.exceptions import ValidationError
          #ValidationError是django提供的一个数据校验失败的一个错误类型
          #先定义一个函数,函数中我们可以做数据的校验,如果数据校验失败,我们可以raise ValidationError('错误信息')
          def mobile_match(value):
                mobile_re = re.compile(r'^(13[0-9]|15[012356789]|17[678]|18[0-9]|14[57])[0-9]{8}$')
                if not mobile_re.match(value):
                      raise ValidationError('手机号格式有误')
    
    
          使用
          phone =forms.CharField(
                label='手机号',
                validators=[mobile_match,],   #列表里面写我们自己定义的函数名
                )
    

    局部钩子

    • 代码示例
          class DataForm(forms.Form):
                #不能为空,长度不能超过10位,不能短于4位
                username = forms.CharField(
                      label='用户名',
                      max_length=10,
                      min_length=4,
                      )
                #可以为空
                phone = forms.CharField(
                      required=False,
                      validators=[mobile_match,],
                )
                。。。
                
                #局部钩子,能够对username这个字段的数据进行一个更高级的校验
                #语法 clean_字段名称
                def clean_username(self):
                      uname = self.cleaned_data.get('username') #cleaned_data.get 获取数据
                      if '666' in uname:
                            raise ValidationError('不能光喊6,没有用')
                      #如果校验通过,需要return这个值
                      return uname
    
                def clean_phone(self):
                      pass
    

    全局钩子

    • 示例代码
          class DataForm(forms.Form):
                #密码
                password = forms.CharField()
                #再次确认密码
                confirm_password=forms.CharField()
    
                #多个字段进行比较时,一般都是用全局钩子固定函数名clean 
                def clean(self):
                      p1 = self.cleaned_data.get('password')
                      p2 = self.cleaned_data.get('confirm_password')
                      if p1 != p2:
                            #直接raise错误,这个错误信息保存到了全局错误信息里面
                            #也就是self.errors里面
                            #所以要用add_error('属性名','错误提示信息')
                            self.add_error('confirm_password','抱歉,两次密码不一致')
                            self.add_error('password','抱歉,两次密码不一致')
    
                      #如果校验通过,必须return self.cleaned_data
                      return self.cleaned_data
    

    源码部分,主要看cleaned_data,为什么会在没有走视图函数的时候,就能获取数据

           def _clean_fields(self):
            for name, field in self.fields.items():
                # print(name, field)
                # name属性名称,field是CharField()类的实例化对象
                # value_from_datadict() gets the data from the data dictionaries.
                # Each widget type knows how to retrieve its own data, because some
                # widgets split data over several HTML fields.
                if field.disabled:
                    value = self.get_initial_for_field(field, name)
                else:
                    value = field.widget.value_from_datadict(self.data, self.files, self.add_prefix(name))
                try:
                    if isinstance(field, FileField):
                        initial = self.get_initial_for_field(field, name)
                        value = field.clean(value, initial)
                    else:
                        # 将CharField中的max_length,required..
                        value = field.clean(value)
                    # 将属性对应的CharField里面的简单校验规则进行了校验
                    self.cleaned_data[name] = value
                    #self.cleaned_data['username'] = 'abc'
                    if hasattr(self, 'clean_%s' % name): #clean_username
                        value = getattr(self, 'clean_%s' % name)()
                        self.cleaned_data[name] = value
                except ValidationError as e:
                    self.add_error(name, e)
    

    简单总结

          首先对所有的我们自己定义的类中的属性进行循环,self.fields能够获取到所有的属性,循环过程中先对属性,比如username=form.CharField(max_length=12),里面的参数校验规则进行了校验,这个校验完成之后,给self.clean_data这个字典进行了赋值  self.clead_data['username'] ='sad'
    然后对该字段的局部钩子进行了校验,在局部钩子中我们可以拿到self.clean_data['username']这个数据进行处理,别忘了局部钩子校验成功之后,要return这个字段的值,然后才进入下一次循环,处理下一个属性
    当上面的所有循环执行完之后,才执行我们的全局钩子,全局钩子中校验成功之后别忘了return self.clean_data
    

    静态文件配置和templates模板配置

          可以在app01/app02应用文件夹下分别创建一个static名称的文件夹,存放自己应用的静态文件
          可以在app01/app02应用文件夹下分别创建一个templates名称的文件夹,存放自己应用的html文件
    
          django寻找html文件静态文件的查找顺序是先找总配置中的templates或者
    statics文件夹,如果找不到对应文件,就去每个应用下的static或者templates去找对
    应文件,顺序是按照settings.py配置文件中的INDSTALL_APP中注册app的顺序进行查
    找,找到就返回,不在继续往下找了,所以要注意,最好在static或者templates下面创
    建一个以应用名为名称的文件夹,将文件放到这里面,以防文件名冲突导致的问题。
    

    -------------------------------------------

    个性签名:代码过万,键盘敲烂!!!

    如果觉得这篇文章对你有小小的帮助的话,记得在右下角点个“推荐”哦,博主在此感谢!

  • 相关阅读:
    339. Nested List Weight Sum
    41. First Missing Positive
    366. Find Leaves of Binary Tree
    287. Find the Duplicate Number
    130. Surrounded Regions
    ubuntu18.04安装mongodb4.4
    阿里dataX配置使用
    MySQL主从同步简单介绍&配置
    阿里yugong配置使用
    ubuntu编译安装mysql
  • 原文地址:https://www.cnblogs.com/weiweivip666/p/13401646.html
Copyright © 2020-2023  润新知