django搭建简单邮件发送系统——细说Form Validation(-)
对于Web而言,提交一个Form的操作是经常的。 Django自己有一个form库, django.forms, 这个库会对web显示到数据验证都提供支持,对于很多“错误”也是提供自动检验而不需要程序员额外花费代码。我们以一个“联系我们”的邮件系统为例来简单介绍一下django.forms库。
本操作环境:(2017.6.18日---今天剁手买了好多“6.18”😂)
OS: win7 (注意下文中的斜杠,我们知道在windows系统里是用'\'来表示路径的,但在django里要用unix 斜杠,我为了方便在下文中没有作区分,但读者要注意。)
python: 3.6.1
django: 1.11.2
创建项目与应用
django-admin startproject mysite
cd mysite
python manage.py startapp contactUs
将contactUs app增加到mysite/mysite/settings.py文件中,进行登记,这样django就知道有一个新的app产生了:
# Application definition INSTALLED_APPS = ( 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'contactUs', )
创建Form类实例
在Django的一般惯例中,会把Form类创建在一个单独的文件中。在mysite/views.py同一个目录中创建forms.py文件,并写入以下代码:
#mysite/mysite/forms.py
from django import forms class ContactUs(forms.Form): subject = forms.CharField(max_length = 100) email = forms.EmailField(required = False,label = 'Your e-mail address') message = forms.CharField(widget = forms.Textarea)
def clean_message(self):
message = self.cleaned_data['message']
num_words = len(message.split())
if num_words < 4:
raise forms.ValidationError("Not enough words!")
return message
可以看出来,form类的语法和Django Model的语法类似,form 里的每个变量都要定义成一种"field"类型——CharField, EmailField都是forms类的属性。在这个定义中,我们要求subject域和message域是必填的,而email 域是可选填的,默认情况下每个域的requried = Ture, 所以我们要显式地对email域指定required = False。
在从django的Forms类翻译成最终的html文件后,会注意到CharFiled会被显示成html里的<input type = 'text'>,message应该被显示成text,可以通过设置该域的widget属性来将CharField类型最终显示成Text。在forms 框架中,你可以理解成在显示层面它把每个域都定义成一个“widget”,每个域都在默认的widget,你可以显性的定义自己的widget从而覆盖掉默认的widget。
django forms会根据每个域的定义值 来对其进行相应的验证(Validation)。比如message域定义成CharField, django forms在对该域进行完默认的CharField验证后,它会自动去寻找以clean_开头,并以域名结尾的函数,如果该函数存在(比如此例中的clean_message),它就会执行该函数中对数据的Validation。在这个例子中,我们要求message域的字符个数不能少于4个,否则会引发ValidationError消息。至于去验证某个域是否有输入值,django forms会默认验证,所以不需要我们另外写代码。
默认设置下,django 自动生成的form html页面时,它会把定义时的域名第一个字母大写,并用下划线连接第二个单词,这样的形式显示在html页面上。所以email 域就会以Email的形式显示在页面上,我们可以显性的定义label,这样就可以改变域名显示规则了(在django model里,是通过显性定义verbose_name来实现这个功能)。
创建Views
按照我的理解,views.py里就是定义数据是按照怎样的逻辑来呈现给用户。我们最终的目的就是要将数据显示给用户看,以供他们操作。打开mysite/mysite/views.py文件,写入:
#mysite/mysite/views.py from django.http import Http404, HttpResponse, HttpResponseRedirect from django.shortcuts improt render from django.core.mail import send_mail, get_connection from .forms import ContactUs import datetime def contact(request): if request.method == 'POST': form = ContactUs(request.POST) if form.is_valid(): datas = form.cleaned_data con = get_connection('django.core.mail.backends.console.EmailBackend') send_mail(datas['subject'],datas['message'],datas.get('email', noreply@exmaple.com'), ['siteowner@example.com'], connection = con) return HttpResponseRedirct('/contact/thanks/') else: form = ContactUs(initial = {'subject':'I love your site'}) return render(request, 'contact_form.html', {'form':form})
在这部分我们有很多需要解释,因为加入了许多新内容。
- 首先我们来讲一下HttpRequest.POST对象。这个对象返回的是一个类似于“字典”的对象,它里面包括的是form类的提交数据。如果想要得到除form类之外的数据,需要引用HttpRequest.body这个属性。HttpRequest.POST不会返回提交的文件类型数据,那个通过HttpRequest.Files对象来得到。如果web网页提交的Form没有任何数据,那么HttpRequest.POST就返回一个空的类“字典”对象,所以不能根据HttpRequest.POST对象来判断网页是否使用了POST方法,而应该是根据HttpRequest.method == 'POST'来判断。
- 再来讲讲Form类。Form类实例化后,这个实例就变成了一个“bound”的对象。对于一个“bound”对象,它有一个方法is_valid(),用来检验这个实例的数据是否有效。如果这个'bound'对象的数据是有效的,那么,这个对象还有一个“cleaned_data”属性,这个属性返回的结果是包含网页提交数据的字典形式。Django forms库不仅仅是对所写入的数据的有效性进行验证,而且还会把写入的有效数据转化成python里的数据类型。比如,它会把forms库里的CharField对象转化成python里的string对象,把IntegerField对象转化成python里的integer对象,把DateField对象转化成python里的datetime.date对象。在这段代码中没有对也可能的错误信息进行处理(比如用户没有填写subject或messages,直接提交数据),因为Forms类会自动对这些错误进行相应的处理,它会把所有的错误信息生成一个errors list。
- “django.core.mail.backends.console.EmailBackend”这个EmailBackend很有用,因为它不需要我们搭建Email 服务器,就可以发送邮件。它会把邮件发送到系统终端系统(你可以在提交完form表单后检查系统的terminal窗口,可以看到发送的相应邮件)。
- 在发送完邮件后,web页面会转向/contact/thanks页面,这是通过HttpResponseRedirect()来实现的。
- 为了使页面更友好,当打开contact页面时,我们可以给subject设置一个初始值,通过设置Form实例的initial参数可以实现。
创建templates
我们需要创建template来显示刚刚创建的form类。在mystie/contactUs里创建templates文件夹,这个文件夹是存放contactUs的模板文件的,然后新建contact_us.html文件,并输入:
#mysite/contactUs/templates/contact_us.html <html> <head> <title>Contact us</title>
</head> <body> <h1>Contact us</h1> {% if form.errors %} <p style="color: red;"> Please correct the error{{ form.errors|pluralize }} below. </p> {% endif %} <form action="" method="post" novalidate> <table> {{ form.as_table }} </table> {% csrf_token %} <input type="submit" value="Submit"> </form> </body> </html>
- 首先要讲的是form.errors。在上一节中我们讲过,django的forms库会把web页面上的错误自动生成一个errors list,用户可以通过form实例来引用这个list。
- {{form.errors | pluralize }} 。pluralize是一个template filter, 它会根据前面列表元素的个数来决定是否输出“s”字符,如果列表中元素个数多于1,它就会输出字符“s”
- form表单的novalidate属性。如果浏览器用的是Html5技术(特别是Chrome),浏览器会自动对form表单提交的数据进行验证,但是我们希望是利用django的forms库进行数据的验证,所以我们不希望浏览器对数据进行验证,novalidate 这个属性就是这个用途。
- {% csrf_token %} template tag。提交的POST数据,有Cross Site Request Forgeries的风险,django会自动处理这个风险。对于POST提交的数据的网页,它会返回HTTP 403Forbidden 页面。对于提交给本地的POST数据,加上{% csrf_token %}这个tag,可以不进入HTTP 403 Forbidden页面。
设置URL
在urls.py文件里,增加条目,完成从url到views的转换
from contactUs.views import contact urlpatterns = [ # ... url(r'^contact/$', contact), ]
启动服务
python manage.py runserver 80
注意:在发送完邮件后,页面会转向contact/thanks页面,这个页面我们在上述代码中没有实现,需要读者自己去实现