@hpp157
2016-08-22T05:01:27.000000Z
字数 12114
阅读 2873
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 pythonimport sysfrom django.conf import settingsDEBUG=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 urlfrom django.core.wsgi import get_wsgi_applicationfrom django.http import HttpResponsedef 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_lineexecute_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 herereturn 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 formsfrom django.cof.urls import urlclass 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 formsfrom django.conf.urls import urlfrom django.core.wsgi import get_wsgi_applicationfrom django.http import HttpRespose, HttpResponseBadRequestclass 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 BytesIOfrom PIL import Imageclass 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 imageimage = Image.new('RGB',(width,height))content = BytesIO()image.save(content,image_format)content.seek(0)return contentdef 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))#drawdraw = ImageDraw.Draw(image)#创建一支笔text ='{}×{}'.format(width,height)textwidth, textheight =draw.textsize(text)if textwidth <width and textheight <height:texttop = (height - textheight)//2textleft = (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 urlfrom 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))#drawdraw = ImageDraw.Draw(image)#创建一支笔text ='{}×{}'.format(width,height)textwidth, textheight =draw.textsize(text)if textwidth <width and textheight <height:texttop = (height - textheight)//2textleft = (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 hashlibimport os...from django.http import HttpResponse, HttpResponseBadRequestfrom 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.pytemplates/home.htmlstatic/site.css
路径多了写者会不方便,我们会使用django内置的os模块来处理它
import hashlibimport osimport sysfrom django.conf import settingsDEBUG=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 cachefrom django.core.urlresolvers import reversefrom django.core.wsgi import get_wsgi_applicationfrom django.http import HttpResponse, HttpResponseBadRequestfrom django.shortcuts import renderfrom 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 hashlibimport osimport sysfrom io import BytesIOfrom PIL import Image, ImageDrawfrom django.conf import settingsDEBUG = 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 formsfrom django.conf.urls import urlfrom django.core.cache import cachefrom django.core.urlresolvers import reversefrom django.core.wsgi import get_wsgi_applicationfrom django.http import HttpResponse, HttpResponseBadRequestfrom django.shortcuts import renderfrom django.views.decorators.http import etagclass 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))# drawdraw = ImageDraw.Draw(image) # 创建一支笔text = '{}×{}'.format(width, height)textwidth, textheight = draw.textsize(text)if textwidth < width and textheight < height:texttop = (height - textheight) // 2textleft = (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 contentdef 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_lineexecute_from_command_line(sys.argv)
现在,在命令行启动服务器
python placeholder.py runserver
打开浏览器访问localhost:8000,图示如下:
