Django是一个全栈式的Web框架,它可能是目前在Python程序员中最流行的框架了。Django几乎提供了一个新手程序员需要的所有功能.它有一套自己的模板系统和URL路由框架,提供了与数据库的交互功能,并且以Python对象的形式来生成数据库查询结果。除此之外,使用Django时不需要使用任何第三方库就能够构造并解析表单。现在,其实很多程序员都没有经过系统的Web编程训练。使用Django这样的框架恰恰提供了易懂而又安全的编程模式,这对新手程序员来说可能更有价值。如果使用的是一些更灵活的框架,程序员就需要自己寻找ORM库和表单操作库,而他们可能还不太清楚应该如何将这些库与Web框架结合使用。
同样地,可以在本书的源代码库中找到完整的用Django编写的账单应用程序,但是截止到当前时间,我们下载到的Django的最新版本会与书中使用的Django1.7版本有所不同,包括django.conf.urls包内的patterns函数被删除。所以想要运行该应用程序,还需要自己去修改一些内容。那些是关于Django自己的部署问题,我们不再列出,在以后对Django的使用中我们可以熟悉它。本章讨论修改过的三段代码。
# models.py
from django.db import models
from django.forms import ModelForm
class Payment(models.Model):
debit = models.CharField(max_length=200)
credit = models.CharField(max_length=200,verbose_name='To account')
dollars = models.PositiveIntegerField()
memo = models.CharField(max_length=200)
class PaymentForm(ModelForm):
class Meta:
model = Payment
fields = ['credit','dollars','memo']
'''
下面的类声明表示一个用于创建和编辑数据库行的表单。用户只需要填写列出的3个字段即可。程序会使用当前登录用户的用户名自动填充debit字段。
后面将会看到,这个类可以与Web应用程序的用户进行双向交互。它可以根据表单类信息生成HTML的字段,也可以反过来在表单提交后解析出
HTTP POST的数据,然后创建或修改Payments数据库行。
如果使用的是Flask这样的微框架,那么久必须自己选择一个外部库来支持这样的操作。例如,SQLAlchemy是一个很有名的ORM。许多程序员之所以
不选择使用Django,就是因为想使用SQLAlchemy这个强大又优雅的ORM。
但是SQLAlchemy本身并不了解任何关于HTML表单的信息,因此使用微框架的程序员还需要寻找另一个第三方库来完成models.py中PaymentForm的功能。
在Flask中,程序员使用flask风格的装饰器将URL路径添加到视图函数中;而在Django中,应用程序开发者需要创建一个urls.py文件。虽然这种做法减
少了我们在单独阅读视图函数时所能获得的语义信息,但是也使得视图和URL分发功能独立,而且能够集中管理URL空间。
'''
#urls.py
'''
在Flask中,程序员使用flask风格的装饰器将URL路径添加到视图函数中;而在Django中,应用程序开发者需要创建一个urls.py文件。虽然这种做法减
少了我们在单独阅读视图函数时所能获得的语义信息,但是也使得视图和URL分发功能独立,而且能够集中管理URL空间。
'''
from django.conf.urls import include,url
from django.contrib import admin
from django.contrib.auth.views import login
urlpatterns = [
url(r'^admin/',admin.site.urls),
url(r'^accounts/login/$',login),
url(r'^$','djbank.views.index_view',name='index'),
url(r'^pay/$','djbank,views.pay_view',name='pay'),
url(r'^logout/$','djbank.views.logout_view'),
]
'''
Django使用正则表达式来匹配URL。这一选择相当诡异,因为当URL中包含较多变量时,正则表达式的匹配模式会变得难以阅读。根据作者经验,使用正则表达式
表示的URL模式也是难以调试的。
上面这些模式与之前的Flask应用程序表示的URL空间只有一处不同:登录页面的路径遵循了Django认证模块的约定。使用Django时不需要自己编写登录页面,标
准的Django登录页面已经完成了这一功能,因此也不必担心如何正确地去除登录页面的任何安全隐患。
'''
# views.py
'''
Django应用程序的视图。
比起Flask版本的应用程序,Django版本的视图既简单,又复杂。
'''
from django.contrib import messages
from django.contrib.auth.decorators import login_required
from django.contrib.auth import logout
from django.db.models import Q
from django.shortcuts import redirect,render
from django.views.decorators.http import require_http_methods,require_safe
from .models import Payment,PaymentForm
def make_payment_views(payments,username):
for p in payments:
yield{
'dollars':p.dollars,'memo':p.memo,
'prep':'to'if(p.debit == username) else 'from',
'account':p.credit if(p.debit == username) else p.debit
}
@require_http_methods(['GET'])
@login_required #如果某个视图要求用户事先登录,那么可以用@login_required进行标记。
def index_view(request):
username = request.user.username
payments = Payment.objects.filter(Q(credit=username)|Q(debit=username))
payment_views = make_payment_views(payments,username)
return render(request,'index.html',{'payments':payment_views})
@require_http_methods(['GET','POST'])
@login_required
def pay_view(request):
form = PaymentForm(request.POST or None)
if form.is_valid():
payment = form.save(commit=False)
payment.debit = request.user.username
payment.save()
messages.add_message(request,messages.INFO,'Payment successful.')
return redirect('/')
return render(request,'pay.html',{'form':form})
@require_http_methods(['GET'])
def logout_view(request):
logout(request)
return redirect('/')
'''
读者可能要问一个很严肃的问题:代码中什么地方对跨站脚本攻击做了保护?其实,当我们运行manage.py startapp命令来构建这个Django
应用程序的框架时,Django就会自动在settings.py中设置跨站脚本攻击保护。
除此之外,我们完全不需要了解CSRF保护,因为如果我们没有在表单模板中添加{%csrf_token%}的话,那么是无法成功提交表单的,Django
会在runserver开发模式下输出错误信息,告诉开发者需要在表单中添加{%csrf_token%}.这一功能对于不太了解安全问题的Web新手开发者非常有
用,默认配置下就能够防御由常见的表单或字段错误而引起的安全威胁。这是其他微框架很少能提供的功能。
上面这段代码利用Django的内置功能完成了几乎所有的工作,因此这个应用程序的视图在概念上要比Flask应用程序的视图简单的多。程序员
不需要自己去实现登陆和会话操作这样的功能,因为urls.py中直接使用了Django的登录页面,所以视图中甚至不包含登录页面。登出页面可以直
接调用logout()函数实现,无需了解具体的工作原理。如果某个视图要求用户事先登录,那么可以用@login_required进行标记。
在Django应用程序的视图中,只有@require_http_methods()装饰器与Flask应用程序使用了相同轴象层次的方法来完成辅助功能。这两者都
用于防止三视图使用非法或不支持的HTTP方法。
在Django应用程序中与数据库进行交互要简单的多。现在已经完全不需要在bank.py中进行SQL操作了。Django会自动建立一个SQLite数据库
——这也是settings.py中的默认配置。当代码查询models.py的模型类时,Django就会打开一个数据库会话。虽然我们没有在代码中要求Django
打开数据库事务,但是调用save()来保存一个新账单时,Django会自动调用COMMIT。
PaymentForm类会负责生成HTML并解析POST参数,因此不需要在pay_view()中将表单字段全部列出。根据要求,pay_view()会使用当前登录的
用户名填充debit字段。而Django的表单则会负责处理所有细节。
不过有一点看上去挺奇怪的,在主页中显示的账单信息现在是在Python代码中编写的,而这本来应该是在模板中进行编写的。这是因为,使用
Django的模板系统来表达这一逻辑并没有那么容易。不过,在Python中处理这一逻辑就要简单多了:index()会试图调用一个生成器,该生成器会
为每个账单信息生成一个字典,然后把原始对象转换成模板能够识别的值。
有些程序员对Django的这个功能弱小的模板系统相当不满。另一些程序员则开始学习编写一些“模板标记”,以便在模板中嵌入更负载的自定义
逻辑,不过也有开发者认为,上面的这段代码在长远来看还是最优的,因为,为make_payment_views()这样的函数编写测试用例要比测试模板内的
逻辑要简单的多。
'''
框架的选择:如果身边有人已经遇到过一些典型的错误并踩过一些坑,对于某框架的使用有自己的心得,而我们又能得到他们的实时帮助,那么我们可以选择使用这个框架,相比之下框架的某个特性是更好用还是用起来麻烦一些就远没有那么重要了。 如果没有参考,那么可以根据别人对Web框架的评价来选择框架。
使用了Javascript的网站通常需要实时更新网页的内容。假设我们在浏览自己的Twitter首页,此时我们关注的某个好友发表了一条新的Twitter,那么我们正在浏览的页面就会实时刷新。在这一过程中,浏览器不会每秒都向服务器进行轮询,检查是否有新的Twitter。WebSocket协议就是用来解决这个“长轮询问题”的最有力的解决方案。
因为WSGI不支持WebSocket,这一点也正是Tornado框架能够流行起来的主要原因之一。
在HTTP中,客户端首先会发送一个请求,然后等待服务器响应。只有服务器完成了响应,客户端才能够发送下一个请求。但是,如果将套接字切换到WebSocket模式,就可以同时向两个方向发送消息了。客户端可以在用户与页面交互时向服务器发送实时更新,服务器也可以从其他地方收到更新时向客户端同步更新消息。
开始时,WebSocket会话看上去和普通的HTTP请求与相应类似,但是WebSocket会话会在头信息和状态码中进行协商,表明套接字不使用HTTP协议。协商完成后,WebSocket会话就会采用一个全新的帧数据系统。
在进行WebSocket编程时,一般需要进行大量的前端Javascript库与服务器代码之间的交互操作。这不属于本书介绍的内容。tornado.websocket模块中包含了一段Python和Javascript代码,它们通过一对对称的回调函数进行交互。读者可以从该模块开始学习WebSocket编程的内容。