• Python入门自学进阶-Web框架——15、Django的Form验证2


    对Form进行更深入的研究。

    使用Form,需要
    1、创建一个验证用户请求的模板,就是一个类:
    from django import forms
    class MyForm(forms.Form)
        user = forms.CharField(...)       # 默认生成 input type=‘text’
        email =  forms.EmailField(...)   # 默认生成input type=‘email’
        pwd = forms.PasswordField(...) # 默认生成input type=‘password’

    通过这个模板,前端就可以自动生成相应的html标签,CharField(...)默认是生成一个input的text标签等等。类就是一个模板,里面有几个字段,就验证几个字段

    2、类中要有相应的字段:user = forms.CharField(...)

    字段是继承了Field类的对象,主要是用来验证输入的某个字段的数据的合法性,如可以在参数中定义长度等,这个类主要是封装了一些正则表达式。

    当obj = MyForm(req.POST)
        obj.is_valid()     # 这个方法就是循环执行类中每个字段的验证规则,如这里是3个字段,如果有一个字段的验证结果为False,结果就为False,全正确,才为True。

    3、插件:
    对于字段,在前端生成HTML标签时,如果没有其他配置,会生成默认的标签的,就如CharField(...)默认是生成一个input的text标签。那是否可以改变这个默认生成的标签呢?可以使用插件来改变,就是在参数中配置“widget=”选项。如下形式

    user = forms.CharField(...,widget=Input框) 

    对于CharField类,看其源代码如下

     在CharField中没有widget,在其父类Field中定义了widget=TextInput

    对于其他的字段类,如下:

     基本上都是在本类中定义了默认的widget。

    django提供的字段类列表如下:

    所以,在定义不同的字段时,是可以通过widget修改不同的插件来生成不同的标签,fields.py中的这些类都是插件。

    如user = forms.CharField(widget=forms.PasswordField),生成的就是密码输入框,而不是默认的文本输入框。

    还可以给生成的标签添加属性:

    user = forms.CharField(widget=forms.TextField(attrs={‘class’:‘c1’,‘placeholder’:‘用户名’}))

    测试:

    模板类:

    1. from django import forms
    2. class MyForm(forms.Form):
    3. user = forms.CharField()
    4. user1 = forms.CharField(widget=forms.PasswordInput)
    5. user2 = forms.CharField(widget=forms.TextInput(attrs={'class':'cl1','placeholder':'用户名'}))
    6. user3 = forms.ChoiceField(choices=[(1,'足球'),(2,'篮球'),(3,'排球'),(4,'乒乓球')])

    视图函数:

    1. def detail(req):
    2. obj = myviews.MyForm()
    3. return render(req,'detail.html',{'obj':obj})

    前端:

    1. <!DOCTYPE html>
    2. <html lang="en">
    3. <head>
    4. <meta charset="UTF-8">
    5. <title>Title</title>
    6. </head>
    7. <body>
    8. <p>user:{{ obj.user }}</p>
    9. <p>user1:{{ obj.user1 }}</p>
    10. <p>user2:{{ obj.user2 }}</p>
    11. <p>user3:{{ obj.user3 }}</p>
    12. </body>
    13. </html>

    结果:

     

     模板的字段还具有自动类型转换的功能,如:

    user4 = forms.IntegerField(),前端传来的是字符串,到了这里,会自动转换为整型数。

     字段的参数:

    1. Field
    2. required=True, 是否必须输入值,即不能为空,默认设置
    3. widget=None, HTML插件
    4. label=None, 用于生成Label标签或显示内容
    5. initial=None, 初始值
    6. help_text='', 帮助信息(在标签旁边显示)
    7. error_messages=None, 错误信息 {'required': '不能为空', 'invalid': '格式错误'}
    8. show_hidden_initial=False, 是否在当前插件后面再加一个隐藏的且具有默认值的插件(可用于检验两次输入是否一直)
    9. validators=[], 自定义验证规则
    10. localize=False, 是否支持本地化
    11. disabled=False, 是否可以编辑
    12. label_suffix=None Label内容后缀
    13. CharField(Field)
    14. max_length=None, 最大长度
    15. min_length=None, 最小长度
    16. strip=True 是否移除用户输入空白
    17. IntegerField(Field)
    18. max_value=None, 最大值
    19. min_value=None, 最小值
    20. FloatField(IntegerField)
    21. ...
    22. DecimalField(IntegerField)
    23. max_value=None, 最大值
    24. min_value=None, 最小值
    25. max_digits=None, 总长度
    26. decimal_places=None, 小数位长度
    27. BaseTemporalField(Field)
    28. input_formats=None 时间格式化
    29. DateField(BaseTemporalField) 格式:2015-09-01
    30. TimeField(BaseTemporalField) 格式:11:12
    31. DateTimeField(BaseTemporalField)格式:2015-09-01 11:12
    32. DurationField(Field) 时间间隔:%d %H:%M:%S.%f
    33. ...
    34. RegexField(CharField)
    35. regex, 自定制正则表达式
    36. max_length=None, 最大长度
    37. min_length=None, 最小长度
    38. error_message=None, 忽略,错误信息使用 error_messages={'invalid': '...'}
    39. EmailField(CharField)
    40. ...
    41. FileField(Field)
    42. allow_empty_file=False 是否允许空文件
    43. ImageField(FileField)
    44. ...
    45. 注:需要PIL模块,pip3 install Pillow
    46. 以上两个字典使用时,需要注意两点:
    47. - form表单中 enctype="multipart/form-data"
    48. - view函数中 obj = MyForm(request.POST, request.FILES)
    49. URLField(Field)
    50. ...
    51. BooleanField(Field)
    52. ...
    53. NullBooleanField(BooleanField)
    54. ...
    55. ChoiceField(Field)
    56. ...
    57. choices=(), 选项,如:choices = ((0,'上海'),(1,'北京'),)
    58. required=True, 是否必填
    59. widget=None, 插件,默认select插件
    60. label=None, Label内容
    61. initial=None, 初始值
    62. help_text='', 帮助提示
    63. ModelChoiceField(ChoiceField)
    64. ... django.forms.models.ModelChoiceField
    65. queryset, # 查询数据库中的数据
    66. empty_label="---------", # 默认空显示内容
    67. to_field_name=None, # HTML中value的值对应的字段
    68. limit_choices_to=None # ModelForm中对queryset二次筛选
    69. ModelMultipleChoiceField(ModelChoiceField)
    70. ... django.forms.models.ModelMultipleChoiceField
    71. TypedChoiceField(ChoiceField)
    72. coerce = lambda val: val 对选中的值进行一次转换
    73. empty_value= '' 空值的默认值
    74. MultipleChoiceField(ChoiceField)
    75. ...
    76. TypedMultipleChoiceField(MultipleChoiceField)
    77. coerce = lambda val: val 对选中的每一个值进行一次转换
    78. empty_value= '' 空值的默认值
    79. ComboField(Field)
    80. fields=() 使用多个验证,如下:即验证最大长度20,又验证邮箱格式
    81. fields.ComboField(fields=[fields.CharField(max_length=20), fields.EmailField(),])
    82. MultiValueField(Field)
    83. PS: 抽象类,子类中可以实现聚合多个字典去匹配一个值,要配合MultiWidget使用
    84. SplitDateTimeField(MultiValueField)
    85. input_date_formats=None, 格式列表:['%Y--%m--%d', '%m%d/%Y', '%m/%d/%y']
    86. input_time_formats=None 格式列表:['%H:%M:%S', '%H:%M:%S.%f', '%H:%M']
    87. FilePathField(ChoiceField) 文件选项,目录下文件显示在页面中
    88. path, 文件夹路径
    89. match=None, 正则匹配
    90. recursive=False, 递归下面的文件夹
    91. allow_files=True, 允许文件
    92. allow_folders=False, 允许文件夹
    93. required=True,
    94. widget=None,
    95. label=None,
    96. initial=None,
    97. help_text=''
    98. GenericIPAddressField
    99. protocol='both', both,ipv4,ipv6支持的IP格式
    100. unpack_ipv4=False 解析ipv4地址,如果是::ffff:192.0.2.1时候,可解析为192.0.2.1, PS:protocol必须为both才能启用
    101. SlugField(CharField) 数字,字母,下划线,减号(连字符)
    102. ...
    103. UUIDField(CharField) uuid类型
    104. ...

    测试:

    后端:

    1. from django.shortcuts import render,HttpResponse,redirect,HttpResponseRedirect
    2. from myadminzdy import models
    3. from django import forms
    4. from django.core.validators import RegexValidator
    5. class MyForm(forms.Form):
    6. # user = forms.CharField()
    7. # user1 = forms.CharField(widget=forms.PasswordInput)
    8. # user2 = forms.CharField(widget=forms.TextInput(attrs={'class':'cl1','placeholder':'用户名'}))
    9. # user3 = forms.ChoiceField(choices=[(1,'足球'),(2,'篮球'),(3,'排球'),(4,'乒乓球')],)
    10. f1 = forms.CharField() #默认字段不能为空,即required=True
    11. f2 = forms.CharField(required=False) # 字段可以为空
    12. f3 = forms.CharField(label="f3的label") # 前端可以使用.label、.id_for_label、.label_tag
    13. f4 = forms.CharField(initial='初始值') # 生成的标签有初始值
    14. f5 = forms.CharField(initial='初始值11111',show_hidden_initial=True) #在生成一个显示的标签外,还生成一个隐藏的标签,其值保持原始值,可用于标签值的比对
    15. f6 =forms.CharField(validators=[RegexValidator(r'^[0-9]+$','jiaoyan1:quanshuzi'),RegexValidator(r'^135[0-9]+$','jiaoyan2:135kaishi')])
    16. # 添加自定义的校验规则,使用validators=参数,规则是RegexValidator的对象,这里添加两条,它的两个参数,一个是规则,一个是错误信息
    17. # 打印的错误信息:{"f6": [{"message": "jiaoyan1:quanshuzi", "code": "invalid"}, {"message": "jiaoyan2:135kaishi", "code": "invalid"}]}
    18. f7 = forms.CharField(validators=[RegexValidator(r'^[0-9]+$', 'jiaoyan1:quanshuzi'),
    19. RegexValidator(r'^135[0-9]+$', 'jiaoyan2:135kaishi')],
    20. error_messages={'required':'111-buweikong','invalid':'222-geshicuowu'})
    21. # 测试error_message参数
    22. # {"f6": [{"message": "jiaoyan1:quanshuzi", "code": "invalid"}, {"message": "jiaoyan2:135kaishi", "code": "invalid"}],
    23. # "f7": [{"message": "222-geshicuowu", "code": "invalid"}, {"message": "222-geshicuowu", "code": "invalid"}]}
    24. # 可以看到,设置error_message后,最后的错误信息以error_message设置的为主
    25. # 对于RegexValidator,还可以第三个参数:code,来定义不同的类型,如系统中的required、invalid等
    26. f8 = forms.CharField(validators=[RegexValidator(r'^[0-9]+$', 'jiaoyan1:quanshuzi',code='f1'),
    27. RegexValidator(r'^135[0-9]+$', 'jiaoyan2:135kaishi',code='f2')],
    28. error_messages={'required':'111-buweikong','invalid':'222-geshicuowu'})
    29. # {"f6": [{"message": "jiaoyan1:quanshuzi", "code": "invalid"}, {"message": "jiaoyan2:135kaishi", "code": "invalid"}],
    30. # "f7": [{"message": "222-geshicuowu", "code": "invalid"}, {"message": "222-geshicuowu", "code": "invalid"}],
    31. # "f8": [{"message": "jiaoyan1:quanshuzi", "code": "f1"}, {"message": "jiaoyan2:135kaishi", "code": "f2"}]}
    32. # error_message优先级高,根据code进行覆盖,想要修改原生的错误信息,可以设置error_message。
    33. f9 = forms.RegexField(r'^135[0-9]+$') # 自定义正则表达式,自定义校验规则的字段,前端默认生成文本输入框
    34. f10 = forms.FileField() # 上传文件,生成input type为file的标签,
    35. # clean()中显示的:'f10': <InMemoryUploadedFile: 1.jpg (image/jpeg)>
    36. f11 = forms.ImageField() # 类似FileField,生成标签多了accept="image/*"
    37. f12 = forms.ChoiceField(
    38. choices=[(1,'羽毛球'),(2,'乒乓球'),(3,'蓝球'),(4,'排球')],
    39. initial=3
    40. )
    41. # 下列选择框,值是字符串
    42. f13 = forms.TypedChoiceField(
    43. coerce=lambda x:int(x), #类型转换
    44. choices=[(1, '羽毛球'), (2, '乒乓球'), (3, '蓝球'), (4, '排球')],
    45. initial=3
    46. )
    47. #f12和f13结果进行比较:'f12': '3', 'f13': 3,一个是字符串,一个是整型
    48. f14 = forms.MultipleChoiceField(
    49. choices=[(1, '羽毛球'), (2, '乒乓球'), (3, '蓝球'), (4, '排球')],
    50. initial=[1,3]
    51. )
    52. # 多选框,要注意初始值是列表。
    53. f15 = forms.FilePathField(path='myadminzdy/',allow_folders=True,allow_files=True,recursive=True)
    54. # 下拉选择框,内容是对应路径下的文件
    55. def detail(req):
    56. if req.method == "GET":
    57. obj = MyForm()
    58. return render(req,'detail.html',{'obj':obj})
    59. else:
    60. obj = MyForm(req.POST,req.FILES)
    61. # 如果要接收文件,参数中增加req.FILES,因为上传的文件是保存在FILES中的
    62. obj.is_valid()
    63. print(obj.clean())
    64. print(obj.errors.as_json())
    65. return render(req,'detail.html',{'obj':obj})

    前端:

    1. <!DOCTYPE html>
    2. <html lang="en">
    3. <head>
    4. <meta charset="UTF-8">
    5. <title>Title</title>
    6. </head>
    7. <script src="/static/jquery-3.6.0.js"></script>
    8. <script>
    9. $('#id_f5').attr(value)
    10. </script>
    11. <body>
    12. <form action="detail.html" method="post" enctype="multipart/form-data">
    13. {% csrf_token %}
    14. <p>f1:{{ obj.f1 }}</p>
    15. <p>f2:{{ obj.f2 }}</p>
    16. <!-- <p>{{ obj.f3.label }}:{{ obj.f3 }}</p> 这种写法标签只是一个文本 -->
    17. <!--<p>{{ obj.f3.id_for_label }}{{ obj.f3 }}</p> --> <!-- 还有一个属性id_for_label,将id值做标签 -->
    18. <!-- <p><label for="{{ obj.f3.id_for_label }}">{{ obj.f3.label }}</label>{{ obj.f3 }}</p>
    19. 这种写法,点击标签,焦点进入相应的输入框 -->
    20. {{ obj.f3.label_tag }}{{ obj.f3 }} <!-- 上面写法可以这样实现 -->
    21. <p>测试初始值{{ obj.f4 }}</p>
    22. <p>测试生成隐藏标签{{ obj.f5 }}</p>
    23. <!-- <input type="text" name="f5" value="初始值11111" id="id_f5">
    24. <input type="hidden" name="initial-f5" value="初始值11111" id="initial-id_f5">
    25. -->
    26. <p>自定义校验规则 : {{ obj.f6 }}</p>
    27. <p>自定义校验规则 : {{ obj.f7 }}</p>
    28. <p>自定义校验规则 : {{ obj.f8 }}</p>
    29. <p>自定义校验规则 : {{ obj.f9 }}</p>
    30. <p>文件上传: {{ obj.f10 }}</p>
    31. <p>tuoian上传: {{ obj.f11 }}</p>
    32. <p>下拉选择框: {{ obj.f12 }}</p>
    33. <p>下拉选择框类型转换: {{ obj.f13 }}</p>
    34. <p>下拉多选选择框: {{ obj.f14 }}</p>
    35. <p>下拉选择框-文件列表: {{ obj.f15 }}</p>
    36. <p><input type="submit" value="提交"></p>
    37. </form>
    38. </body>
    39. </html>

    django提供的widget插件:

    1. TextInput(Input)
    2. NumberInput(TextInput)
    3. EmailInput(TextInput)
    4. URLInput(TextInput)
    5. PasswordInput(TextInput)
    6. HiddenInput(TextInput)
    7. Textarea(Widget)
    8. DateInput(DateTimeBaseInput)
    9. DateTimeInput(DateTimeBaseInput)
    10. TimeInput(DateTimeBaseInput)
    11. CheckboxInput
    12. Select
    13. NullBooleanSelect
    14. SelectMultiple
    15. RadioSelect
    16. CheckboxSelectMultiple
    17. FileInput
    18. ClearableFileInput
    19. MultipleHiddenInput
    20. SplitDateTimeWidget
    21. SplitHiddenDateTimeWidget
    22. SelectDateWidget
    1. # 单radio,值为字符串
    2. # user = fields.CharField(
    3. # initial=2,
    4. # widget=widgets.RadioSelect(choices=((1,'上海'),(2,'北京'),))
    5. # )
    6. # 单radio,值为字符串
    7. # user = fields.ChoiceField(
    8. # choices=((1, '上海'), (2, '北京'),),
    9. # initial=2,
    10. # widget=widgets.RadioSelect
    11. # )
    12. # 单select,值为字符串
    13. # user = fields.CharField(
    14. # initial=2,
    15. # widget=widgets.Select(choices=((1,'上海'),(2,'北京'),))
    16. # )
    17. # 单select,值为字符串
    18. # user = fields.ChoiceField(
    19. # choices=((1, '上海'), (2, '北京'),),
    20. # initial=2,
    21. # widget=widgets.Select
    22. # )
    23. # 多选select,值为列表
    24. # user = fields.MultipleChoiceField(
    25. # choices=((1,'上海'),(2,'北京'),),
    26. # initial=[1,],
    27. # widget=widgets.SelectMultiple
    28. # )
    29. # 单checkbox
    30. # user = fields.CharField(
    31. # widget=widgets.CheckboxInput()
    32. # )
    33. # 多选checkbox,值为列表
    34. # user = fields.MultipleChoiceField(
    35. # initial=[2, ],
    36. # choices=((1, '上海'), (2, '北京'),),
    37. # widget=widgets.CheckboxSelectMultiple
    38. # )

    对于字段,主要是进行验证的,对于widget插件,主要是定义前端生成的标签类型,如果字段定义的是CharField,而插件使用的是MultipleSelect,前端返回的是一个列表,但是验证是按字符串进行验证,就失去验证的意义,所以要做好两者的匹配。

    下拉单选框,通过数据库动态获取数据:

    页面:

     后端代码:

    1. # models中代码,生成数据库
    2. class UserType(models.Model):
    3. caption = models.CharField(max_length=32)
    4. # 视图函数中
    5. # 定义模板类
    6. from myadminzdy import models
    7. class MyFormDb(forms.Form):
    8. host = forms.CharField()
    9. host_type = forms.IntegerField(
    10. # widget=forms.Select(choices=[(1,'BJ'),(2,'SH')]) # 静态的获取
    11. # widget = forms.Select(choices=models.UserType.objects.all().values_list('id','caption'))
    12. # 从数据库动态获取,django启动时获取,只执行一次
    13. widget=forms.Select(choices=[])
    14. )
    15. # 为了在数据库记录改变时,前端动态获取到最新的数据,需要每次生成对象时执行一次获取数据
    16. def __init__(self,*args,**kwargs):
    17. super(MyFormDb,self).__init__(*args,**kwargs)
    18. self.fields['host_type'].widget.choices = models.UserType.objects.all().values_list('id','caption')
    19. # 每次实例化都会重新获取数据库数据并赋值给choices。
    20. #视图函数
    21. def db(req):
    22. if req.method == "GET":
    23. obj = MyFormDb()
    24. return render(req, 'db.html', {'obj': obj})
    25. else:
    26. obj = MyForm(req.POST, req.FILES)
    27. # 如果要接收文件,参数中增加req.FILES,因为上传的文件是保存在FILES中的
    28. obj.is_valid()
    29. print(obj.clean())
    30. print(obj.errors.as_json())
    31. return render(req, 'db.html', {'obj': obj})

    前端:

    1. <!DOCTYPE html>
    2. <html lang="en">
    3. <head>
    4. <meta charset="UTF-8">
    5. <title>Title</title>
    6. </head>
    7. <body>
    8. {{ obj.host }}
    9. {{ obj.host_type }}
    10. </body>
    11. </html>

    关键点是,在定义模板类时,按照以前的写法,只定义字段,在定义字段时设置choices获取数据库数据,测试时,在后台数据库改变时,前端数据没有改变,因为字段的choices值只在第一次加载时执行了一次,以后都是这个加载值的拷贝,所以需要定义__init__(),在每次实例化时都执行一遍查询数据库,并赋值给choices,这样就实现数据的实时更新。

    注意这一句:
    self.fields['host_type'].widget.choices = models.UserType.objects.all().values_list('id','caption')

    通过这个推断,模板类中有一个字典类型字段fields,我们定义的字段作为其中的键值对,所以可以使用fields['host_type']获取到forms.IntegerField(widget=forms.Select(choices=[])),这又是一个字典结构,可以通过.widget获取到forms.Select(choices=[]),又是一个字典结构,在.choices获取到choices。

    定义一个用户表:

    1. class UserType(models.Model):
    2. caption = models.CharField(max_length=32)
    3. class User(models.Model):
    4. username = models.CharField(max_length=32)
    5. user_type = models.ForeignKey('UserType',on_delete=models.DO_NOTHING)

    前台请求传递一个用户id,返回其用户名和类型:

    1. def db(req):
    2. if req.method == "GET":
    3. nid = req.GET.get('nid')
    4. m = models.User.objects.filter(id=nid).first()
    5. dic = {'host':m.username,'host_type':m.user_type_id}
    6. obj = MyFormDb(dic)
    7. return render(req, 'db.html', {'obj': obj})

    只需要形成一个验证模板类字段的字典格式数据,这里就是dic ={'host':'','host_type':''},传递给生成的模板对象就可以了。

  • 相关阅读:
    java毕业设计峨眉山景点介绍及旅游攻略推荐平台Mybatis+系统+数据库+调试部署
    redis集群之分片集群的原理和常用代理环境部署
    循环队列的实现
    从SimpleKV到Redis
    2024/2/18 图论 最短路入门 dijkstra 2
    UNPV2 学习:Posix Message Queues Exercise 解答记录
    Nautilus Chain 与 Coin98 生态达成合作,加速 Zebec 生态亚洲战略进
    Solidity智能合约事件(event)
    从这 5 个 DevOps “恐怖故事”,我们能学到什么?
    iptables不同网段的ip转发
  • 原文地址:https://blog.csdn.net/kaoa000/article/details/125168374