首页 > 技术文章 > Python学习笔记Day23 - Django_Form

JeromeLong 2020-07-05 23:27 原文

Form操作

orm(model)提供了数据库操作,但是验证的功能较弱
form专门用来做验证

参考文档

a.创建form类

from django.forms import Form
from django.forms import widgets
from django.forms import fields

class MyForm(Form):
    user = fields.CharField(
        widget=widgets.TextInput(attrs={'id': 'i1', 'class': 'c1'}),
        error_messages={'required':'用户名不能为空'},      # 自定义错误信息
    )
    gender = fields.ChoiceField(
        choices=((1, '男'), (2, '女'),),
        widget=widgets.RadioSelect
    )
    pwd = fields.CharField(
        widget=widgets.PasswordInput(attrs={'class': 'c1'}, render_value=True)
    )

b.View函数处理

from ... import MyForm
def index(request):
    if request.method == "GET":
        obj = MyForm()  
        return render(request, 'index.html', {'form': obj})
    elif request.method == "POST":
        obj = MyForm(request.POST, request.FILES)   # 创建form对象
        if obj.is_valid():                  # 判断form验证是否通过
            values = obj.cleaned_data       # 获取正确数据
            models.UserInfo.objects.create(**values)
        else:
            errors = obj.errors             # 获取错误信息,dict对象
            print(obj.errors['user'][0])            # 获取指定错误信息
            print(obj.errors.as_json())             # 打包为字典的错误信息
        return render(request, 'index.html', {'form': obj})

c.生成HTML标签

<form action="/" method="POST" enctype="multipart/form-data">
    {% csrf_token %}

    <p>{{ obj.user }} {{ obj.user.errors }}</p>
    <p>{{ obj.user }} {{ obj.errors.user.0 }}</p>    # 视频中的用法

    {{ obj.as_p }} {{ obj.user.errors }}           # 一次性以<p>生成所有标签
    {{ obj.as_ul }} {{ obj.user.errors }}          # 一次性以<ul>生成所有标签

    <table>   
        {{ obj.as_table }} {{ obj.user.errors }}   # 一次性以<table>生成所有标签
    </table>

    {{ form.xxoo.label }}                   # label值
    {{ form.xxoo.id_for_label }}            # label的for值,id_xxx
    {{ form.xxoo.label_tag }}               # 自动生成label(包含上述两点)
    {{ form.xxoo.errors }}                  # 错误信息列表ul
    {{ form.xxoo.errors.0 }}                # 错误信息文本
    <input type="submit"/>
</form>

字段

用于对用户请求数据的验证

详情见http://www.cnblogs.com/wupeiqi/articles/6144178.html

Field
    required=True,               是否允许为空
    widget=None,                 HTML插件
    label=None,                  用于生成Label标签或显示内容,相当于verbose_name
    initial=None,                初始值
    help_text='',                帮助信息(在标签旁边显示)
    error_messages=None,         自定义错误信息 {'required': '不能为空', 'invalid': '格式错误'} 用法类似ORM
    show_hidden_initial=False,   是否在当前插件后面再加一个隐藏的且具有默认值的插件(可用于检验两次输入是否一直)
    validators=[],               自定义验证规则
    localize=False,              是否支持本地化
    disabled=False,              是否可以编辑
    label_suffix=None            Label内容后缀,默认是':'

CharField(Field)
    max_length=None,             最大长度
    min_length=None,             最小长度
    strip=True                   是否移除用户输入空白

IntegerField(Field)
    max_value=None,              最大值
    min_value=None,              最小值

model中的foreignkey字段在Form中没有对应的字段,可用ChoiceField+choices从model里用valuelist(...)获取

插件

用于自动生成需要的HTML标签类型,可定制标签的属性

widgets = {
    'name': forms.TextInput(attrs={'class': "form-control"}),
    'authors': forms.Select(attrs={'class': "form-control"}),
    'publish_date': forms.DateInput(attrs={'class': "form-control",'placeholder': "YYYY-MM-DD"})
}

# 字段:
    TextInput(Input)
    NumberInput(TextInput) 
    EmailInput(TextInput)
    URLInput(TextInput)
    PasswordInput(TextInput)
    HiddenInput(TextInput)
    Textarea(Widget)
    DateInput(DateTimeBaseInput)
    DateTimeInput(DateTimeBaseInput)
    TimeInput(DateTimeBaseInput)
    CheckboxInput
    Select
    NullBooleanSelect
    SelectMultiple
    RadioSelect
    CheckboxSelectMultiple
    FileInput
    ClearableFileInput
    MultipleHiddenInput
    SplitDateTimeWidget
    SplitHiddenDateTimeWidget
    SelectDateWidget

常用选择插件

# 单选radio,值为字符串
    user = fields.CharField(
        widget=widgets.RadioSelect(choices=((1,'上海'),(2,'北京'),))
    )
    user = fields.ChoiceField(
        choices=((1, '上海'), (2, '北京'),),
        widget=widgets.RadioSelect
    )
    
# 单选select,值为字符串
    user = fields.CharField(
        widget=widgets.Select(choices=((1,'上海'),(2,'北京'),))
    )
    user = fields.ChoiceField(
        choices=((1, '上海'), (2, '北京'),),
        widget=widgets.Select
    )
    
# 多选select,值为列表
    user = fields.MultipleChoiceField(
        choices=((1,'上海'),(2,'北京'),),
        initial=[1,],
        widget=widgets.SelectMultiple
    ) 
    
# 单checkbox
    user = fields.CharField(
        widget=widgets.CheckboxInput()
    )
    
# 多选checkbox,值为列表
    user = fields.MultipleChoiceField(
        initial=[2, ],
        choices=((1, '上海'), (2, '北京'),),
        widget=widgets.CheckboxSelectMultiple
    ) 

choices的选项可以从数据库中获取

由于是静态字段,获取的值无法实时更新,可自定义构造方法从而达到此目的。

  • 方法一:

      class MyForm(Form):
          user = fields.ChoiceField(widget=widgets.Select)
          def __init__(self, *args, **kwargs):        
              super(MyForm,self).__init__(*args, **kwargs)        # 继承父类
              self.fields['user'].widget.choices = models.Classes.objects.all().value_list('id','caption')
    
  • 方法二:

    使用django提供的ModelChoiceField和ModelMultipleChoiceField字段来实现

      authors = models.ModelMultipleChoiceField(queryset=models.NNewType.objects.all())
      # authors = models.ModelChoiceField(queryset=models.NNewType.objects.all())
    

初始化数据

values = {'user': 'root', 'city': 2}
obj = MyForm(values)        #在views创建obj时传入字典
#obj = MyForm(initial=values)        # 视频中的用法

自定义验证规则

方式一:使用RegexValidator模块

from django.core.validators import RegexValidator
class MyForm(Form):
    user = fields.CharField(
        validators=[
            RegexValidator(r'^[0-9]+$', '请输入数字'), 
            RegexValidator(r'^159[0-9]+$', '数字必须以159开头')
        ],
    )

方式二:

from django.core.exceptions import ValidationError
def mobile_validate(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 = fields.CharField(validators=[mobile_validate, ],)

方式三:利用钩子自定义验证方法

class MyForm(forms.Form):
    username = fields.CharField(
        validators=[RegexValidator(r'^[0-9]+$', '请输入数字')], 
    )
    def clean_username(self):
        # Form中字段定义的格式匹配完之后,执行此方法进行验证
        value = self.cleaned_data['username']
        if "666" in value:      # 符合则抛验证异常
            raise ValidationError('666已经被玩烂了...', 'invalid')
        return value

方式四:

自定义字段和插件,详见地址
http://www.cnblogs.com/wupeiqi/articles/6144178.html

is_valid校验的过程--源码分析

- 验证 is_valid -> 字段内置正则 + clean_字段 -> clean(__all__)  -> _post_clean

form_obj.is_valid()

self._errors = ErrorDict()      -> {}
self.cleand_data = {}       -> {}
full_clean()
# 在Form.full_clean()中初始化了Form._errors属性,
# 并调用了_clean_fields(),_clean_form(),_post_clean()完成对表单的验证,
# 这三个方法分别对应字段层次的验证,表单层次的验证,ModelForm层次的额外验证。

两个钩子hook

1. _clean_fields() 里面的 clean_字段() 局部钩子

自定义字段验证规则

if hasattr(self, 'clean_%s' % name):        -> form_obj里是否自定义了clean_字段方法
    value = getattr(self, 'clean_%s' % name)    -> 有则赋值,否则异常触发
    self.cleand_data[name] = value

# 重写username字段的局部钩子

def clean_username(self):
    username = self.cleaned_data.get("username")
    is_exist = models.Student.objects.filter(username=username)
    if is_exist:
        raise ValidationError("用户名已存在!")    -> _clean_fields()内部会将异常捕捉并加入errors 
    return username

2. _clean_form() 里面的全局clean()钩子

自定义表单层次验证,例如确认密码校验

_clean_form() 会调用对象继承的clean()方法,但是默认什么也没干

# 重写父类的clean()方法,全局钩子

def clean(self):
    password = self.cleaned_data.get("password")
    re_password = self.cleaned_data.get("re_password")
    if re_password and re_password != password:
        self.add_error("re_password", ValidationError("两次密码不一致"))
    else:
        return self.cleaned_data

在Form.errors中指定一个存放表单层次的ValidationError的ErrorList,而它的field name则为__all__

错误信息写入none  -> __all__   ->  {{ form.non_field_errors }}

其他

去除select字段默认生成的多的“---------”选项

  1. 在model中设置默认值:

     user = models.ForeignKey(....,default=1)        # 可设置成默认0,则默认不选中,前端提交时仍为required
    

model中null=True 和 blank=True的区别

null 是针对数据库而言,如果 null=True, 表示数据库的该字段可以为空,即在Null字段显示为YES。多对多字段没有该属性
blank 是针对表单的,如果 blank=True,表示你的表单填写该字段的时候可以不填,但是对数据库来说,没有任何影响

修改样式属性

  • 第一种:直接修改

      class BookForm(forms.Form):
          name = forms.CharField(widget=forms.TextInput(attrs={'class': 'special'}))
          url = forms.URLField()
          comment = forms.CharField(widget=forms.TextInput(attrs={'size': '40'}))
    
  • 第二种:在表单定义中修改widget属性

      class CommentForm(forms.Form):
          name = forms.CharField()
          url = forms.URLField()
          comment = forms.CharField()
    
          name.widget.attrs.update({'class': 'special'})
          comment.widget.attrs.update(size='40')
    

自定义datetime日期格式

自定义显示格式的配置如下,更改Django的setting.py文件:

USE_L10N = False
DATE_FORMAT = 'Y-m-d'
DATETIME_FORMAT = 'Y-m-d H:i:s'

注意事项:如果USE_L10N设置为了True,那么语言环境规定的格式具有更高的优先级并将被应用,即DATE_FORMAT不生效。

这里可用的格式化字符串的其他写法参见Django官方文档

作业

============= 作业:xxxoo管理 =============
用户验证:session
新URL:Form验证
中间件:IP过滤
信号:记录操作
CSRF:

推荐阅读