Django表单(二)

什么是django表单

  • django中的表单不是html中的那个表单,这里是指django有一个组件名叫表单
  • 它可以通过配置去验证数据的合法性
  • 同样也可以通过配置生成HTML代码.

使用表单:

  • 创建一个forms.py的文件,放在指定的app当中,然后在里面写表单。
  • 如果新建了app应用,同样要记得在settings中添加至INSTALLED_APPS。
  • 表单是通过类实现的,继承自`forms.Form,然后在里面定义要验证的字段。
 from django import forms 
  • 在表单中,创建字段跟模型是一模一样的,但是没有null=True(是否接受空值NULL,默认值False)或者blank=True(是否接受空白内容,默认为False)等这几种参数了,有的参数是required=True/False(请求能否为空,True不能为空,默认为True)
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)
  • 表单生成HTML元素:
 # views.py
class Register(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.html
<body>
{% if username %}
    提交的post数据:
    {{ username }}
    {{ password }}
{% else %}
<form action = "{% url "register_form" %}" method="post">
    {% csrf_token %}
    {{ form }}  <!--会自动识别表单属性的 -->
    <input type="submit" vlaue='注册'>
</form>
{% endif %}
</body>
image.png
image.png

ps: 使用django的Form类生成的表单,不包含form和submit按钮两个标签,需要手动添加。
ps : 这个模块用得比较少,这个功能确实很鸡肋,把前端该做的事情放到后台来实现,增加了代码的耦合性也增加了服务器的压力。

  • 不使用表单生成元素
  1. 写好对应的html元素,HTML表单元素的name必须和django中的表单的name保持一致,否则匹配不到。
  2. 后台post方法调用
 form = RegistForm(request.POST)
  • 使用is_valid()方法可以验证用户提交的数据是否合法, 这个方法会返回一个bool,合法返回True, 否则返回False
 form.is_valid()
  • is_bound属性:用来表示form是否绑定了数据,如果绑定了,则返回True,否则返回False。即是否为None
  • cleaned_data:这个是在is_valid()返回True的时候,保存用户提交上来的数据。
#models.py
from django.db import models
# Create your models here.
class User(models.Model):
    username = models.CharField(u'用户名',max_length= 20)
    password = models.CharField(max_length = 40,verbose_name = u'密码')
    class Meta:
        db_table = 'user'
        managed = True

#forms.py
# -*- coding: utf-8 -*-
from django import forms
'''
forms.py的作用
它是专门编写你的forms配置的模型
forms.py本身命名没有要求,你可以为任意名称, 但是我们一般约定它叫forms,代表这个文件是专门处理该APP下处理表单组件的
'''
class RegisterForm(forms.Form):
    # label 属性是form表单中特有的属性,代表这个字段的描述,这个字典类似于verbose_name
    username = forms.CharField(label = u'用户名',max_length = 20,min_length=3)
    # 存储到数据库的密码,是一个加密后的字符串,但是这里是通过前端传输过来的,并没进行加密
    password1 = forms.CharField(label= u'密码',max_length=20,min_length=8)
    password2 = forms.CharField(label= u'密码',max_length=20,min_length=8)

#views.py
class Register(View): #注册功能
    def get(self,request):
        return render(request,'register_form.html')
    def post(self,request):
        # 如果使用表单
        form = RegisterForm(request.POST)
        # is_bound 是一个属性,它只验证数据字段存在不存在,不验证你的数据是否正确
        print(form.is_bound)
        # is_valid,这是一个方法,它会进行所有的验证,包括是否存在,跟数据是否正确
        if form.is_valid():
            # 使用cleaned_data 必须执行完is_valid 且返回为True才能获取数据,保存用户提交上来的数据
            username = form.cleaned_data['username']
            password1 = form.cleaned_data['password']
            password1 = md5_password(password1)
            User.objects.create(username=username,password=password1)
            return HttpResponse('注册成功')
        else:
            return HttpResponse('数据验证失败')

#register_form.html
<body>
{% if username %}
    提交的post数据:
    {{ username }}
    {{ password }}
{% else %}
<form action = "{% url "register_form" %}" method="post">
    {% csrf_token %}
    用户名:<input type="text" name="username"/><br/>
    密码:<input type="password" name="password1"/><br/>
    确认密码:<input type="password" name="password2"/><br/>
    <input type="submit" vlaue='注册'>
</form>
{% endif %}
</body>
  • 上传文件:
    在相应的模型里面定义FileField或者是ImageField类型的字段,并且设置好upload_to参数来指定上传的路径。
headshot = models.ImageField(u'头像', upload_to="upload/%Y/%m/%d")
  • 如果是使用ImageField,会需要安装一个依赖Pillow, Pillow是专门做图片处理的一个python包
 pip install Pillow
  • 需要在settings.py文件中指定媒体路径MEDIA_ROOT
 MEDIA_ROOT = "media/"

ps: 这里是媒体文件,它也是一种静态文件,在django中,这一块的内容是要跟其它静态文件分开处理的

  • django 中的文件存储分为两种:
    静态文件存储,一般是我们的JS、css、系统的图片文件等。
    媒体文件存储,一般是用户上传的图片、文件数据,或大的文件或视频等等。
  • 文件上传需要在HTML代码中的form表单中添加enctype="multipart/form-data"以及在views当中,使用request.FILES来接收文件.
 form = LoginUserForm(request.POST, request.FILES)
  • 文件只有在保存时才会处理,数据库保存的是文件的路径,不会保存文件本身。
user = LoginUser(**form.clean())
user.save()
  • 表单错误消息:
  1. 表单验证没有通过后,表单会产生一个errors属性,这个属性包括所有的验证错误信息。
  2. 通过form.errors即可访问。
  3. 通过form['属性名'].erros访问对应的错误
  4. 通过form.errors.get_json_data()form['属性名'].erros.get_json_data()可以将错误消息转换成json数据。
  5. 自定义错误消息:在Field中添加一个error_messagesdict类型的参数,然后根据属性名设置对应的message,例如以下代码:
 password = forms.CharField(max_length=10,error_messages={'required':u'密码不能少'})
  1. 必须要执行完is_valid函数,否则errors是不会包含错误
  • 表单自定义错误消息:
    系统自带的表单数据错误信息有时无法满足我们的需求,比如用户是否已经存在了,可以在表单类中自定义错误信息,在表单中,重写方法clean_field(field是一个属性名),可以自定义针对某一个field的验证机制,如果出现错误
    1)如果某个field出现验证错误,通过add_error方法给指定的field添加错误消息。
    2)直接抛出一个raise ValidationError(message, code="属性名")就可以了。
    3)重写clean方法会在先完成django默认的验证后,再重新执行clean方法的验证。
    4)如果验证完成成功了,则直接返回当前值。
 def clean_password(self):
        password = self.cleaned_data.get('password',None)
        if len(password) < 6:
            raise forms.ValidationError(u'password at least 6 length',code='min_length')
        return password

注意:
1)如果没有设置表单属性,则不会出现相应错误:

#如果不设置max_length = 20,min_length=3等属性,则不会出现错误;
password1 = forms.CharField(label= u'密码')

2)设置相关属性:

password2 = forms.CharField(label= u'密码',max_length=20,min_length=8)
image.png

3)通过error_messages字典来设置对应message

username = forms.CharField(label = u'用户名',required= True,max_length = 20,min_length=3,error_messages={'required':u'用户名不能为空','max_length':u'最大长度不能超过20个字符','min_length':u'至少3个字符'})
image.png

4)自定义错误

    def clean_username(self):
        username = self.cleaned_data['username']
        user = User.objects.filter(username=username)
        if user:
            # 如果这里判断有多个错误存在,则使用add_error方法
            self.add_error('username',u'用户名已存在')
            # 如果只是单个错误,使用raise ValidationError,否则这里raise抛出去了,后面就不能执行了
            # raise forms.ValidationError(u'用户名已存在')
         #敏感字符
        if username.find('mmp') >= 0:
             self.add_error('username',u'存在敏感字符')
        return username #刚才这里没有返回值  你对username处理完了就没有了没有了  后面的时候views.py、没有数据传进去

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

完整参考代码:
app应用urls.py

url(r'^register/$', views.Register.as_view(), name="register_form")

models.py:

# -*- coding: utf-8 -*-
from __future__ import unicode_literals

from django.db import models

# Create your models here.

class User(models.Model):
    username = models.CharField(u'用户名',max_length= 10)
    password = models.CharField(max_length = 40,verbose_name = u'密码')
    #upload_to的设置会自动在settings配置的media文件夹下创建这样一个文件夹
    #% Y / % m / % d‘被strftime()所格式化;
    # ’ % Y’ 将会被格式化为一个四位数的年份, ‘ % m’ 被格式化为一个两位数的月份’ % d’是两位数日份。
    headshot = models.ImageField(u'头像',upload_to='upload/%Y/%m/%d',null= True)

    class Meta:
        db_table = 'user'
        managed = True

表单forms.py:

# -*- coding: utf-8 -*-

from django import forms

from .models import User

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

class RegisterForm(forms.Form):
    # label 属性是form表单中特有的属性,代表这个字段的描述,这个字典类似于verbose_name
    #根据属性名设置对应的message
    username = forms.CharField(label = u'用户名',required= True,min_length = 3,max_length = 10,error_messages={'required':u'用户名不能为空','max_length':u'最大长度不能超过20个字符','min_length':u'至少3个字符'})
    # 存储到数据库的密码,是一个加密后的字符串,但是这里是通过前端传输过来的,并没进行加密
    password1 = forms.CharField(label= u'密码',min_length = 8,max_length = 16)
    #如果没有进行
    password2 = forms.CharField(label= u'密码',min_length = 8,max_length = 16)
    #如果设置required= False则该字段可不填
    headshot = forms.ImageField(label=u'头像',required= False)

    #表单自定义错误消息:重写方法clean_field(field是一个属性名),可以自定义针对某一个field的验证机制,一个属性一个对应方法
    def clean_username(self):
        username = self.cleaned_data['username']
        user = User.objects.filter(username=username)

        if user:
            # 如果这里判断有多个错误存在,则使用add_error方法
            self.add_error('username',u'用户名已存在')

            # 如果只是单个错误,使用raise ValidationError,否则这里raise抛出去了,后面就不能执行了
            # raise forms.ValidationError(u'用户名已存在')
         #敏感字符
        if username.find('mmp') >= 0:
             self.add_error('username',u'存在敏感字符')
        return username #刚才这里没有返回值  你对username处理完了就没有了没有了  后面的时候views.py、没有数据传进去

视图views.py:

class Register(View): #注册功能
    def get(self,request):

        return render(request,'register_form.html')

    def post(self,request):
        #因为我们的文件是通过request.FILES传递的,所以如果需要上传文件,则需要把request.FILES传递进去
        # form = RegisterForm(request.POST)
        form = RegisterForm(request.POST,request.FILES)
        # is_bound 是一个属性,它只验证数据字段存在不存在,不验证你的数据是否正确
        print(form.is_bound)
        # is_valid,这是一个方法,它会进行所有的验证,包括是否存在,跟数据是否正确
        if form.is_valid():
            # 使用cleaned_data 必须执行完is_valid 且返回为True才能获取数据,保存用户提交上来的数据
            username = form.cleaned_data['username']
            password1 = form.cleaned_data['password1']
            password1 = md5_password(password1)
            # User.objects.create(username=username,password=password1,headshot=form.cleaned_data['headshot'])
            #利用字典解包方式
            params = {'username':username,'password':password1,'headshot' : form.cleaned_data['headshot']}
            User.objects.create(**params)
            #如果使用实例化对象方式,一定要记住save保存到数据库中去;
            # user = User(username = username,password=password1,headshot=form.cleaned_data['headshot'])
            # user.save()

            return HttpResponse('注册成功')
        else:
            # print(form.errors)
            # print(form['username'].errors)
            # print(form['username'].errors.as_json())
            # print(form.errors.as_json())

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

html页面代码:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>register注册表单页面</title>
    <style>
        * {
            margin: 0;
            padding: 0;
        }
    </style>
</head>
<body>
{% if username %}
    提交的post数据:
    {{ username }}
    {{ password }}
{% else %}
<p>用户注册</p>
{{ form.errors }}
<!--如果表单需要上传文件,需要设置enctype="multipart/form-data" 否则文件无法找到 -->
{#{% url 'register_form' %} 通过name找到urls配置的对应视图#}
<form action = "{% url 'register_form' %}" method="post" enctype="multipart/form-data">
    {% csrf_token %}
    用户名:<input type="text" name="username"/><br/>
    密码:<input type="password" name="password1"/><br/>
    确认密码:<input type="password" name="password2"/><br/>
    上传头像:<input type="file" name = "headshot" /><br/>
    <input type="submit" value="注册"/>
</form>
{% endif %}
</body>
</html>
学以致用

需求背景:
使用表单写一个用户注册
1)注册需要有头像、用户名、密码(密码存储是加密的)、性别、出生年月
2)需要做数据验证,字符长度, 是否允许为空等判断
3)使用自定义异常验证用户名是否存在
4)使用自定义异常验证两次密码是否相同

  • 分析
  1. 需要数据库创建一个用户表,包含用户名,密码,性别,出生年月,头像字段。--在models.py中需要定义一个用户模型类;
  2. 关于数据验证,对于字符长度,是否允许为空,可以用表单自带的错误消息form.errors前端访问,或者用erros_messages在属性里面设置;
  3. 使用自定义异常验证则用在表单中,重写方法clean_field(field是一个属性名),理清form执行顺序。

模型models.py:

# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models
# Create your models here.
class User(models.Model):
    username = models.CharField(u'用户名',max_length= 10)
    password = models.CharField(max_length = 40,verbose_name = u'密码') #存储数据库是用的md5加密后的所以设置长点;
    sex = models.CharField(u'性别',max_length = 4,default='boy',null=True)
    birthday = models.DateField(u'出生日期',auto_now_add=True)
    headshot = models.ImageField(u'头像',upload_to='upload/%Y/%m/%d',null= True)

    class Meta:
        db_table = 'user'
        managed = True

表单forms.py:

# -*- coding: utf-8 -*-
from django import forms
from .models import User

class RegisterForm(forms.Form):
    username = forms.CharField(label = u'用户名',required= True,min_length = 3,max_length = 12,error_messages={'required':u'用户名不能为空','max_length':u'最大长度不能超过12个字符','min_length':u'至少3个字符'})
    password = forms.CharField(label= u'密码',min_length = 8,max_length = 16,error_messages={'required':u'密码不能为空','max_length':u'最大长度不能超过16个字符','min_length':u'至少8个字符'})
    password2 = forms.CharField(label= u'密码',min_length = 8,max_length = 16,error_messages={'required':u'密码不能为空','max_length':u'最大长度不能超过16个字符','min_length':u'至少8个字符'})
    sex = forms.CharField(label=u'性别',max_length = 4,required= False)
    birthday = forms.DateField(label=u'出生日期')
    headshot = forms.ImageField(required= False)

    def clean_username(self):
        username = self.cleaned_data['username']
        user = User.objects.filter(username=username)
        if user:
            # self.add_error('username',u'用户名已存在')
            raise forms.ValidationError(u'用户名已存在')
        return username

    def clean_password2(self):  #不能用clean_password,因为加载这个时候,password2还没加载出来,是没有值的。
        password = self.cleaned_data['password']
        password2 = self.cleaned_data['password2']
        if password != password2:
            raise forms.ValidationError(u'两次密码不一致')
        return password

页面代码:

<body>
<h2>用户注册</h2>
{{ form.errors }}<!--form.errors访问错误信息-->
<form action = "{% url 'register_form' %}" method="post" enctype="multipart/form-data">
    {% csrf_token %}
    用户名:<input type="text" name="username"/><br/>
    密码:<input type="password" name="password"/><br/>
    确认密码:<input type="password" name="password2"/><br/>
    性别:
        <input type="radio" name="sex">男
        <input type="radio" name="sex">女
    <br/>
    上传头像:<input type="file" name = "headshot" /><br/>
    出生日期:<input type="text" name="birthday"/><br/>
    <input type="submit" value="注册"/>
</form>
</body>

应用urls.py:

# -*- coding:utf-8 -*-
from django.conf.urls import url,include
from TestApp import views
import views
urlpatterns = [
    url(r'^register/$', views.Register.as_view(), name="register_form")
]

视图views.py

# -*- coding: utf-8 -*-
from __future__ import unicode_literals

from django.shortcuts import render,redirect,reverse
from django.views import View
from django.http import HttpResponse
import hashlib
from .models import User
import uuid
from .forms import *

# Create your views here.
def md5_password(password): #使用md5加密
    return hashlib.md5(password).hexdigest()
class Register(View): #注册功能
    def get(self,request):
        return render(request,'register_form.html')
    def post(self,request):
        form = RegisterForm(request.POST,request.FILES)
        # is_bound 是一个属性,它只验证数据字段存在不存在,不验证你的数据是否正确
        print(form.is_bound)
        # is_valid,这是一个方法,它会进行所有的验证,包括是否存在,跟数据是否正确
        if form.is_valid():
            # 使用cleaned_data 必须执行完is_valid 且返回为True才能获取数据,保存用户提交上来的数据
            username = form.cleaned_data['username']
            password = form.cleaned_data['password']
            password = md5_password(password)
            birthday = form.cleaned_data['birthday']
            print(len(password),password,birthday)
            #利用字典解包方式
            params = {'username':username,'password':password,'birthday':birthday,'headshot' : form.cleaned_data['headshot']}
            User.objects.create(**params)
            #如果使用实例化对象方式,一定要记住save保存到数据库中去;
            # user = User(username = username,password=password1,headshot=form.cleaned_data['headshot'])
            # user.save()
            return HttpResponse('注册成功')
        else:
            return render(request,'register_form.html',locals())

页面效果:
image.png

image.png

image.png

image.png

image.png

重点

注意验证流程与逻辑。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,657评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,662评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,143评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,732评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,837评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,036评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,126评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,868评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,315评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,641评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,773评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,470评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,126评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,859评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,095评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,584评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,676评论 2 351

推荐阅读更多精彩内容