[关闭]
@hpp157 2016-08-22T05:01:27.000000Z 字数 12114 阅读 2754

Django学习 第二章 无状态应用

django python


本章源代码已经上传到github,需要参考的可以下载:download

大多数django的教程集中在一些UGC程序的开发开发上,比如to
-do lists
,博客,cms等,考虑到它最初是被开发来用于管理劳伦斯出版集团旗下的一些以新闻内容为主的网站的你可能就不会太奇怪了。
2005年,django于美国肯萨斯州的劳伦斯集团的World Online上发布,它可以使记者非常快速的把自己的内容发布到web网站上。从那时候开始django被一些出版机构比如美国《华盛顿邮报》,英国《卫报》,《PolitiFact》以及美国著名报纸《the onion》。从这个角度看,可能会给你一种django的目的是用来内容出版,或者本身是一个cms系统的印象。然而随着后来美国的NASA开始采用django作为它们的框架选择,django明显已经超出了最初设计目的。
上一章,我们仅仅用djangohttp处理以及URL 路由写了一个小型项目。本章会继续扩展这个项目,使用django的工具如,input 验证缓存模板创建一个无状态的Web应用。

为什么无状态(stateless)

HTTP本身是一个无状态的协议,每一个个从服务器接收到的请求都是独立于先前的请求。如果特别需要一个状态,需要添加到应用层,django使用cookies和其他机制把每个独立的请求绑定到一个相同的客户端上。
随着session持久化存储在服务器上,应用然后可以处理任务,比如通过请求来处理用户验证。这给分布式服务器统一的状态读取和写入带来了许多挑战。
你可以想象,一个无状态的应用,不用维护在一台服务器上的一致性状态。如果其他用户身份验证是必须的,那么它们必须通过客户端发送到每一个请求上面,无状态应用让伸缩(scaling),缓存(caching),负载均衡(load balancing)更容易。可以参考一下下面这篇文章了解一下无状态的概念:
对于REST中无状态(stateless)的一点认识

app复用和服务组件化

django社区聚焦于可以安装和配置到任何django项目中的可复用应用。可是,不同组件组成的大型应用通常有非常复杂结构。
一种解决方法是把复杂的网站按服务拆分成不同的组件——也就是说,一个可以和另一个服务交流的小服务。这并是说它们之间就不能共用代码了,而是每个服务可以独立安装和配置的意思。
无状态组件,像REST APIs可以把django项目分开,变成独立的组件服务。接下来我们将给第一章的例子加上一个placeholder image server,使用我们刚学过的服务组件化知识来做

图像占位符组件

atartproject命令来创建一个叫做placeholder的项目,项目模板用第一章创建的

  1. django-admin.py startproject placeholder --template=project_name

这会产生一个placeholder.py的文件让我们开始我们的项目,这个文件内容如下:

  1. #!/usr/bin/env python
  2. import sys
  3. from django.conf import settings
  4. DEBUG=os.environ.get('DEBUG','on') =='on'
  5. SECRET_KEY =os.environ.get('SECRET','%jv_4#hoaqwig2gu!eg#^ozptd*a@88u(aasv7z!7xt^5(*i&k')
  6. ALLOWED_HOSTS=os.environ.get('ALLOWED_HOSTS','localhost').split('.')
  7. settings.configure(
  8. DEBUG = DEBUG,
  9. SECRET_KEY =SECRET_KEY,
  10. ALLOWED_HOSTS=ALLOWED_HOSTS,
  11. ROOT_URLCONF = __name__,
  12. MIDDLEWARE_CLASSES=(
  13. 'django.middleware.common.CommonMiddleware',
  14. 'django.middleware.csrf.CsrfViewMiddleware',
  15. 'django.middleware.clickjacking.XFrameOptionsMiddleware'
  16. ),
  17. )
  18. from django.conf.urls import url
  19. from django.core.wsgi import get_wsgi_application
  20. from django.http import HttpResponse
  21. def index(request):
  22. return HttpResponse('hello,world')
  23. urlpatterns=(
  24. url(r'^$',index),
  25. )
  26. application = get_wsgi_application()
  27. #manage.py的功能
  28. if __name__ == '__main__':
  29. from django.core.management import execute_from_command_line
  30. execute_from_command_line(sys.argv)

views

由于应用比较简单,我们只需要两个视图来提供我们的响应,第一个视图会根据请求中的宽和高来渲染产生图像占位符palceholder view。另一个(homepage view)渲染首页内容,用来阐释项目怎么工作,以及渲染几个成品,因为我们在使用atartproject命令来创建项目的时候使用了template标签,因此,placeholder.py中会自动添加一个首页(下面代码),我们在稍后会使用它。

  1. ...
  2. def placeholder(request,width,height):
  3. #todo :rest of the view will go here
  4. return HttpResponse('ok')
  5. def index(request):
  6. return HttpResponse('hello world')
  7. ...

有了简单的palceholder视图,我们现在应该考虑接下来我们项目要显示的URL结构

URL模式

placeholder视图后面应该跟上两宽和高参数,正如先前提到的,这两个参数将会被URL捕获并且传递给视图。由于参数只能是整数,我们应该通过URL确保参数为整数,django中用正则表达式来匹配输入的url,我们将会很方便的处理这些参数。
捕获模式组(captured pattern groups)作为位置参数传递给view,命名组(named groups)作为关键字参数传递,用?P语法来捕获( 使用命名组,能够使代码更加清晰,在做一些复杂模块的时候,只需看一下URLconf就大概知道怎么回事了。)。任意的一个数字字符用[0-9].匹配多个用[0-9]+下面是匹配的规则:

  1. urlpatterns =(
  2. #localhost/user/image/30×25/
  3. url(r'^image/(?P<width>[0-9]+)×(?P<height>[0-9]+)/$'
  4. )

现在,首页homepage已经被添加到index了,我们也会看到怎样命名url模式,为了更好的练习,我们会在稍后创建一个templates

placehold view

根据原始的http的请求,placeholder view应该接收两个整数参数,作为图像的宽和高。正则表达式可以确保高和宽由数字组成,传递给视图过程中会变为字符串,view需要将它们再次转换成整数,并且验证图像的尺寸。我们可以通过djangoforms模块来轻松的处理用户输入

forms表单常常用来检验POSTGET内容,但是它也可应用来检验url的特殊值,或者存储在cookies中的值,这儿有一个使用forms检验图像的宽和高的的简单例子

  1. ...
  2. from django import forms
  3. from django.cof.urls import url
  4. class ImageForm(forms.Form):
  5. height = forms.IntegerField(min_value=1,max_value=2000)
  6. weight = forms.IntegerField(min_value=1,max_value=2000)
  7. def palceholder(request,width,height):
  8. ...

如你看到的,view先验证请求的图像尺寸,如果尺寸有效,widthheight会被加上clean_data属性。到这儿,宽和高会被转换成整数,并且 view可以保证宽和高的值在1到2000之间。我们还需要一个forms的错误信息的发送的功能,如果尺寸不正确的话可以发送错误信息。

  1. ...
  2. from django import forms
  3. from django.conf.urls import url
  4. from django.core.wsgi import get_wsgi_application
  5. from django.http import HttpRespose, HttpResponseBadRequest
  6. class ImageForm(forms.Form):
  7. height = forms.IntegerField(min_value=1,max_value=2000)
  8. weight = forms.IntegerField(min_value=1,max_value=2000)
  9. def placeholder(request,width,height):
  10. form = ImageForm({'width':width,'height':height })
  11. if form.is_valid():
  12. height = form.cleaned_data['height']
  13. width = form.cleaned_data['width']
  14. #TODO :generate image of requested size这里要编写生成满足宽和高条件的图像,先空着
  15. return HttpResponse('ok')
  16. else:
  17. return HttpResponse('Invalid Image Request')

如果图像尺寸验证失败,view会给客户端发送一个错误响应。这里的HttpResponseBadRequestHttpResponse的子类,发送一个404错误响应

图像处理

view现在有能力接收和处理客户端请求的宽和高了。但是还不能生成实际图像,要在python中处理图像的话,你需要安装Pillow模块
默认的,Pillow的安装要从源码编译,如果你的环境没有安装编译器,可能会安装失败,遵从其官网的安装说明安装,这儿有个官网的链接Pillow安装
pillow创建图像需要两个参数,颜色模式和一个尺寸组成的tuple,还有第三个可选参数,是用来设置创建图像的颜色的,默认创建的图像是黑色的

  1. ...
  2. from io import BytesIO
  3. from PIL import Image
  4. class ImageForm(forms.Form):
  5. height = forms.IntegerField(min_value=1,max_value=2000)
  6. weight = forms.IntegerField(min_value=1,max_value=2000)
  7. def generate(self,image_format='PNG'):
  8. height = self.cleaned_data['height']
  9. width = self.cleaned_data['width']
  10. #create image
  11. image = Image.new('RGB',(width,height))
  12. content = BytesIO()
  13. image.save(content,image_format)
  14. content.seek(0)
  15. return content
  16. def placeholder(request,width,height):
  17. form = ImageForm({'width':width,'height':height })
  18. if form.is_valid():
  19. image=form.generate()
  20. return HttpResponse(image,content_type='image/png')
  21. else:
  22. return HttpResponse('Invalid Image Request')

ImageForm类中添加了一个generate方法,里面封装了生成图像的方法逻辑。这个方法需要一个参数来表示图像格式,默认是PNG,并且以字节的形式返回图像内容,
调用该方法用from.generate()来生成实际的图像,图像是发送给了客户端,并没有写入磁盘。
有一点需要改进,就是图像生成的是纯黑色的图片,没有尺寸信息,这点不太好。我们可以用pillowImageDraw模块给图片添加描述信息(palceholder)。

  1. ...
  2. from PIL import Image,ImageDraw
  3. ...
  4. class ImageForm(forms.Form):
  5. def generate(self,image_format='PNG'):
  6. height = self.cleaned_data['height']
  7. width = self.cleaned_data['widht']
  8. image = Image.new('RGB',(width,height))
  9. #draw
  10. draw = ImageDraw.Draw(image)#创建一支笔
  11. text ='{}×{}'.format(width,height)
  12. textwidth, textheight =draw.textsize(text)
  13. if textwidth <width and textheight <height:
  14. texttop = (height - textheight)//2
  15. textleft = (width - textwidth)//2
  16. #开始写描述信息了
  17. draw.text((textleft,texttop),text,fill=(255,255,255))
  18. content=BytesIO()
  19. image.save(content,image_format)
  20. content.seek(0)
  21. return content
  22. ...

添加缓存(caching)

考虑一下,如果每次访问都生成一次图像(图像没有改变情况下),好像有点浪费资源。
避免重复的解决方法就是使用缓存,当你决定给项目增加缓存时有两个选择,服务端和客户端,服务端缓存可以使用django的缓存工具,方法如下:

  1. ...
  2. from django.conf.urls import url
  3. from django.core.cache import cache
  4. ...
  5. class ImageForm(forms.Form):
  6. def generate(self,image_format='PNG'):
  7. height = self.cleaned_data['height']
  8. width = self.cleaned_data['widht']
  9. key = '{}.{}.{}'.format(width,height,image_format)
  10. content = cache.get(key)
  11. if content is None:
  12. image = Image.new('RGB',(width,height))
  13. #draw
  14. draw = ImageDraw.Draw(image)#创建一支笔
  15. text ='{}×{}'.format(width,height)
  16. textwidth, textheight =draw.textsize(text)
  17. if textwidth <width and textheight <height:
  18. texttop = (height - textheight)//2
  19. textleft = (width - textwidth)//2
  20. #开始写描述信息了
  21. draw.text((textleft,texttop),text,fill=(255,255,255))
  22. content=BytesIO()
  23. image.save(content,image_format)
  24. content.seek(0)
  25. cache.set(key,content,60*60)
  26. return content

而另一种客户端缓存是使用浏览器内置的缓存,django使用装饰器etag来实现:

  1. import hashlib
  2. import os
  3. ...
  4. from django.http import HttpResponse, HttpResponseBadRequest
  5. from django.views.decorators.http import etag
  6. ...
  7. def generate_etag(request,width,height):
  8. content = 'Placeholder:{0}×{1}'.formate(width,height)
  9. return hashlib.sha1(content.encode('utf-8')).hexdigest()
  10. @etag(generate_etag)
  11. def placeholder(request,widht,height):
  12. ...

这样placeholder视图就完成了,接下来我们回去把 homepage视图完成,然后整个项目就完成了

创建首页视图

django的模板加载器,会自动发现已安装的app项目里的template和静态资源。不过需要你设置模板的路径TEMPLATE_DIRS和静态资源路径STATICFILES_DIRS等,django.contrib.staticfiles也需要添加到INSTALLED_APPS来使{% static %}这样的命令生效。
目前你的项目目录是这样:

  1. placeholder/
  2. placeholder.py

静态资源的目录要和placeholder.py在同级目录

  1. placeholder/
  2. placeholder.py
  3. templates/
  4. home.html
  5. static/
  6. site.css

路径多了写者会不方便,我们会使用django内置的os模块来处理它

  1. import hashlib
  2. import os
  3. import sys
  4. from django.conf import settings
  5. DEBUG=os.environ.get('DEBUG','on') =='on'
  6. SECRET_KEY =os.envrion.get('SECRET','%jv_4#hoaqwig2gu!eg#^ozptd*a@88u(aasv7z!7xt^5(*i&k')
  7. #设置项目根路径
  8. BASE_DIR =os.path.dirname(__file__)
  9. settings.configure(
  10. DEBUG = DEBUG,
  11. SECRET_KEY =SECRET_KEY,
  12. ALLOWED_HOSTS=ALLOWED_HOSTS,
  13. ROOT_URLCONF = __name__,
  14. MIDDLEWARE_CLASSES=(
  15. 'django.middleware.common.CommonMiddleware',
  16. 'django.middleware.csrf.CsrfViewMiddleware',
  17. 'django.middleware.clickjacking.XFrameOptionsMiddleware'
  18. ),
  19. #模板设置
  20. INSTALLED_APPS=(
  21. 'django.contrib.staticfiles',
  22. ),
  23. TEMPLATE_DIRS=(
  24. os.path.join(BASE_DIR,'templates'),
  25. ),
  26. STATICFILES_DIRS=(
  27. os.path.join(BASE_DIR,'static'),
  28. ),
  29. STATIC_URL='/static/',
  30. )

首页模板和css文件

首页的目的是为了演示程序怎么使用的,包括说明文档和例子

  1. {% load staticfiles %}
  2. <!DOCTYPE html>
  3. <html lang="en">
  4. <head>
  5. <meta charset="UTF-8">
  6. <meta name="viewport"
  7. content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
  8. <meta http-equiv="X-UA-Compatible" content="ie=edge">
  9. <link rel="stylesheet" type="text/css" href="{% static 'site.css' %}">
  10. <title>Document</title>
  11. </head>
  12. <body>
  13. <h1>Django Placeholder Images</h1>
  14. <p>This server can be used for serving placeholder images for webpage</p>
  15. <h2>Examples</h2>
  16. <ul>
  17. <li><img src="{% url 'placeholder' width=50 height=50 %}" alt=""></li>
  18. <li><img src="{% url 'placeholder' width=100 height=50 %}" alt=""></li>
  19. <li><img src="{% url 'placeholder' width=50 height=100 %}" alt=""></li>
  20. </ul>
  21. </body>
  22. </html>

下面是文件site.css中添加的样式:

  1. body{
  2. text-align: center;
  3. }
  4. ul{
  5. list-style-type: none;
  6. }
  7. li{
  8. display: inline-block;
  9. }

最后,我们需要更新placeholder.py的首页视图view,渲染这个模板:

  1. ...
  2. from django.core.cache import cache
  3. from django.core.urlresolvers import reverse
  4. from django.core.wsgi import get_wsgi_application
  5. from django.http import HttpResponse, HttpResponseBadRequest
  6. from django.shortcuts import render
  7. from django.views.decorators.http import etag
  8. ...
  9. def index(request):
  10. example = reverse('placeholder', kwargs={'width': 50, 'height': 50})
  11. context = {
  12. 'example': request.build_absolute_uri(example)
  13. }
  14. return render(request, 'home.html', context)

项目完成

最终项目文件如下:

  1. import hashlib
  2. import os
  3. import sys
  4. from io import BytesIO
  5. from PIL import Image, ImageDraw
  6. from django.conf import settings
  7. DEBUG = os.environ.get('DEBUG', 'on') == 'on'
  8. SECRET_KEY = os.environ.get('SECRET_KEY', '@up*9j#$0$bza-38l!id=79!at!j@wg%!2%2ur!n&2q07jl2%0 ')
  9. ALLOWED_HOSTS = os.environ.get('ALLOWED_HOSTS', 'localhost').split('.')
  10. BASE_DIR = os.path.dirname(__file__)
  11. settings.configure(
  12. DEBUG=DEBUG
  13. , SECRET_KEY=SECRET_KEY
  14. , ROOT_URLCONF=__name__
  15. , MIDDLEWARE_CLASSES=(
  16. 'django.middleware.common.CommonMiddleware',
  17. 'django.middleware.csrf.CsrfViewMiddleware',
  18. 'django.middleware.clickjacking.XFrameOptionsMiddleware'
  19. )
  20. , INSTALLED_APPS=(
  21. 'django.contrib.staticfiles',
  22. 'django.contrib.contenttypes',
  23. 'django.contrib.auth',
  24. )
  25. , TEMPLATE_DIRS=(
  26. os.path.join(BASE_DIR, 'templates'),
  27. )
  28. , STATICFILES_DIRS=(
  29. os.path.join(BASE_DIR, 'static'),
  30. )
  31. , STATIC_URL='/static/',
  32. )
  33. from django import forms
  34. from django.conf.urls import url
  35. from django.core.cache import cache
  36. from django.core.urlresolvers import reverse
  37. from django.core.wsgi import get_wsgi_application
  38. from django.http import HttpResponse, HttpResponseBadRequest
  39. from django.shortcuts import render
  40. from django.views.decorators.http import etag
  41. class ImageForm(forms.Form):
  42. height = forms.IntegerField(min_value=1,max_value=2000)
  43. width =forms.IntegerField(min_value=1,max_value=2000)
  44. def generate(self, image_format='PNG'):
  45. height = self.cleaned_data['height']
  46. width = self.cleaned_data['width']
  47. key = '{}.{}.{}'.format(width, height, image_format)
  48. content = cache.get(key)
  49. if content is None:
  50. image = Image.new('RGB', (width, height))
  51. # draw
  52. draw = ImageDraw.Draw(image) # 创建一支笔
  53. text = '{}×{}'.format(width, height)
  54. textwidth, textheight = draw.textsize(text)
  55. if textwidth < width and textheight < height:
  56. texttop = (height - textheight) // 2
  57. textleft = (width - textwidth) // 2
  58. # 开始写描述信息了
  59. draw.text((textleft, texttop), text, fill=(255, 255, 255))
  60. content = BytesIO()
  61. image.save(content, image_format)
  62. content.seek(0)
  63. cache.set(key, content, 60 * 60)
  64. return content
  65. def generate_etag(request, width, height):
  66. content = 'Placeholder:{0}×{1}'.format(width, height)
  67. return hashlib.sha1(content.encode('utf-8')).hexdigest()
  68. @etag(generate_etag)
  69. def placeholder(request, width, height):
  70. form = ImageForm({'width': width, 'height': height})
  71. if form.is_valid():
  72. image = form.generate()
  73. return HttpResponse(image, content_type='image/png')
  74. else:
  75. return HttpResponse('Invalid Image Request')
  76. def index(request):
  77. example = reverse('placeholder', kwargs={'width': 50, 'height': 50})
  78. context = {
  79. 'example': request.build_absolute_uri(example)
  80. }
  81. return render(request, 'home.html', context)
  82. urlpatterns = (
  83. url(r'^image/(?P<width>[0-9]+)×(?P<height>[0-9]+)/$',
  84. placeholder, name='placeholder'),
  85. url(r'^$', index, name='homepage'),
  86. )
  87. application = get_wsgi_application()
  88. if __name__ == '__main__':
  89. from django.core.management import execute_from_command_line
  90. execute_from_command_line(sys.argv)

现在,在命令行启动服务器

  1. python placeholder.py runserver

打开浏览器访问localhost:8000,图示如下:

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注