HTTP自己是個無狀態的協議,就是說,每個到達服務器的請求都獨立於以前的請求。若是須要特定的狀態,則須要把它加到應用層上css
Django這樣的框架使用Cookie及其餘機制將統一客戶端發送的請求綁定在一塊兒。html
藉助在Django學習1中的模板,建立一個placeholder的新項目python
django-admin startproject placeholder --template=project_name
視圖與URL模式web
咱們用兩個視圖來生成響應,一個視圖用來按照請求的寬度高度渲染佔位圖像,另外一個用來渲染主頁面內容。正則表達式
def placeholder(request,width,height): # TODO : Rest of the view will go here return HttpResponse('OK') def index(request): return HttpResponse('Hello World')
咱們還要一個指向placeholder視圖的路由,包含兩個參數:width和height,Django的URL模式使用正則表達式來匹配輸入的URLdjango
# 關聯URL模式,捕捉的模式組會以位置參數的形式傳遞給視圖, # 命名的組會以關鍵字參數的形式傳遞,經過?P語法來捕獲被命名的組, # 並用[0-9]來匹配任意數字字符 urlpatterns = ( url(r'^image/(?P<width>[0-9]+)×(?P<height>[0-9]+)/',placeholder,name='placeholder'), url(r'^$',index,name='homepage'), )
完成模式的設置後,像**/image/30×25/的這樣的URL會被路由到placeholder中,並傳遞這些數值(width=30、height=50)。同時以homepage爲名稱與placeholder視圖新的路由一塊兒被加入index**路由上。瀏覽器
咱們說到佔位視圖接收兩個整數參數來設置圖片的高度和寬度。可是儘管正則表達式保證了高度寬度只包含整數,但它們還是以字符串的形式傳遞給視圖的。爲了確保在一個可管理的尺寸內,咱們用表單來實現驗證。緩存
典型的表單用於驗證POST和GET內容,它們也能夠用於驗證URL和cookie的特定值。服務器
from django.http import HttpResponse,HttpResponseBadRequest from django.conf.urls import url from django.core.wsgi import get_wsgi_application from django import forms # 視圖 class ImageForm(forms.Form): height = forms.IntegerField(min_value=1, max_value=2000) width = forms.IntegerField(min_value=1, max_value=2000) def placeholder(request,width,height): form = ImageForm({'height':height,'width':width}) 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 HttpResponseBadRequest('Invalid Image Request')
要生成實際的圖片,咱們還須要安裝Pillow模塊來處理圖片:cookie
pip install Pillow
經過Pillow建立圖片須要兩個參數,一個以元組表示的顏色模式和尺寸。
咱們在placeholder.py中的視圖使用RGB模式和在表單中處理過的數據尺寸
from io import BytesIO from PIL import Image 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'] 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({'height':height,'width':width}) if form.is_valid(): # 視圖調用from.generate 來獲取建立的圖片,圖片的字節被用於以後建立響應體 image = form.generate() return HttpResponse(image,content_type='image/png') else: return HttpResponseBadRequest('Invalid Image Request')
Image 中加入了一個新的generate方法,用於封裝建立圖片的邏輯,它接收一個參數來設置圖片格式,默認爲PNG,並以字節的形式返回圖片內容。
表單驗證圖片尺寸經過後,視圖會成功返回一張請求寬度和高度的PNG圖片。圖片內容不寫入磁盤,直接發送給客戶端。
咱們還可使用ImageDraw模塊在圖片中加入文字,如:
from PIL import Image,ImageDraw def generate(self,image_format='PNG'): height = self.cleaned_data['height'] width = self.cleaned_data['width'] image = Image.new('RGB',(width,height)) # 使用ImageDrawm模塊在圖片中加入文字 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地緩存工具。這會把內存開銷轉換爲緩存存儲,同時會節約生成圖片所需的CPU週期。
``` python
from django.core.cache import cache 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)) # 使用ImageDrawm模塊在圖片中加入文字 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標頭的etag修飾符。該修飾符接收一個參數,一個從請求和視圖參數中生成ETag標頭的函數。
import os import hashlib from django.views.decorators.http import etag # generate_etag是個新函數,接收placeholder視圖中的參數 # 使用hashlib來返回一個基於width和height值變化的不透明的ETag值 def generate_etag(request,width,height): content = 'Placeholder:{0}×{1}'.format(width,height) return hashlib.sha1(content.encode('utf-8')).hexdigest() # generate_etag函數會被髮送到placeholder視圖的etag修飾符中 @etag(generate_etag) def placeholder(request,width,height):
使用etag修飾符具備在視圖被訪問以前進行ETag計算的優點。
完成佔位視圖後,須要渲染一個基本的HTML模板。首先咱們加入Static和Template設置。讓Django能夠按照路徑找到模板和靜態資源。添加資源後的目錄應該是這樣的:
placeholder/ placeholder.py templates/ home.html static/ site.css
手動配置路徑時,爲了不對路徑的硬性編碼,使用Python標準庫中的os模塊
import os import hashlib import sys # 附加配置 DEBUG = os.environ.get('DEBUG','on') == 'on' # 隨機的祕鑰 SECRET_KEY = os.environ.get('SECRET_KEY','p*0c^!*uu70+vqq%y@xp66g*n8i9$au9$nx+d4p))6d_&kl@nb') 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', ), TEMPLATES=( { 'BACKEND':'django.template.backends.django.DjangoTemplates', 'DIRS':(os.path.join(BASE_DIR,'templates'),), }, ), STATICFILES_DIRS=( os.path.join(BASE_DIR,'static'), ), STATIC_URL = '/static/', )
最後,咱們加入簡單的主頁面模板和CSS文件,
** home.html**:
{% load staticfiles %} <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <title>Django Placeholder Images</title> <link rel="stylesheet" href="{% static 'site.css' %}" type="text/css"> </head> <body> <h1>Django Placeholder Images</h1> <p>This server can be used for serving placeholder images for any web page.</p> <p>To request a placeholder image of a given width and height simply include an image with the source pointing to <b>/image/<width>x<height>/</b> on this server such as:</p> <pre> <img src="{{ example }}" > </pre> <h2>Examples</h2> <ul> <li><img src="{% url 'placeholder' width=50 height=50 %}"></li> <li><img src="{% url 'placeholder' width=100 height=50 %}"></li> <li><img src="{% url 'placeholder' width=50 height=100 %}"></li> </ul> </body> </html>
site.css:
body{ text-align: center; } ul{ list-type:none; } li{ display: inline-block; }
固然,咱們要在placeholder.py中更新index視圖來渲染這個模板。
from django.core.urlresolvers import reverse from django.shortcuts import render #更新index視圖來渲染模板 def index(request): # 更新過的index視圖經過翻轉placeholder視圖來建立一個URL樣例 # 並將它傳給模板上下文 example = reverse('placeholder',kwargs={'width':50,'height':50}) context = { 'example':request.build_absolute_uri(example) } # 經過render快捷方式來渲染home.html模板 return render(request,'home.html',context)
運行
最後運行完整的項目查看建立的佔位圖片服務
python placeholder.py runserver
結果:
應用在python3環境下順利跑通,在python2.7下會遇到字符編碼問題