最近在学习Django,打算玩玩网页后台方面的东西,因为一直很好奇但却没怎么接触过。Django对我来说是一个全新的内容,思路想来也是全新的,或许并不能写得很明白,所以大家就凑合着看吧~
本篇笔记(其实我的所有笔记都是),并不会过于详细的讲解。因此如果有大家看不明白的地方,欢迎在我正版博客下留言,有时间的时候我很愿意来这里与大家探讨问题。(当然,不能是简简单单就可以百度到的问题-.-)
我所选用的教材是《The Django Book 2.0》,本节是表单部分,对应书中第七章。
------------------------------------------------------------------------------------------------------------------------------------------------
0、阅读方法
本节笔记,略去很多书中学习过程与讲解,建议在看完原书此节后,作总结复习之用。
站点创建:django-admin.py startproject comeback
1、视图中的HttpResponse
首先给我们的代码加上一个视图,网址是"http://127.0.0.1:8000/",网站内容就是一个Hello World。
显然,其在"/comeback/views.py"中的代码内容是:
from django.http import HttpResponse def hello(request): return HttpResponse("Hello World")
其中,HttpResponse对象,即request变量,是有很多成员(属性和方法)的。通过他们,你可以知道很多信息,例如:正在加载这个页面的用户是谁,他用的是什么浏览器。
这里列举一些属性:
成员 | 说明 | 举例 |
request.path | 除域名以外的请求路径,以斜杠(即 /)开头 | ”/hello/“ |
request.get_host() | 主机名(例如:通常所说的域名) | "127.0.0.1" or "www.example.com" |
request.get_full_path() | 请求路径,可能包含查询字符串 | "/hello/?print=true" |
request.is_secure() | 如果通过https访问,返回True;否则,返回False | True or False |
还有一个属性要重点说明,request.META,这是一个python字典,包含了所有本次HTTP请求的Header信息。这个信息是由用户的浏览器所提交的:
成员 | 说明 | 备注 |
HTTP_REFERER | 进站前链接网页(如果有的话) | 这是REFERRER的笔误-.-||| |
HTTP_USER_AGENT | 用户浏览器的user-agent字符串(如果有的话) | 详见这一篇博文 |
REMOTE_ADDR | 客户端IP | 如果经过代理服务器,那么是逗号分割的多个IP地址 |
应当注意,既然是用户浏览器提交的,这个信息也就不一定靠谱。因此,应当使用下列方式读取其中内容:
1. 使用 try / except 语句
def ua_display_good1(request): try: ua = request.META['HTTP_USER_AGENT'] except KeyError: ua = 'unknown' return HttpResponse("Your browser is %s" % ua)
2. 使用 python字典的 get()方法(推荐)
def ua_display_good2(request): ua = request.META.get('HTTP_USER_AGENT', 'unknown') return HttpResponse("Your browser is %s" % ua)
书中建议,你写一个函数,把request.META中所有数据打印出来看看,比如这样
1 def display_meta(request): 2 values = request.META.items() 3 values.sort() 4 html = [] 5 for k, v in values: 6 html.append('<tr><td>%s</td><td>%s</td></tr>' % (k, v)) 7 return HttpResponse('<table>%s</table>' % '\n'.join(html))
request.META的内容太多了,我把其内容做了初步的整理和翻译,有兴趣的同学可以到本节末尾附录中看。
当然,也可以用模板实现,而非手动输入代码,这里不多说。
request中,还有两个属性,内含用户所提交的信息:
成员 | 说明 |
request.GET | HTML中的<form>标签提交的 or URL中的查询字符串(the query string) |
request.POST | HTML中的<form>标签提交的 |
这两个都是类字典对象,即其实现了字典的所有成员,另外还有些字典没有的成员。
2. 利用GET请求,查询一本书籍
要做的事情很简单,做一个书籍查询页面,可以输入书名查书的信息。做法如下:
1. 按照模型一节所讲,创建书籍的数据库。
2. 在"/books/"下,创建几个文件
search_form.html
1 <html> 2 <head> 3 <title>Search</title> 4 </head> 5 <body> 6 {% if errors %} 7 <ul> 8 {% for error in errors %} 9 <li>{{ error }}</li> 10 {% endfor %} 11 </ul> 12 {% endif %} 13 <form action="" method="get"> 14 <input type="text" name="q"> 15 <input type="submit" value="Search"> 16 </form> 17 </body> 18 </html>
search_results.html
1 <p>You searched for: <strong>{{ query }}</strong></p> 2 3 {% if books %} 4 <p>Found {{ books|length }} book{{ books|pluralize }}.</p> 5 <ul> 6 {% for book in books %} 7 <li>{{ book.title }}</li> 8 {% endfor %} 9 </ul> 10 {% else %} 11 <p>No books matched your search criteria.</p> 12 {% endif %}
views.py
1 from django.shortcuts import render_to_response 2 from books.models import Book 3 4 # Create your views here. 5 6 def search(request): 7 errors = [] 8 if 'q' in request.GET: 9 q = request.GET['q'] 10 if not q: 11 errors.append('Enter a search term.') 12 elif len(q)>20: 13 errors.append('Please enter at most 20 characters.') 14 else: 15 books = Book.objects.filter(title__icontains=q) 16 return render_to_response('search_results.html', {'books': books, 'query': q}) 17 return render_to_response('search_form.html', {'errors': errors})
3. 在url中的urlpatterns属性内,加入如下一条 url(r'^search/$', views.search), 并对应写出from...import语句
4. 运行站点:python manage.py runserver
5. 打开搜索页面:http://127.0.0.1:8000/search/
3. 表单简介
在HTTP中,表单(form标签),是用来提交数据的,其action属性说明了其传输数据的方法:如何传、如何接收。
访问网站时,表单可以实现客户端与服务器之间的通信。例如我们上面的查询书籍,就用到了表单(其属性中,action=get)。再比如说注册与登陆,也是要用到表单的。但这里由于涉及到隐私问题,需要保证数据传输的安全性,因此其传输方法就应当使用post而非get。
总之,对客户端来说,表单就是用来向服务器提交数据的;而对服务器来说,表单就是你提供给客户端的发送信息的渠道,你需要对用户发送来的信息进行处理和响应,以达到页面的交互。
3+. get与post方法简介
这里做一些扩展——介绍一下表单的传输方法。
表单,一共有四种数据传输方法(即action的值):get、post、put、delete,即查、改、增、删。
比如,上面查询书籍的 search_form.html 代码中,用的就是get方法。
由于put和delete都可以用post实现,因此往往只使用get和post两种,甚至传统的Web MVC框架基本上都只支持这两种HTTP方法(-.-||)。这里,暂不介绍put和delete方法。
首先给出一段百度知道上对于get和post的简介,原文作者是tawa08,原文在这里。
1. get是从服务器上获取数据,post是向服务器传送数据。
2. get是把参数数据队列加到提交表单的action属性所指的url中,值和表单内各个字段一一对应,在url中可以看到。
post是通过http post机制,将表单内各个字段与其内容放置在html header内一起传送到action属性所指的url地址。
用户看不到这个过程。
3. 对于get方式,服务器端用Request.QueryString获取变量的值,对于post方式,服务器端用Request.Form获取提交的数据。
4. get传送的数据量较小,不能大于2KB。post传送的数据量较大,一般被默认为不受限制。
但理论上,IIS4中最大量为80KB,IIS5中为100KB。
5. get安全性非常低,post安全性较高。但是执行效率却比Post方法好。
建议:
1、get方式的安全性较Post方式要差些,包含机密信息的话,建议用Post数据提交方式;
2、在做数据查询时,建议用Get方式;而在做数据添加、修改或删除时,建议用Post方式;
如果希望对get和post进一步了解,那我这里推荐一篇文章,虽然长但却清晰而且很全:为什么大型网站都采用get方法,而非post方法。
至此,对于传输数据方法的介绍告一段落,咱们言归正传。
4. CSRF简介
由于我们要使用django中的form库,而且要用到post,所以便需要了解CSRF。
CSRF,Cross Site Request Forgery,跨站请求伪造,这是一种黑客攻击方式,这里不过多介绍。你只需知道,当你使用表单传输数据时,有可能会接触这种攻击方式。因此,我们学习表单,最好知道这种攻击方式的存在。对于想深入了解的同学,我推荐一篇博文:浅谈CSRF攻击方式。
从之前对get和post的介绍中,大家可以了解到,在标准的用法中,get由于毫无安全性可言,因此只应用作数据的查询;而一旦涉及到数据的添加、修改、删除时,则一定要采用post方式。那么,只要网站设计的符合规范,针对get的CSRF攻击便无从谈起。
因此,django则假设大家遵守这个标准,只在除了get之外那三种方法中才有针对CSRF的防御机制。
综上,在django中,若你接触到form中的post,要么就只使用get,要么就关了CSRF防御机制,要么就正确打开并使用CSRF防御机制,若不正确设置则无法使用form库。
这里给出官方的设置CSRF防御机制的步骤,若想进一步了解,参见官方文档:
1. 在所有使用post方法的模板(html)中,做如下修改:
把表单开头的代码 <form action="." method="post">
改成这样 <form action="." method="post">{% csrf_token %}
2. 在所有上面修改过的模板对应的视图(views.py)中,做如下修改:
把原视图代码,比如这样的
from django.shortcuts import render_to_response def my_view(request): return render_to_response("a_template.html")
改成这样
from django.core.context_processors import csrf from django.shortcuts import render_to_response def my_view(request): c = {} c.update(csrf(request)) return render_to_response("a_template.html", c)
5. email设置
我们后面要发送邮件,因此还需要先设置好email相关的内容。
先说一下后面具体要用django.form做什么:我们要做一个表单,效果如下图:
点击Submit之后,网页后台会发送一封邮件。标题就是hello,内容是hi!,从邮箱A发送到邮箱B。这两个邮箱都是我们提前设置好的。
我们设想的场景就是:这是一个网站,网站的访问者可以通过这里直接向网站的制作者发送邮件。
这个过程,是需要用到邮箱A的SMTP服务的,这需要你的开通。比如我用的qq邮箱,开通方式就如教程所说。
另外,这过程是django后台做的,那你自然需要告诉django你的邮箱用户名密码,还有SMTP服务的主机和端口,这需要在settings.py中添加以下参数:
EMAIL_HOST = 'smtp.qq.com' EMAIL_PORT = 25 EMAIL_HOST_USER = '[email protected]' EMAIL_HOST_PASSWORD = 'nicai'
这样一来,你便可以让django从后台帮你用你设置的这个邮箱(EMAIL_HOST_USER)发送邮件了。
这个存两个疑问,希望高手予以解答:
1. 为什么把EMAIL_PORT参数屏蔽了,仍可以正常运行?难道端口可以自动找?
2. 本例原意是邮箱A由访问者输入,但我这种设置方法则锁定邮箱A必须是设置的这个邮箱,于是这个Email的输入框形同虚设。
比如我就像下面代码中那样实现,send_mail()中的变量f若不是[email protected],则会报错:
SMTPSenderRefused at /contact/ (501, 'mail from address must be same as authorization user', u'[email protected]')
如果我想达到访问者可以用任意邮箱访问的效果,那么我应当如何设置呢?
6. django中的form库:django.form
了解了表单、django中的CSRF防御机制、email的设置,下面我们终于可以介绍django.form了。
django是框架,那么它的存在始终只有一个目的:让你写网站更加方便。django.form,就是一个可以让你快速写出表单的库。(不用form库的写法,原书中有,有兴趣的可以去看看,这里不写了)
具体例子上面介绍email设置时已经说过,下面直接给出实现步骤:
0. 把上面的email设置做好
1. 创建"/contact/"目录
2. 在其中创建一个空的__init__.py,以及下列文件
contact_form.html
1 <html> 2 <head> 3 <title>Contact us</title> 4 </head> 5 <body> 6 <h1>Contact us</h1> 7 8 {% if form.errors %} 9 <p style="color: red;"> 10 Please correct the error{{ form.errors|pluralize }} below. 11 </p> 12 {% endif %} 13 14 {% csrf_token %} 15 16 <form action="" method="post">{% csrf_token %} 17 <table> 18 {{ form.as_table }} 19 </table> 20 <input type="submit" value="Submit"> 21 </form> 22 </body> 23 </html>
forms.py
1 from django import forms 2 3 class ContactForm(forms.Form): 4 subject = forms.CharField() 5 email = forms.EmailField(required=False) 6 message = forms.CharField()
views.py
1 from django.core.mail import send_mail 2 from django.core.context_processors import csrf 3 from django.shortcuts import render_to_response, RequestContext 4 from contact.forms import ContactForm 5 from django.http import HttpResponseRedirect 6 7 def thanks(request): 8 return render_to_response('thanks.html') 9 10 def contact(request): 11 c = {} 12 c.update(csrf(request)) 13 if request.method == 'POST': 14 form = ContactForm(request.POST) 15 if form.is_valid(): 16 cd = form.cleaned_data 17 f = cd.get('email', '[email protected]') 18 if f == '': f = '[email protected]' 19 send_mail( 20 cd['subject'], 21 cd['message'], 22 f, 23 ['[email protected]'], 24 ) 25 return HttpResponseRedirect('/contact/thanks/', {'method': request.method}) 26 else: 27 form = ContactForm() 28 c['form'] = form 29 return render_to_response('contact_form.html', c, context_instance=RequestContext(request))
thanks.html
1 <html> 2 <body> 3 Thanks! 4 </body> 5 </html>
3. 在url的urlpatterns属性内,加入如下两条 url(r'^contact/$', contact), url(r'^contact/thanks/$', thanks), 并对应写出from...import语句
4. 运行站点:python manage.py runserver
5. 打开站点联系表单页面:http://127.0.0.1:8000/contact/
6. 如果成功,你应该能在邮箱B(即代码中[email protected])中收到你用邮箱A(即代码中[email protected])所发的邮件。
7. 总结:这一节都讲了些什么
这一节首先介绍了之前一直在使用却并不了解的HttpResponse对象,
然后介绍了表单、GET和POST方法、CSRF攻击方式、email设置,
最后介绍了django中的表单(form)库,以及如何使用它做出网站的表单。
------------------------------------------------------------------------------------------------------------------------------------------------
至此,“表单”一章笔记完成,django基础部分学习完毕。后面开始高级部分,下一章是“高级视图与URL配置”。
附录1+. HttpResponse.META内容
名称 | 值(断句方式仅供参考) | 参考翻译 |
CLUTTER_IM_MODULE | xim | CLUTTER输入法模块 |
COLORTERM | gnome-terminal | 终端配色 |
COMPIZ_CONFIG_PROFILE | ubuntu | 特效配置资料 |
CONTENT_LENGTH | 内容长度 | |
CONTENT_TYPE | text/plain | 内容类型 |
CSRF_COOKIE | R6SCXazGfl9QGZ2YsCI3VniLFiYNeOUj | CSRFcookie |
DBUS_SESSION_BUS_ADDRESS | unix:abstract=/tmp/dbus-HALHk0izgV | 数据总线会话 总线地址 |
DEFAULTS_PATH | /usr/share/gconf/ubuntu.default.path | 默认路径 |
DESKTOP_SESSION | ubuntu | 桌面会话 |
DISPLAY | :0 | 展示 |
DJANGO_SETTINGS_MODULE | comeback.settings | django设置模块 |
GATEWAY_INTERFACE | CGI/1.1 | 网关接口 |
GDMSESSION | ubuntu | GDM会话 |
GDM_LANG | zh_CN | GDM语言 |
GNOME_DESKTOP_SESSION_ID | this-is-deprecated | GNOME桌面会话ID |
GNOME_KEYRING_CONTROL | /run/user/1000/keyring-SkW2gT | GNOME钥匙控制 |
GNOME_KEYRING_PID | 2176 | GNOME钥匙PID |
GPG_AGENT_INFO | /run/user/1000/keyring-SkW2gT/gpg:0:1 | GPG代理信息 |
GTK_IM_MODULE | fcitx | GTK输入法模块 |
GTK_MODULES | overlay-scrollbar: unity-gtk-module |
GTK模块 |
HOME | /home/icedream | 家 |
HTTP_ACCEPT | text/html, application/xhtml+xml, application/xml;q=0.9, image/webp, */*;q=0.8 |
HTTP接收 |
HTTP_ACCEPT_ENCODING | gzip, deflate, sdch |
HTTP接收编码 |
HTTP_ACCEPT_LANGUAGE | zh-CN, zh;q=0.8, en;q=0.6, en-US;q=0.4, en-GB;q=0.2 |
HTTP接收语言 |
HTTP_CONNECTION | keep-alive | HTTP连接 |
HTTP_COOKIE | sessionid=8ifnqpfwvuh0pm04kq24zz4djw3lx4fp; csrftoken=R6SCXazGfl9QGZ2YsCI3VniLFiYNeOUj |
HTTPcookie 会话id & CSRF令牌 |
HTTP_HOST | 127.0.0.1:8000 | HTTP主机 |
HTTP_USER_AGENT | Mozilla/5.0 (X11; Linux i686) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/43.0.2357.134 Safari/537.36 |
HTTP用户代理 |
IM_CONFIG_PHASE | 1 | 输入法配置阶段 |
INFOPATH | :/usr/local/texlive/2015/texmf-dist/doc/info | 信息路径 |
INSTANCE | Unity | 实例 |
JOB | gnome-session | 工作 |
LANG | zh_CN.UTF-8 | 语言 |
LANGUAGE | zh_CN: zh |
语言 |
LESSCLOSE | /usr/bin/lesspipe %s %s | LESS关闭 |
LESSOPEN | | /usr/bin/lesspipe %s | LESS打开 |
LOGNAME | icedream | 登陆用户名 |
LS_COLORS | rs=0: |
LS颜色 |
MANDATORY_PATH | /usr/share/gconf/ubuntu.mandatory.path | MANDATORY(托管)路径 |
MANPATH | :/usr/local/texlive/2015/texmf-dist/doc/man | MAN路径 |
OLDPWD | /home/icedream | 旧的工作目录 |
PATH | /usr/local/sbin: /usr/local/bin: /usr/sbin: /usr/bin: /sbin: /bin: /usr/games: /usr/local/games: /usr/local/texlive/2015/bin/i386-linux |
路径 |
PATH_INFO | /cookie/ | 路径信息 |
PWD | /home/icedream/workspace/django/comeback | 当前目录 |
QT4_IM_MODULE | fcitx | QT4输入法模块 |
QT_IM_MODULE | fcitx | QT输入法模块 |
QT_QPA_PLATFORMTHEME | appmenu-qt5 | QT_QPA平台主题 |
QUERY_STRING | 查询字符串 | |
REMOTE_ADDR | 127.0.0.1 | 远程地址 |
REMOTE_HOST | 远程主机 | |
REQUEST_METHOD | GET | 请求方法 |
RUN_MAIN | true | 运行MAIN |
SCRIPT_NAME | 脚本名称 | |
SERVER_NAME | localhost | 服务器名称 |
SERVER_PORT | 8000 | 服务器端口 |
SERVER_PROTOCOL | HTTP/1.1 | 服务器协议 |
SERVER_SOFTWARE | WSGIServer/0.1 Python/2.7.8 | 服务器软件 |
SESSIONTYPE | gnome-session | 会话类型 |
SHELL | /bin/bash | 命令行 |
SHLVL | 1 | 命令行层次 |
SSH_AUTH_SOCK | /run/user/1000/keyring-SkW2gT/ssh | SSH_AUTH_SOCK |
TERM | xterm | TERM |
TEXTDOMAIN | im-config | 文本域 |
TEXTDOMAINDIR | /usr/share/locale/ | 文本域目录 |
TZ | UTC | 时区 |
UPSTART_EVENTS | started starting | UPSTART事件 |
UPSTART_INSTANCE | UPSTART距离 | |
UPSTART_JOB | unity-settings-daemon | UPSTART作业 |
UPSTART_SESSION | unix:abstract=/com/ubuntu/upstart-session/1000/2178 | UPSTART会话 |
USER | icedream | 用户 |
VTE_VERSION | 3603 | VTE版本 |
WINDOWID | 69206028 | 窗口ID |
XAUTHORITY | /home/icedream/.Xauthority | X权威 |
XDG_CONFIG_DIRS | /etc/xdg/xdg-ubuntu: /usr/share/upstart/xdg: /etc/xdg |
XDG配置路径 |
XDG_CURRENT_DESKTOP | Unity | XDG当前桌面 |
XDG_DATA_DIRS | /usr/share/ubuntu: /usr/share/gnome: /usr/local/share/: /usr/share/ |
XDG数据路径 |
XDG_GREETER_DATA_DIR | /var/lib/lightdm-data/icedream | XDG_GREETER数据路径 |
XDG_RUNTIME_DIR | /run/user/1000 | XDG运行时路径 |
XDG_SEAT | seat0 | XDG椅子 |
XDG_SEAT_PATH | /org/freedesktop/DisplayManager/Seat0 | XDG椅子路径 |
XDG_SESSION_DESKTOP | ubuntu | XDG会话桌面 |
XDG_SESSION_ID | c2 | XDG会话ID |
XDG_SESSION_PATH | /org/freedesktop/DisplayManager/Session0 | XDG会话路径 |
XDG_SESSION_TYPE | x11 | XDG会话类型 |
XDG_VTNR | 7 | XDG_VTNR |
XMODIFIERS | @im=fcitx | XMODIFIERS |
_ | /usr/bin/python | _ |
wsgi.errors | ', mode 'w' at 0xb74d20d0> | WSGI错误 |
wsgi.file_wrapper | wsgiref.util.FileWrapper | WSGI文件包装 |
wsgi.input | <socket._fileobject object="" at="" 0xb5a726ec=""> | WSGI输入 |
wsgi.multiprocess | False | WSGI多进程 |
wsgi.multithread | True | WSGI多线程 |
wsgi.run_once | False | WSGI运行一次 |
wsgi.url_scheme | http | WSGI网址类型 |
wsgi.version | (1, 0) | WSGI版本 |