@hpp157
2016-08-22T05:01:27.000000Z
字数 12114
阅读 2754
django
python
本章源代码已经上传到github,需要参考的可以下载:download
大多数django
的教程集中在一些UGC
程序的开发开发上,比如to
,博客,
-do listscms
等,考虑到它最初是被开发来用于管理劳伦斯出版集团
旗下的一些以新闻内容为主的网站的你可能就不会太奇怪了。
2005年,django
于美国肯萨斯州的劳伦斯集团的World Online
上发布,它可以使记者非常快速的把自己的内容发布到web网站上。从那时候开始django
被一些出版机构比如美国《华盛顿邮报》,英国《卫报》,《PolitiFact》以及美国著名报纸《the onion》。从这个角度看,可能会给你一种django
的目的是用来内容出版,或者本身是一个cms
系统的印象。然而随着后来美国的NASA开始采用django
作为它们的框架选择,django
明显已经超出了最初设计目的。
上一章,我们仅仅用django
的http处理
以及URL 路由
写了一个小型项目。本章会继续扩展这个项目,使用django
的工具如,input 验证
,缓存
,模板
创建一个无状态的Web应用。
HTTP
本身是一个无状态的协议,每一个个从服务器接收到的请求都是独立于先前的请求。如果特别需要一个状态,需要添加到应用层,django
使用cookies
和其他机制把每个独立的请求绑定到一个相同的客户端上。
随着session
被持久化
存储在服务器上,应用然后可以处理任务,比如通过请求来处理用户验证。这给分布式服务器统一的状态读取和写入带来了许多挑战。
你可以想象,一个无状态的应用,不用维护在一台服务器上的一致性状态。如果其他用户身份验证是必须的,那么它们必须通过客户端发送到每一个请求上面,无状态应用让伸缩(scaling
),缓存(caching
),负载均衡(load balancing
)更容易。可以参考一下下面这篇文章了解一下无状态的概念:
对于REST中无状态(stateless)的一点认识
django
社区聚焦于可以安装和配置到任何django
项目中的可复用
应用。可是,不同组件组成的大型应用通常有非常复杂结构。
一种解决方法是把复杂的网站按服务拆分成不同的组件——也就是说,一个可以和另一个服务交流的小服务。这并是说它们之间就不能共用代码了,而是每个服务可以独立安装和配置的意思。
无状态组件
,像REST APIs
可以把django
项目分开,变成独立的组件服务。接下来我们将给第一章的例子加上一个placeholder image server
,使用我们刚学过的服务组件化知识来做
用atartproject
命令来创建一个叫做placeholder
的项目,项目模板用第一章创建的
django-admin.py startproject placeholder --template=project_name
这会产生一个placeholder.py
的文件让我们开始我们的项目,这个文件内容如下:
#!/usr/bin/env python
import sys
from django.conf import settings
DEBUG=os.environ.get('DEBUG','on') =='on'
SECRET_KEY =os.environ.get('SECRET','%jv_4#hoaqwig2gu!eg#^ozptd*a@88u(aasv7z!7xt^5(*i&k')
ALLOWED_HOSTS=os.environ.get('ALLOWED_HOSTS','localhost').split('.')
settings.configure(
DEBUG = DEBUG,
SECRET_KEY =SECRET_KEY,
ALLOWED_HOSTS=ALLOWED_HOSTS,
ROOT_URLCONF = __name__,
MIDDLEWARE_CLASSES=(
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware'
),
)
from django.conf.urls import url
from django.core.wsgi import get_wsgi_application
from django.http import HttpResponse
def index(request):
return HttpResponse('hello,world')
urlpatterns=(
url(r'^$',index),
)
application = get_wsgi_application()
#manage.py的功能
if __name__ == '__main__':
from django.core.management import execute_from_command_line
execute_from_command_line(sys.argv)
由于应用比较简单,我们只需要两个视图来提供我们的响应,第一个视图会根据请求中的宽和高来渲染产生图像占位符palceholder view
。另一个(homepage view
)渲染首页内容,用来阐释项目怎么工作,以及渲染几个成品,因为我们在使用atartproject
命令来创建项目的时候使用了template
标签,因此,placeholder.py
中会自动添加一个首页(下面代码),我们在稍后会使用它。
...
def placeholder(request,width,height):
#todo :rest of the view will go here
return HttpResponse('ok')
def index(request):
return HttpResponse('hello world')
...
有了简单的palceholder
视图,我们现在应该考虑接下来我们项目要显示的URL结构
placeholder
视图后面应该跟上两宽和高参数,正如先前提到的,这两个参数将会被URL捕获并且传递给视图。由于参数只能是整数,我们应该通过URL确保参数为整数,django
中用正则表达式来匹配输入的url
,我们将会很方便的处理这些参数。
捕获模式组(captured pattern groups)作为位置参数传递给view,命名组(named groups)作为关键字参数传递,用?P
语法来捕获( 使用命名组,能够使代码更加清晰,在做一些复杂模块的时候,只需看一下URLconf就大概知道怎么回事了。)。任意的一个数字字符用[0-9]
.匹配多个用[0-9]+
下面是匹配的规则:
urlpatterns =(
#localhost/user/image/30×25/
url(r'^image/(?P<width>[0-9]+)×(?P<height>[0-9]+)/$'
)
现在,首页homepage
已经被添加到index
了,我们也会看到怎样命名url模式
,为了更好的练习,我们会在稍后创建一个templates
根据原始的http
的请求,placeholder view
应该接收两个整数参数,作为图像的宽和高。正则表达式可以确保高和宽由数字组成,传递给视图过程中会变为字符串,view
需要将它们再次转换成整数,并且验证图像的尺寸。我们可以通过django
的forms
模块来轻松的处理用户输入
forms
表单常常用来检验POST
和GET
内容,但是它也可应用来检验url
的特殊值,或者存储在cookies
中的值,这儿有一个使用forms
检验图像的宽和高的的简单例子
...
from django import forms
from django.cof.urls import url
class ImageForm(forms.Form):
height = forms.IntegerField(min_value=1,max_value=2000)
weight = forms.IntegerField(min_value=1,max_value=2000)
def palceholder(request,width,height):
...
如你看到的,view
先验证请求的图像尺寸,如果尺寸有效,width
和height
会被加上clean_data
属性。到这儿,宽和高会被转换成整数,并且 view
可以保证宽和高的值在1到2000之间。我们还需要一个forms
的错误信息的发送的功能,如果尺寸不正确的话可以发送错误信息。
...
from django import forms
from django.conf.urls import url
from django.core.wsgi import get_wsgi_application
from django.http import HttpRespose, HttpResponseBadRequest
class ImageForm(forms.Form):
height = forms.IntegerField(min_value=1,max_value=2000)
weight = forms.IntegerField(min_value=1,max_value=2000)
def placeholder(request,width,height):
form = ImageForm({'width':width,'height':height })
if form.is_valid():
height = form.cleaned_data['height']
width = form.cleaned_data['width']
#TODO :generate image of requested size这里要编写生成满足宽和高条件的图像,先空着
return HttpResponse('ok')
else:
return HttpResponse('Invalid Image Request')
如果图像尺寸验证失败,view会给客户端发送一个错误响应。这里的HttpResponseBadRequest
是HttpResponse
的子类,发送一个404
错误响应
view
现在有能力接收和处理客户端请求的宽和高了。但是还不能生成实际图像,要在python中处理图像的话,你需要安装Pillow
模块
默认的,Pillow
的安装要从源码编译,如果你的环境没有安装编译器,可能会安装失败,遵从其官网的安装说明安装,这儿有个官网的链接Pillow安装
用pillow
创建图像需要两个参数,颜色模式
和一个尺寸
组成的tuple
,还有第三个可选参数,是用来设置创建图像的颜色的,默认创建的图像是黑色的
...
from io import BytesIO
from PIL import Image
class ImageForm(forms.Form):
height = forms.IntegerField(min_value=1,max_value=2000)
weight = forms.IntegerField(min_value=1,max_value=2000)
def generate(self,image_format='PNG'):
height = self.cleaned_data['height']
width = self.cleaned_data['width']
#create image
image = Image.new('RGB',(width,height))
content = BytesIO()
image.save(content,image_format)
content.seek(0)
return content
def placeholder(request,width,height):
form = ImageForm({'width':width,'height':height })
if form.is_valid():
image=form.generate()
return HttpResponse(image,content_type='image/png')
else:
return HttpResponse('Invalid Image Request')
在ImageForm
类中添加了一个generate
方法,里面封装了生成图像的方法逻辑。这个方法需要一个参数来表示图像格式,默认是PNG
,并且以字节的形式返回图像内容,
调用该方法用from.generate()
来生成实际的图像,图像是发送给了客户端,并没有写入磁盘。
有一点需要改进,就是图像生成的是纯黑色的图片,没有尺寸信息,这点不太好。我们可以用pillow
的ImageDraw
模块给图片添加描述信息(palceholder
)。
...
from PIL import Image,ImageDraw
...
class ImageForm(forms.Form):
def generate(self,image_format='PNG'):
height = self.cleaned_data['height']
width = self.cleaned_data['widht']
image = Image.new('RGB',(width,height))
#draw
draw = ImageDraw.Draw(image)#创建一支笔
text ='{}×{}'.format(width,height)
textwidth, textheight =draw.textsize(text)
if textwidth <width and textheight <height:
texttop = (height - textheight)//2
textleft = (width - textwidth)//2
#开始写描述信息了
draw.text((textleft,texttop),text,fill=(255,255,255))
content=BytesIO()
image.save(content,image_format)
content.seek(0)
return content
...
考虑一下,如果每次访问都生成一次图像(图像没有改变情况下),好像有点浪费资源。
避免重复的解决方法就是使用缓存,当你决定给项目增加缓存时有两个选择,服务端和客户端,服务端缓存可以使用django
的缓存工具,方法如下:
...
from django.conf.urls import url
from django.core.cache import cache
...
class ImageForm(forms.Form):
def generate(self,image_format='PNG'):
height = self.cleaned_data['height']
width = self.cleaned_data['widht']
key = '{}.{}.{}'.format(width,height,image_format)
content = cache.get(key)
if content is None:
image = Image.new('RGB',(width,height))
#draw
draw = ImageDraw.Draw(image)#创建一支笔
text ='{}×{}'.format(width,height)
textwidth, textheight =draw.textsize(text)
if textwidth <width and textheight <height:
texttop = (height - textheight)//2
textleft = (width - textwidth)//2
#开始写描述信息了
draw.text((textleft,texttop),text,fill=(255,255,255))
content=BytesIO()
image.save(content,image_format)
content.seek(0)
cache.set(key,content,60*60)
return content
而另一种客户端缓存是使用浏览器内置的缓存,django
使用装饰器etag
来实现:
import hashlib
import os
...
from django.http import HttpResponse, HttpResponseBadRequest
from django.views.decorators.http import etag
...
def generate_etag(request,width,height):
content = 'Placeholder:{0}×{1}'.formate(width,height)
return hashlib.sha1(content.encode('utf-8')).hexdigest()
@etag(generate_etag)
def placeholder(request,widht,height):
...
这样placeholder
视图就完成了,接下来我们回去把 homepage
视图完成,然后整个项目就完成了
django
的模板加载器,会自动发现已安装的app项目里的template
和静态资源。不过需要你设置模板的路径TEMPLATE_DIRS
和静态资源路径STATICFILES_DIRS
等,django.contrib.staticfiles
也需要添加到INSTALLED_APPS
来使{% static %}
这样的命令生效。
目前你的项目目录是这样:
placeholder/
placeholder.py
静态资源的目录要和placeholder.py
在同级目录
placeholder/
placeholder.py
templates/
home.html
static/
site.css
路径多了写者会不方便,我们会使用django
内置的os
模块来处理它
import hashlib
import os
import sys
from django.conf import settings
DEBUG=os.environ.get('DEBUG','on') =='on'
SECRET_KEY =os.envrion.get('SECRET','%jv_4#hoaqwig2gu!eg#^ozptd*a@88u(aasv7z!7xt^5(*i&k')
#设置项目根路径
BASE_DIR =os.path.dirname(__file__)
settings.configure(
DEBUG = DEBUG,
SECRET_KEY =SECRET_KEY,
ALLOWED_HOSTS=ALLOWED_HOSTS,
ROOT_URLCONF = __name__,
MIDDLEWARE_CLASSES=(
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware'
),
#模板设置
INSTALLED_APPS=(
'django.contrib.staticfiles',
),
TEMPLATE_DIRS=(
os.path.join(BASE_DIR,'templates'),
),
STATICFILES_DIRS=(
os.path.join(BASE_DIR,'static'),
),
STATIC_URL='/static/',
)
首页的目的是为了演示程序怎么使用的,包括说明文档和例子
{% load staticfiles %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<link rel="stylesheet" type="text/css" href="{% static 'site.css' %}">
<title>Document</title>
</head>
<body>
<h1>Django Placeholder Images</h1>
<p>This server can be used for serving placeholder images for webpage</p>
<h2>Examples</h2>
<ul>
<li><img src="{% url 'placeholder' width=50 height=50 %}" alt=""></li>
<li><img src="{% url 'placeholder' width=100 height=50 %}" alt=""></li>
<li><img src="{% url 'placeholder' width=50 height=100 %}" alt=""></li>
</ul>
</body>
</html>
下面是文件site.css
中添加的样式:
body{
text-align: center;
}
ul{
list-style-type: none;
}
li{
display: inline-block;
}
最后,我们需要更新placeholder.py
的首页视图view
,渲染这个模板:
...
from django.core.cache import cache
from django.core.urlresolvers import reverse
from django.core.wsgi import get_wsgi_application
from django.http import HttpResponse, HttpResponseBadRequest
from django.shortcuts import render
from django.views.decorators.http import etag
...
def index(request):
example = reverse('placeholder', kwargs={'width': 50, 'height': 50})
context = {
'example': request.build_absolute_uri(example)
}
return render(request, 'home.html', context)
最终项目文件如下:
import hashlib
import os
import sys
from io import BytesIO
from PIL import Image, ImageDraw
from django.conf import settings
DEBUG = os.environ.get('DEBUG', 'on') == 'on'
SECRET_KEY = os.environ.get('SECRET_KEY', '@up*9j#$0$bza-38l!id=79!at!j@wg%!2%2ur!n&2q07jl2%0 ')
ALLOWED_HOSTS = os.environ.get('ALLOWED_HOSTS', 'localhost').split('.')
BASE_DIR = os.path.dirname(__file__)
settings.configure(
DEBUG=DEBUG
, SECRET_KEY=SECRET_KEY
, ROOT_URLCONF=__name__
, MIDDLEWARE_CLASSES=(
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware'
)
, INSTALLED_APPS=(
'django.contrib.staticfiles',
'django.contrib.contenttypes',
'django.contrib.auth',
)
, TEMPLATE_DIRS=(
os.path.join(BASE_DIR, 'templates'),
)
, STATICFILES_DIRS=(
os.path.join(BASE_DIR, 'static'),
)
, STATIC_URL='/static/',
)
from django import forms
from django.conf.urls import url
from django.core.cache import cache
from django.core.urlresolvers import reverse
from django.core.wsgi import get_wsgi_application
from django.http import HttpResponse, HttpResponseBadRequest
from django.shortcuts import render
from django.views.decorators.http import etag
class ImageForm(forms.Form):
height = forms.IntegerField(min_value=1,max_value=2000)
width =forms.IntegerField(min_value=1,max_value=2000)
def generate(self, image_format='PNG'):
height = self.cleaned_data['height']
width = self.cleaned_data['width']
key = '{}.{}.{}'.format(width, height, image_format)
content = cache.get(key)
if content is None:
image = Image.new('RGB', (width, height))
# draw
draw = ImageDraw.Draw(image) # 创建一支笔
text = '{}×{}'.format(width, height)
textwidth, textheight = draw.textsize(text)
if textwidth < width and textheight < height:
texttop = (height - textheight) // 2
textleft = (width - textwidth) // 2
# 开始写描述信息了
draw.text((textleft, texttop), text, fill=(255, 255, 255))
content = BytesIO()
image.save(content, image_format)
content.seek(0)
cache.set(key, content, 60 * 60)
return content
def generate_etag(request, width, height):
content = 'Placeholder:{0}×{1}'.format(width, height)
return hashlib.sha1(content.encode('utf-8')).hexdigest()
@etag(generate_etag)
def placeholder(request, width, height):
form = ImageForm({'width': width, 'height': height})
if form.is_valid():
image = form.generate()
return HttpResponse(image, content_type='image/png')
else:
return HttpResponse('Invalid Image Request')
def index(request):
example = reverse('placeholder', kwargs={'width': 50, 'height': 50})
context = {
'example': request.build_absolute_uri(example)
}
return render(request, 'home.html', context)
urlpatterns = (
url(r'^image/(?P<width>[0-9]+)×(?P<height>[0-9]+)/$',
placeholder, name='placeholder'),
url(r'^$', index, name='homepage'),
)
application = get_wsgi_application()
if __name__ == '__main__':
from django.core.management import execute_from_command_line
execute_from_command_line(sys.argv)
现在,在命令行启动服务器
python placeholder.py runserver
打开浏览器访问localhost:8000
,图示如下: