整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

Django 表单

Django 表单

TML表单是网站交互性的经典方式。 本章将介绍如何用Django对用户提交的表单数据进行处理。


HTTP 请求

HTTP协议以"请求-回复"的方式工作。客户发送请求时,可以在请求中附加数据。服务器通过解析请求,就可以获得客户传来的数据,并根据URL来提供特定的服务。

GET 方法

我们在之前的项目中创建一个 search.py 文件,用于接收用户的请求:

/HelloWorld/HelloWorld/search.py 文件代码:

# -*- coding: utf-8 -*-fromdjango.httpimportHttpResponsefromdjango.shortcutsimportrender_to_response# 表单defsearch_form(request): returnrender_to_response('search_form.html')# 接收请求数据defsearch(request): request.encoding='utf-8'if'q'inrequest.GET: message='你搜索的内容为: ' + request.GET['q']else: message='你提交了空表单'returnHttpResponse(message)

在模板目录 templates 中添加 search_form.html 表单:

/HelloWorld/templates/search_form.html 文件代码:

<!DOCTYPEhtml><html><head><metacharset="utf-8"><title>菜鸟教程(runoob.com)</title></head><body><formaction="/search"method="get"><inputtype="text"name="q"><inputtype="submit"value="搜索"></form></body></html>

urls.py 规则修改为如下形式:

/HelloWorld/HelloWorld/urls.py 文件代码:

fromdjango.conf.urlsimporturlfrom . importview,testdb,searchurlpatterns=[url(r'^hello$', view.hello), url(r'^testdb$', testdb.testdb), url(r'^search-form$', search.search_form), url(r'^search$', search.search),]

访问地址 http://127.0.0.1:8000/search-form 并搜索,结果如下所示:

POST 方法

上面我们使用了GET方法。视图显示和请求处理分成两个函数处理。

提交数据时更常用POST方法。我们下面使用该方法,并用一个URL和处理函数,同时显示视图和处理请求。

我们在tmplate 创建 post.html:

/HelloWorld/tmplates/post.html 文件代码:

<!DOCTYPEhtml><html><head><metacharset="utf-8"><title>菜鸟教程(runoob.com)</title></head><body><formaction="/search-post"method="post"> {% csrf_token %} <inputtype="text"name="q"><inputtype="submit"value="Submit"></form><p>{{ rlt }}</p></body></html>

在模板的末尾,我们增加一个 rlt 记号,为表格处理结果预留位置。

表格后面还有一个{% csrf_token %}的标签。csrf 全称是 Cross Site Request Forgery。这是Django提供的防止伪装提交请求的功能。POST 方法提交的表格,必须有此标签。

在HelloWorld目录下新建 search2.py 文件并使用 search_post 函数来处理 POST 请求:

/HelloWorld/HelloWorld/search2.py 文件代码:

# -*- coding: utf-8 -*-fromdjango.shortcutsimportrenderfromdjango.views.decoratorsimportcsrf# 接收POST请求数据defsearch_post(request): ctx={} ifrequest.POST: ctx['rlt']=request.POST['q']returnrender(request, "post.html", ctx)

urls.py 规则修改为如下形式:

/HelloWorld/HelloWorld/urls.py 文件代码:

fromdjango.conf.urlsimporturlfrom . importview,testdb,search,search2urlpatterns=[url(r'^hello$', view.hello), url(r'^testdb$', testdb.testdb), url(r'^search-form$', search.search_form), url(r'^search$', search.search), url(r'^search-post$', search2.search_post),]

访问 http://127.0.0.1:8000/search-post 显示结果如下:

完成以上实例后,我们的目录结构为:

HelloWorld|-- HelloWorld| |-- __init__.py| |-- __init__.pyc| |-- search.py| |-- search.pyc| |-- search2.py| |-- search2.pyc| |-- settings.py| |-- settings.pyc| |-- testdb.py| |-- testdb.pyc| |-- urls.py| |-- urls.pyc| |-- view.py| |-- view.pyc| |-- wsgi.py| `-- wsgi.pyc
|-- TestModel
| |-- __init__.py
| |-- __init__.pyc
| |-- admin.py
| |-- admin.pyc
| |-- apps.py
| |-- migrations
| | |-- 0001_initial.py
| | |-- 0001_initial.pyc
| | |-- __init__.py
| | `-- __init__.pyc| |-- models.py| |-- models.pyc| |-- tests.py| `-- views.py
|-- db.sqlite3
|-- manage.py
`-- templates |-- base.html |-- hello.html |-- post.html `-- search_form.html

Request 对象

每个 view 函数的第一个参数是一个 HttpRequest 对象,就像下面这个 hello() 函数:

from django.http import HttpResponsedef hello(request):
 return HttpResponse("Hello world")

HttpRequest对象包含当前请求URL的一些信息:

属性

描述

path

请求页面的全路径,不包括域名—例如, "/hello/"。

  • CONTENT_LENGTH

  • CONTENT_TYPE

  • QUERY_STRING: 未解析的原始查询字符串

  • REMOTE_ADDR: 客户端IP地址

  • REMOTE_HOST: 客户端主机名

  • SERVER_NAME: 服务器主机名

  • SERVER_PORT: 服务器端口

META 中这些头加上前缀HTTP_最为Key, 例如:

  • HTTP_ACCEPT_ENCODING

  • HTTP_ACCEPT_LANGUAGE

  • HTTP_HOST: 客户发送的HTTP主机头信息

  • HTTP_REFERER: referring页

  • HTTP_USER_AGENT: 客户端的user-agent字符串

  • HTTP_X_BENDER: X-Bender头信息

method

请求中使用的HTTP方法的字符串表示。全大写表示。例如:

if request.method=='GET':

do_something()

elif request.method=='POST':

do_something_else()

GET

包含所有HTTP GET参数的类字典对象。参见QueryDict 文档。

  • filename: 上传文件名,用Python字符串表示

  • content-type: 上传文件的Content type

  • content: 上传文件的原始内容

注意:只有在请求方法是POST,并且请求页面中<form>有enctype="multipart/form-data"属性时FILES才拥有数据。否则,FILES 是一个空字典。


POST

包含所有HTTP POST参数的类字典对象。参见QueryDict 文档。

服务器收到空的POST请求的情况也是有可能发生的。也就是说,表单form通过HTTP POST方法提交请求,但是表单中可以没有数据。因此,不能使用语句if request.POST来判断是否使用HTTP POST方法;应该使用if request.method=="POST" (参见本表的method属性)。

注意: POST不包括file-upload信息。参见FILES属性。

REQUEST

为了方便,该属性是POST和GET属性的集合体,但是有特殊性,先查找POST属性,然后再查找GET属性。借鉴PHP's $_REQUEST。

例如,如果GET={"name": "john"} 和POST={"age": '34'},则 REQUEST["name"] 的值是"john", REQUEST["age"]的值是"34".

强烈建议使用GET and POST,因为这两个属性更加显式化,写出的代码也更易理解。

COOKIES

包含所有cookies的标准Python字典对象。Keys和values都是字符串。

FILES

包含所有上传文件的类字典对象。FILES中的每个Key都是<input type="file" name="" />标签中name属性的值. FILES中的每个value 同时也是一个标准Python字典对象,包含下面三个Keys:

META

包含所有可用HTTP头部信息的字典。 例如:

user

是一个django.contrib.auth.models.User 对象,代表当前登录的用户。

如果访问用户当前没有登录,user将被初始化为django.contrib.auth.models.AnonymousUser的实例。

你可以通过user的is_authenticated()方法来辨别用户是否登录:

if request.user.is_authenticated():
 # Do something for logged-in users.else:
 # Do something for anonymous users.

只有激活Django中的AuthenticationMiddleware时该属性才可用

session

唯一可读写的属性,代表当前会话的字典对象。只有激活Django中的session支持时该属性才可用。

raw_post_data

原始HTTP POST数据,未解析过。 高级处理时会有用处。

Request对象也有一些有用的方法:

方法描述
__getitem__(key)返回GET/POST的键值,先取POST,后取GET。如果键不存在抛出 KeyError。

这是我们可以使用字典语法访问HttpRequest对象。

例如,request["foo"]等同于先request.POST["foo"] 然后 request.GET["foo"]的操作。

has_key()检查request.GET or request.POST中是否包含参数指定的Key。
get_full_path()返回包含查询字符串的请求路径。例如, "/music/bands/the_beatles/?print=true"
is_secure()如果请求是安全的,返回True,就是说,发出的是HTTPS请求。

QueryDict对象

在HttpRequest对象中, GET和POST属性是django.http.QueryDict类的实例。

QueryDict类似字典的自定义类,用来处理单键对应多值的情况。

QueryDict实现所有标准的词典方法。还包括一些特有的方法:

方法描述

__getitem__

和标准字典的处理有一点不同,就是,如果Key对应多个Value,__getitem__()返回最后一个value。

__setitem__

设置参数指定key的value列表(一个Python list)。注意:它只能在一个mutable QueryDict 对象上被调用(就是通过copy()产生的一个QueryDict对象的拷贝).

get()

如果key对应多个value,get()返回最后一个value。

update()

参数可以是QueryDict,也可以是标准字典。和标准字典的update方法不同,该方法添加字典 items,而不是替换它们:

>>> q=QueryDict('a=1')>>> q=q.copy() # to make it mutable>>> q.update({'a': '2'})>>> q.getlist('a')
['1', '2']>>> q['a'] # returns the last['2']

items()

和标准字典的items()方法有一点不同,该方法使用单值逻辑的__getitem__():

>>> q=QueryDict('a=1&a=2&a=3')>>> q.items()[('a', '3')]

values()

和标准字典的values()方法有一点不同,该方法使用单值逻辑的__getitem__():

此外, QueryDict也有一些方法,如下表:

方法描述

copy()

返回对象的拷贝,内部实现是用Python标准库的copy.deepcopy()。该拷贝是mutable(可更改的) — 就是说,可以更改该拷贝的值。

getlist(key)

返回和参数key对应的所有值,作为一个Python list返回。如果key不存在,则返回空list。 It's guaranteed to return a list of some sort..

setlist(key,list_)

设置key的值为list_ (unlike __setitem__()).

appendlist(key,item)

添加item到和key关联的内部list.

setlistdefault(key,list)

和setdefault有一点不同,它接受list而不是单个value作为参数。

lists()

和items()有一点不同, 它会返回key的所有值,作为一个list, 例如:

>>> q=QueryDict('a=1&a=2&a=3')>>> q.lists()[('a',['1','2','3'])]

urlencode()

返回一个以查询字符串格式进行格式化后的字符串(e.g., "a=2&b=3&b=5").

章来源于公众号【Python野路子】


HTML表单是网站交互性的经典方式,本章将记录如何用Django对用户提交的表单数据进行处理。

HTML中的表单

站在前端角度,form表单HTML中用于提交数据给服务器的一个标签,所有的表单元素(input/textarea/button...)都要放在form标签当中,还有以下参数:

  • formmethod参数用于设置表单的提交方式,默认使用GET
  • action用于设置表单的提交url,如果不写或者保持空字符串,那么将使用当前的URL,建议尽量指定一个url,因为有些浏览器可能兼容问题,不填是不能获取到对应的action的。

Django中的表单

Django中的表单不是html中的那个表单,这里是指Django中的组件名叫表单,主要做以下2件事:

1)表单验证数据的合法性。

2)通渲染表单模板;

Form

Form类在from django import forms中,使用时需要定义一个Form的子类,相当于将请求的表单数据封装到一个特殊的类中,并自动完成一些数据的验证工作。

Form基本使用

1)先在某个应用app下,新建一个forms.py的文件(类似前面学过的views.pyurls.py在对应app应用下)。

2)在里面定义一个表单类,继承自django.forms.Form

 from django import forms 

3)在表单类中,创建字段与模型类类似,但是没有null=True(是否接受空值NULL,默认值False)或者blank=True(是否接受空白内容,默认为False)等这几种参数了,有的参数是required=True/False(请求能否为空,True不能为空,默认为True)。

from django import forms

class RegisterForm(forms.Form):
    # label属性是form表单中特有的属性,代表这个字段的描述,这个属性类似于模型类中的verbose_name属性
    username = forms.CharField(label=u'用户名', max_length=20, min_length=3)
    # 存储到数据库的密码,是一个加密后的字符串,但是这里是通过前端传输过来的,并没进行加密
    password = forms.CharField(label=u'密码', max_length=20, min_length=8)

4)表单生成HTML表单元素。

# views.py

class RegisterView(View):
    def get(self, request):
        # 如果需要使用django表单渲染html页面
        # 实例化该表单模型,传递给前端
        form = RegisterForm()
        return render(request, 'register_form.html', {'form': form})

    def post(self, request):
        # 如果不使用django表单,需要一个一个的值取出来,并且需要自己写对应的验证
        username = request.POST.get('username')
        password = request.POST.get('password')
        return render(request, 'register_form.html', locals())
<!-- register_form.html -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    {% if username %}
        提交的post数据:
        {{ username }}
        {{ password }}
    {% else %}
        <form action = "{% url "user:register" %}" method="post">
            {% csrf_token %}
            {{ form }}  <!--会自动识别表单属性的 -->
            <input type="submit" vlaue='注册'>
        </form>
    {% endif %}
</body>
</html>




注意:使用DjangoForm类生成的表单,不包含formsubmit按钮两个标签,需要手动添加。

一般用于生成HTML表单元素很少使用,尤其在现在前后分离趋势下,这个功能很鸡肋,把前端该做的事情放到后台来实现,增加了代码的耦合性,也增加了服务器的压力。

我们一般使用forms组件的校验功能,比如账号长度必须6~12位,密码长度必须为8~20位,且必须含大小写字母,我们可以一个个获取前端传过来的字段进行一个个校验,如果字段比较多,如果一个个单独校验,那比较繁琐,那我们可以使用django提供的forms组件,我们先来看个简单的。

# forms.py
'''
forms.py的作用
它是专门编写你的forms配置的模型
forms.py本身命名没有要求,你可以为任意名称, 但是我们一般约定它叫forms,代表这个文件是专门处理该APP下处理表单组件的
'''
from django import forms

class RegisterForm(forms.Form):
    username = forms.CharField(max_length=20, min_length=3)
    password = forms.CharField(max_length=20, min_length=8)

# views.py

class RegisterView(View):
    def get(self, request):
        # 如果需要使用django表单渲染html页面
        # 实例化该表单模型,传递给前端
        form = RegisterForm()
        return render(request, 'register_form.html', {'form': form})

    def post(self, request):
        # 满足Form里面
        form = RegisterForm({'username': 'admin', 'password': '12345678'})
        print(form.is_valid())  # True
        print(form.cleaned_data)  # {'username': 'admin', 'password': '12345678'},输出全部校验正确的字段
        print(form.errors)

        # 其中一个不满足,例如密码长度少于8
        form = RegisterForm({'username': 'admin', 'password': '12345'})
        print(form.is_valid())  # False
        print(form.cleaned_data)  #  {'username': 'admin'}, 输出校验正确的字段
        print(form.errors)
        '''
        <ul class="errorlist">
            <li>password
                <ul class="errorlist">
                    <li>Ensure this value has at least 8 characters (it has 5).</li>
                </ul>
            </li>
        </ul>
        '''
        print(type(form.errors))  # <class 'django.forms.utils.ErrorDict'>
        print(form['password'].errors)
        # <ul class="errorlist"><li>Ensure this value has at least 8 characters (it has 5).</li></ul>
        print(form.errors.get('password'))
        # <ul class="errorlist"><li>Ensure this value has at least 8 characters (it has 5).</li></ul>
        print(form.errors.get_json_data())
        # {'password': [{'message': 'Ensure this value has at least 8 characters (it has 5).', 'code': 'min_length'}]}


        # 比Form少一个字段
        form = RegisterForm({'username': 'admin'})
        print(form.is_valid())  # False
        print(form.cleaned_data)  # {'username': 'admin'}
        print(form.errors)
        '''
        <ul class="errorlist">
            <li>password
                <ul class="errorlist">
                    <li>This field is required.</li>
                </ul>
            </li>
        </ul>        
        '''
        print(form.errors.get('password'))
        # <ul class="errorlist"><li>This field is required.</li></ul>
        
        print(form.errors.get_json_data())
        # {'password': [{'message': 'This field is required.', 'code': 'required'}]}


        # 比Form多一个字段,例如多个age
        form = RegisterForm({'username': 'admin', 'password': '12345678', 'age': 18})
        print(form.is_valid())  # True
        print(form.cleaned_data)  # {'username': 'admin', 'password': '12345678'}, 比Form多的字段不输出
        print(form.errors)

        return render(request, 'register_form.html', locals())

我们这里先传几个固定的参数来进行测试,通过上面测试,我们可以总结下。

1)使用is_valid()方法可以验证用户提交的数据是否合法,这个方法会返回一个bool值,合法则返回True,否则返回False。其中在实例化一个form对象时,传的参数必须与Form里面定义的字段:

  • 传的字段名一样;
  • 字段值必须满足Form里面的校验规则;
  • 传的参数个数 等于或大于 Form定义的个数。

2)cleaned_data()获取满足Form校验规则的字段, 使用cleaned_data必须执行完is_valid()方法。

3)如果表单校验没有通过,form对象则会产生一个errors属性,这个属性包括所有的验证错误信息。我们可以获取错误信息传递给前端。

  • form.errors:这个属性获取的错误信息是一个包含html标签的错误信息。
  • 通过form['属性名'].errosform.errors.get('属性名')访问对应的错误。
  • 通过form.errors.get_json_data()form['属性名'].erros.get_json_data()可以将错误消息转换成JSON数据。
  • 默认的错误信息是英文不够人性化,我们可以通过在Field中添加一个error_messagesdict类型的参数,然后根据属性名设置对应的message,例如以下代码:password=forms.CharField(label=u'密码', max_length=20, min_length=8, error_messages={'required':u'密码不能为空'})

必须要执行完is_valid函数,否则errors是不会包含错误。

常用Field字段

在表单中,创建字段跟模型是一模一样的,但是没有null=True(是否接受空值NULL,默认值False)或者blank=True(是否接受空白内容,默认为False)等这几种参数了,有的参数是required=True/False(请求能否为空,True不能为空,默认为True)。

使用Field可以是对数据验证的第一步。你期望这个提交上来的数据是什么类型,那么就使用什么类型的Field

CharField

用来接收文本。参数:

  • max_length:这个字段值的最大长度。
  • min_length:这个字段值的最小长度。
  • required:这个字段是否是必须的。默认是True,必须的。
  • error_messages:字段验证失败时给出的错误提示信息,需要传入一个字典,字典中需要指定对应验证条件的错误提示信息,如error_messages={'max_length': '最多只能有100个字符!'}表示指定文本最大长度不满足时给出提示信息。

EmailField

用来接收邮件,会自动验证邮箱格式是否合法。错误信息的key:requiredinvalid

FloatField

用来接收浮点类型,并且如果验证通过后,会将这个字段的值转换为浮点类型。参数:

  • max_value:最大的值。
  • min_value:最小的值。

错误信息的key:requiredinvalidmax_valuemin_value

IntegerField

用来接收整形,并且验证通过后,会将这个字段的值转换为整形。参数:

  • max_value:最大的值。
  • min_value:最小的值。

错误信息的key:requiredinvalidmax_valuemin_value

URLField

用来接收url格式的字符串。错误信息的key:requiredinvalid

FileField

用来接收文件,allow_empty_file=False是否允许空文件。

ImageField

接收图片,注意需要PIL模块,pip3 install Pillow,以上2个字典使用时,需要注意2点:

  • form表单中enctype="multipart/form-data"
  • view函数中my_form=MyForm(request.POST, request.FILES)

验证器

Form类中的各个Field字段其实都有一些基本的验证器,如果表单中的某个字段想要额外添加一些验证功能,可以指定validators参数给字段添加一些验证器,或者给这个字段定义一个额外的形如clean_[字段名]自定义验证方法。

内置验证器

内置验证器通过字段的validators参数指定对应的验证器列表即可,Django内置的验证器都在django.core.validators中,常用的内置验证器:

  • MaxValueValidator:最大值。
  • MinValueValidator:最小值。
  • MaxLengthValidator:最大长度。
  • MinLengthValidator:最小长度。
  • EmailValidator:是否为邮箱格式。
  • URLValidator:是否为url格式。
  • RegexValidator:是否符合正则表达式,使用时传入一个正则表达式即可。
  • FileExtensionValidator:验证文件名后缀是否符合要求,使用时传入一个文件名后缀的列表,如['txt', 'csv'],表示只允许上传这些类型的文件。

自定义验证

系统自带表单校验规则,有时无法满足我们的需求,比如在注册的表单验证中,我们需要验证用户是否已经被注册过了,那么这个时候就需要去数据库查询判断才知道,这个时候我们可以对某个字段,进行自定义的验证。

需要针对某个字段进行特殊验证时,可以在Form表单类中定义一个clean_[字段名]的方法,就会自动执行这个方法进行验证了,如果不符合要求,直接抛出异常即可,符合要求则返回对应的值。如果想要针对多个字段之间的验证,可以重写clean()方法,当所有字段的验证都通过后就会执行这个方法。

# form.py

from django import forms
from .models import Account

class RegisterForm(forms.Form):
    # label 属性是form表单中特有的属性,代表这个字段的描述,这个属性类似于模型类中的verbose_name属性
    username = forms.CharField(label=u'用户名', min_length=3, max_length=18,
                               error_messages={'required': u'用户名不能为空',
                                               'min_length': u'用户名不少于3位',
                                               'max_length': u'用户名不超过18位'
                                               })

    password = forms.CharField(label=u'密码', required=True, min_length=6, max_length=16,
                               error_messages={'required': u'密码不能为空', 'min_length': u'密码不少于6位',
                                               'max_length': u'密码不超过16位'})
    confirm_pwd = forms.CharField(label=u'确认密码', min_length=6, max_length=16,
                                  error_messages={'required': u'确认密码不能为空', 'min_length': u'密码不少于6位',
                                               'max_length': u'密码不超过16位'})
    # 可以不填
    email = forms.EmailField(label=u'邮箱', required=False)  # required 请求能否为空,True不能为空,默认为True

    # 表单自定义错误消息:重写方法clean_field(field是一个属性名),可以自定义针对某一个field的验证机制,一个属性一个对应方法
    # clean() 或者 clean_xxx() 会在执行 form.is_valid() 的过程中被调用
    def clean_username(self):
        # 当字段的基本验证通过后,会将数据存储在cleaned_data中
        username = self.cleaned_data['username']
        if Account.objects.filter(username=username).exists():
            # 如果这里判断有多个错误存在,则使用add_error方法。
            self.add_error('username', '该用户名已使用,请重新选择!')
            # 如果只是单个错误,使用ValidationError,否则这里raise抛出去了,后面就不能执行了
            #raise forms.ValidationError('用户已存在')

        # 敏感词汇
        if username.find('mmp') >= 0:
            self.add_error('username', '存在敏感字符')
            
  # 需要返回处理后的值
        return username

    def clean_confirm_pwd(self):  #不能用clean_password,因为加载这个时候,confirm_pwd还没加载出来,是没有值的。
        pwd = self.cleaned_data['password']
        confirm_pwd = self.cleaned_data['confirm_pwd']

        if pwd != confirm_pwd:
            raise forms.ValidationError('两次密码不一致', code='')

        return pwd
    
    def clean(self):
        # 执行这个方法时表示所有字段都已验证成功,当然,需要先调用父类的clean()方法
        cleaned_data = super().clean()
        # 当字段的基本验证通过后,会将数据存储在cleaned_data中
        # 获取需要验证的字段
        
        ...
        # 需要返回处理后的值
        return cleaned_data      

    def get_error_dict(self):
        # 提取错误信息
        if hasattr(self, 'errors'): #hasattr() 函数用于判断对象是否包含对应的属性。
            errors = self.errors.get_json_data()
            print(type(errors)) # <class 'dict'>
            print(errors)
            # {'username': [{'message': '用户名不能为空', 'code': 'required'}],
            # 'password': [{'message': '密码不能为空', 'code': 'required'}],
            # 'confirm_pwd': [{'message': '确认密码不能为空', 'code': 'required'}]}

            err_msg_dict = {}
            for key, message in errors.items():
                print(key, message) # username [{'message': '用户名不能为空', 'code': 'required'}]
                msg_list = []
                for msg in message:
                    print(msg) # {'message': '用户名不能为空', 'code': 'required'}
                    msg_list.append(msg['message'])

                err_msg_dict[key] = msg_list

            print(err_msg_dict)  # {'username': ['用户名不能为空'], 'password': ['密码不能为空'], 'confirm_pwd': ['确认密码不能为空']}

            return err_msg_dict

        return None


    def get_error_str(self):
        # 提取错误信息
        if hasattr(self, 'errors'): #hasattr() 函数用于判断对象是否包含对应的属性。
            errors = self.errors.get_json_data().values()  # 获取字典值部分
            print(errors)
            #dict_values([[{'message': '用户名不能为空', 'code': 'required'}],
            # [{'message': '密码不能为空', 'code': 'required'}], [{'message': '确认密码不能为空', 'code': 'required'}]])

            err_msg_list = []
            for itme in errors:
                print(itme) # [{'message': '用户名不能为空', 'code': 'required'}]

                err_msg_list.append(itme[0].get('message'))

            print(err_msg_list) # ['用户名不能为空', '密码不能为空', '确认密码不能为空']

            err_msg_str = ';'.join(err_msg_list)  # 将错误信息通过;拼接成字符串

            print(err_msg_str)  # 用户名不能为空;密码不能为空;确认密码不能为空

            return err_msg_str

        return None

视图views.py

class RegisterView(View):
    def get(self, request):
        # 如果需要使用django表单渲染html页面
        # 实例化该表单模型,传递给前端
        return render(request, 'register_form.html')

    def post(self, request):
        print(request.POST) #<QueryDict: {'username': ['admin'], 'password': ['6666'], 'confirm_pwd': ['222'], 'email': ['']}>

        register_form = RegisterForm(request.POST)

        if register_form.is_valid():
            # 使用cleaned_data 必须执行完is_valid 且返回为True才能获取数据,保存用户提交上来的数据
            print(register_form.cleaned_data)
            username = register_form.cleaned_data['username']
            password = register_form.cleaned_data['password']

            # 利用字典解包方式
            params = {'username': username, 'password': password}
            Account.objects.create(**params)

            return HttpResponse('注册成功')

        else:
            print(register_form.errors)
            # <ul class="errorlist"><li>username<ul class="errorlist"><li>用户名不能为空</li></ul></li></ul>
            print(register_form.errors.get_json_data())
            # {'username': [{'message': '用户名不能为空', 'code': 'required'}]}

            print(register_form.errors.get('username'))  # <ul class="errorlist"><li>用户名不能为空</li></ul>

            print(register_form.errors.get('username').get_json_data()) # [{'message': '用户名不能为空', 'code': 'required'}]

            err_msg_dict = register_form.get_error_dict()
            # {'username': ['用户名不能为空'], 'password': ['密码不能为空'], 'confirm_pwd': ['确认密码不能为空']}

            return render(request, 'register_form.html', err_msg_dict)

模板register_form.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<form action="{% url "user:register" %}" method="post">
    用户名:<input type="text" name="username"><span>{{ username.0 }}</span>
    密  码:<input type="password" name="password"><span>{{ password.0 }}</span>
    确认密码:<input type="password" name="confirm_pwd"><span>{{ confirm_pwd.0 }}</span>
    邮  箱:<input type="email" name="email">

    <input type="submit" vlaue='注册'>
</form>
</body>
</html>

自定义错误信息,可以通过在定义表单类是设置相关属性,校验不通过则会报默认错误。我们可以通过设置error_messages字典来设置对应message。也可以自定义错误信息。

不管那种必须要执行完is_valid函数,否则执行相关errors是不会包含错误,form类的运行顺序是initcleanvalidtesave,如果遇到类似错误,比如说不能为空,最大最小长度时,在error_messages写了错误信息,也自定义了表单错误信息,则required=True时调用error_message,否则自定义的。

ModelForm

通过上面我们发现,Form表单中的字段与模型中的字段基本是一模一样的,表单中需要验证的数据,也就是模型中需要保存的,我们可以将模型中的字段与表单中的字段进行绑定,例如上面RegisterForm改成继承forms.ModelForm

# models.py
class Account(models.Model):
    username = models.CharField(max_length=16)
    password = models.CharField(max_length=20)

    class Meta:
        db_table = 'tb_account'

    def __str__(self):
        return self.username
    
# forms.py
class RegisterForm(forms.ModelForm):
    """
      1.补充 Model 没有的 Field 到表单,例如这里的confirm_pwd
      2.覆盖 Model 中的 Field 定义
    """
    confirm_pwd = forms.CharField(label=u'确认密码', min_length=6, max_length=16,
                                  error_messages={'required': u'确认密码不能为空',                                   'min_length': u'密码不少于6位', 'max_length': u'密码不超过16位'})

    class Meta:
        model = Account  # 对应model中的类
        fields = '__all__'  # 说明要关联类中的哪些字段,默认__all__关联所有字段,如果不需要全部
        # exclude = ['avatar']  # 排除字段
        error_messages = {  # 自定义错误信息
            'username': {
                'max_length': '用户名长度不超过16位',
                'required': "用户名不能为空",
            },
            'password': {
                'max_length': '用户名长度不超过16位',
                'required': "用户名不能为空",
            }
        }

    def clean_confirm_pwd(self): 
        pwd = self.cleaned_data['password']
        confirm_pwd = self.cleaned_data['confirm_pwd']

        if pwd != confirm_pwd:
            raise forms.ValidationError('两次密码不一致', code='')

        return pwd

    
# views.py
from .forms import RegisterForm

from hashlib import md5

class RegisterView(View):
    def get(self, request):
        # 如果需要使用django表单渲染html页面
        # 实例化该表单模型,传递给前端
        return render(request, 'register_form.html')

    def post(self, request):
        print(request.POST)

        register_form = RegisterForm(request.POST)

        if register_form.is_valid():
            # 使用cleaned_data 必须执行完is_valid 且返回为True才能获取数据,保存用户提交上来的数据
            print(register_form.cleaned_data)
            # {'username': 'admin666', 'password': '1q2w3e4r', 'confirm_pwd': '1q2w3e4r'}

            register_form.save()  # 这里save即使多了个confirm_pwd没关系,是按照model字段来的。

            return HttpResponse('注册成功')

        else:
            print(register_form.errors.get_json_data())
            #{'username': [{'message': '用户名不能为空', 'code': 'required'}],
            # 'password': [{'message': '用户名不能为空', 'code': 'required'}],
            # 'confirm_pwd': [{'message': '确认密码不能为空', 'code': 'required'}]}

            print(register_form.errors.get('username').get_json_data())  # [{'message': '用户名不能为空', 'code': 'required'}]

            return render(request, 'register_form.html')

RegisterForm是继承自forms.ModelForm,然后在表单中定义了一个Meta类,在Meta类中指定了model=Account,以及fields="__all__",这样就可以将Account模型中所有的字段都复制过来,进行验证。如果只想针对其中几个字段进行验证,那么可以给fields指定一个列表,将需要的字段写进去。

如果要验证的字段比较多,只是除了少数几个字段不需要验证,那么可以使用exclude来代替fields

与普通的Form表单验证类型类似,ModelForm表单的验证在调用is_valid()或访问errors属性时隐式调用。

验证时,可以在ORM模型的字段定义中指定validators参数,添加额外的验证器即可。

自定义错误,因为字段都不是在表单中定义的,而是在模型中定义的,因此一些错误消息无法在字段中定义。那么这时候可以在Meta类中,定义error_messages,然后把相应的错误消息写到里面去。

自定义校验规则与Form表单一样。

save保存,ModelForm还有save方法,可以在验证完成后直接调用save方法,就可以将这个数据保存到数据库中了。这个方法必须要在clean没有问题后才能使用,如果在clean之前使用,会抛出异常。另外,我们在调用save方法的时候,如果传入一个commit=False,那么只会生成这个模型的对象,而不会把这个对象真正的插入到数据库中。比如表单上验证的字段没有包含模型中所有的字段,这时候就可以先创建对象,再根据填充其他字段,把所有字段的值都补充完成后,再保存到数据库中,例如:

form = MyForm(request.POST)
if form.is_valid():
    article = form.save(commit=False)
    article.category = 'Python'
    article.save()
    return HttpResponse('succes')
else:
    print(form.get_errors())
    return HttpResponse('fail')

并且即使form的数据比model模型字段多也没关系。

ModelForm的子类可以接受现有的模型实例作为关键字参数instance;如果提供此功能,则save()将更新该实例。如果没有提供,save() 将创建模型的一个新实例。

# 修改表数据是,记得把instance信息也传进去,不然是新建数据,而不是对某行数据进行修改。
article_form = ArticleAddForm(dict_data, instance=article)  # 指定给谁做修改
if article_form.is_valid():
    article_form.save()
    return json_status.result(message='文章更新成功')


#Django#

文章来源于公众号【Python野路子】

eb.py的form模块能够帮助你生成HTML表单;获取用户的输入,并在处理或添加到数据库之前对其进行内容的验证。

如图所示:

上图是一个post表单

提交以后会给code.py中的hello post处理

上图中

i=web.input()
接收表单中的值,并赋值给i
?
最后返回给模板hello.html中的name属性
return render.hello(name=i.name)
?
运行效果如下:


此时我们再文本框里面任意输入一个值,提交以后默认值就是改变(这里的默认值就是我们的name属性)