django 搭建上传文件系统——细说Form Validation(二)
我学习django的主要途径是http://djangobook.com, 作者的书好像也出版了。作者的思路很清爽,讲解浅显易懂,该深入的地方深入,我很喜欢,比django官方文档感觉好多了,官方文档讲解的太晦涩。这些随笔是结合一些例子来细说django的学习要点与本人的学习心得。
有些中文博客也讲解了用django做一些小的项目,但是没有细讲django的原理,即为什么要这样做,我就个人的理解会细致地讲解各个部分的知识点。希望能帮助更多人,也希望与更多人一起进步。
本人配置环境:(2017.6.20)
注意: 在windows中的文件系统是用反斜杠'\'来表示,但是即使是在windows系统下的django,在表示文件路径时依然用unix 风格的斜杠'/'
OS: win7
python:3.6.2
Django: 1.11.2
创建项目与应用
每个项目的前部分:创建项目与应用,把应用添加到settings.py 的'INSTALLED_APP'里,在settings.py里设置默认的数据库,并同步数据库数据的操作都是一样的,就不具体叙述,可以参考我前面的例子。
我把这个上传文件的项目命名为disk, 项目名称为mysite,创建完成后的结构如下:(disk/templates, disk/uploads, disk/forms.py 是我本人创建的,不是系统创建的。请忽略除mysite, disk以外的部分,与此例无关。)
# Application definition INSTALLED_APPS = ( 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'disk', )
创建Form类实例
为了简单起见,在这个文件上传系统中,我们只设计两个变量:上传文件的用户改名,及上传的文件。Django中为用户定义了一个django.forms库,这个库可以方便地进行form类的数据显示及数据验证,省去了很多用户的代码。django.forms库用起来也方便,只需要在HTML文件中需要增加<form> tag时,定义相应的 Form 类就可以。在以下的示例中我们最终的页面上只有一个<form> tag, 所以我们只需要定义一个Form类即可。Django 社区的惯例是把Form类单独放在一个forms.py文件中,我们最好也遵循这个规则。创建disk\forms.py文件,并写入:
# mysite\disk\forms.py from django import forms class UserForm(forms.Form): userName = forms.CharField(max_length = 20) uploadFile = forms.FileField()
可以看出Form类的语法与定义Model时的语法很像,对于每个变量都要定义它的域的属性,这个我在下面的Model章节中会仔细再讲。在UserForm中,我们定义了userName,它是字符属性,还定义了uploadFile,它是文件属性。默认情况下这两个变量都是不能为空的,如果为空,则会在html页面中显示出相应的错误。
创建Models(数据库层)
我们需要把用户名和上传的文件路径放到数据库中, 所以在设计数据库时需要两个字段:userName and uploadFile。在disk\models.py文件中写入:
# disk\models.py from django.db import models # Create your models here. class User(models.Model): userName = models.CharField(max_length = 30) uploadFile = models.FileField(upload_to = 'disk/upload/') def __str__(self): return self.userName
对于models的定义,要讲的东西很多。
- 每个变量都要定义成某种类型的域(field),django会根据不同的域来决定某些事情。比如,域的类型会决定数据库中该变量的数据类型(比如INTERGER, VARCHAR)。对于每个变量,在html显示层,django把它定义成一个单独的widget。最终是以什么widget显示变量也是与该域的类型有关的,比如一个CharField变量最终会以<input type= 'text'>的形式显现。
- 以下是每种域类型变量的定义,来自http://www.djangobook.com/model-definition-reference/。为了保证释义的完整性与准确性,我就不翻译成中文了:
下张表是所有域通用的可选参数:
- FileFiled 说明:此类变量不支持primary_key, unique参数(见上表)。有两个可选参数:upload_to和storage。'upload_to'定义的是一个本地的文件路径,这个路径可以是个相对路径,它会自动加到settings.py文件中 BASE_DIR 目录的后面。一般情况下,即使文件上传成功也不会保存到数据库中,除非显性的调用save()函数。从性能上考虑,不会把上传的文件存储到数据库中,而是把文件的路径存储到数据库中(如果调用了save()函数)。
- 在定义完models后,要把它同步到数据库中,主要用两个命令python manage.py makemigrations, python manage.py migrate
创建Views (逻辑层)
视图views决定的是按照怎样的逻辑收集处理数据,并把用户的数据呈现给用户;可以把它理解成用户数据(数据库)到最终呈现页面(web页面)的中间人。这部分一般就是纯粹的python代码。打开mysite\disk\views.py文件并写入:
# mysite\disk\views.py from django.shortcuts import render from disk.forms import UserForm from disk.models import User def uploads(request): if request.method == 'POST': userform = UserForm(request.POST, request.FILES) if userform.isValid(): user = User() user.userName = userform.cleaned_data['userName'] user.uploadFile = userform.cleaned_data['uploadFile'] user.save() return render(request, 'uploadOK.html') else: userform = UserForm(initial ={'userName': 'sunshore'}) return render(request, 'upload.html', {'userform': userform})
views.py文件就把上文定义的UserForm和User (models文件)引用了过来。
- 对于HttpRequest.POST对象,我们在上一节中讲过,这里不再叙述。
- HttpRequest.FILES对象也是一个“类字典”对象,它的'key'是文件名,与html文件中<input type = "file" name = "" />中的内容相同;'key'所对应的'value' 是上传的文件。注意: 这个对象必须是在HttpRequest method = 'POST' 并且<form> tag中包含 enctype="multipart/form-data"时才会有值,否则会返回一个空的类字典对象。
- django中Form类实例化后,可以通过isValid()函数来检查数据是否有效;如果有效,则该实例就会有cleaned_data属性,这是一个字典对象,包含的是Form类数据。
- 我们首先检查HttpRequest是一个POST 方法,然后实例化Form类,如果实例化成功,就把数据存储到数据库中去。(当然最终存储到数据库中不是文件本身,而是文件路径,上部分提到过)。如果HttpRequest不是POST方法,则把用户名初始化成'sunshore'。
创建Templates(显示层)
我们需要创建templates来定制数据的显示。Django中将数据的处理与显示分开了,数据的处理是在views.py中,数据的显示是在templates里。 默认情况下,mysite/mysite/settings.py文件中的TEMPLATES定义'APP_DIRS' = True, 也就是说django会到各个应用的文件下寻找templates文件夹,使用里面定义的html文件。
我们需要在mysite/disk/templates创建两个html文件:upload.html和uploadOK.html,并写入:
# mysite\disk\templates\upload.html <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> <head>
<style type="text/css">
ul.errorlist{
margin:0;
padding:0;
}
.errorlist li{
background-color: blue;
color: white;
display: block;
font-size: 1.0em;
margin: 0 0 1px;
padding: 0 10px;
}
</style>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> <title>Upload File</title> </head> <body> <h1>User Upload Files</h1> <form action="" method="post" enctype="multipart/form-data" > {{userform.as_p}} {% csrf_token %} <input type="submit" value="ok"/> </form> </body> </html>
# mysite\disk\templates\uploadOK.html <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> <title>Upload OK</title> </head> <body> <p > Uploading Files Successfully </p> <a href= 'upload'>Back to upload file page </a> </body> </html>
- Form类会自动生成页面的错误信息,并以errorlist的形式返回结果。在html文件中我们可以对errorlist进行CSS的定制,使得error信息更为突出。这就是<style> ....</style>那段代码的作用。
- 默认情况下,Form类显示成员的排版格式有form.as_p, form.as_table, form.as_ul这三种形式,不同形式下成员的排列是不同的。比如本示例中userform.as_p会显示如下:
userform.as_table 显示结果:
userform.as_ul 显示结果:
我们可以根据自己的喜好来选择成员的显示形式。当然django也支持自己定制所有成员的显示形式。感兴趣的读者可以参考:http://djangobook.com/tying-forms-views.
设置urlconf 文件
这个是用正则表达式的方式定义了HttpRequest.url和views的对应关系。在mysite\urls.py文件中输入:
# mysite\urls.py from django.conf.urls import patterns, include, url from django.contrib import admin admin.autodiscover() from disk import views as disk_views urlpatterns = patterns('', # Examples: # url(r'^$', 'mysite2.views.home', name='home'), # url(r'^blog/', include('blog.urls')), url(r'^admin/', include(admin.site.urls)), url(r'^upload/$', disk_views.upload), )
启动服务
在mysite文件夹下,终端命令行输入: python manage.py runserver 80
在用户正确地上传了文件后,我们可以在mysite\disk\upload文件夹下看到此文件,这个是在models.py里定义的,读者们可以试一下。