- 更新
2013-11-05
- バージョン
0.1.7
- 作者
@voluntas
- URL
Django 前提
- 画像のアップロード先を S3 にして欲しい
- アップロードと同時にサムネイルを生成して欲しい
- サムネイル生成は非同期であって欲しい
この 3 つの願いはよくある話なのではないでしょうか。
この辺の処理がまとまってるのが見つけられなかったのでまとめてみました。
Django 前提
- S3 アップロードには django-storages を使う
- サムネイル生成には django-imagekit を使う
- 非同期処理には django-celery を使う
- Celery のキューには Redis を使う
これらの 3 つを組み合わせることで画像を S3 にアップロードし、 非同期にサムネイルを生成するという処理を実現させます。
必須(Pillow は後ほど):
$ pip install django django-celery django-imagekit django-storages boto redis celery
オプション:
$ pip install flower
flower は Celery でキューを Web UI から見れる便利なツール。ただし簡易的なのであれば celery events で見られる。
Pillow はイメージ変換ライブラリだが、外部ライブラリに依存するので、要注意。
今回は JPEG のサポートが出来れば良い。
macports:
$ sudo port install libjpeg-turbo
$ pip install pillow
...
--------------------------------------------------------------------
PIL SETUP SUMMARY
--------------------------------------------------------------------
version Pillow 2.2.1
platform darwin 2.7.5 (default, Aug 1 2013, 01:01:17)
[GCC 4.2.1 Compatible Apple Clang 4.1 ((tags/Apple/clang-421.11.66))]
--------------------------------------------------------------------
--- TKINTER support available
--- JPEG support available
--- ZLIB (PNG/ZIP) support available
*** TIFF G3/G4 (experimental) support not available
--- FREETYPE2 support available
*** LITTLECMS support not available
*** WEBP support not available
*** WEBPMUX support not available
--------------------------------------------------------------------
...
CentOS 6.4:
$ sudo yum install libjpeg-turbo-devel
$ pip install pillow
...
--------------------------------------------------------------------
PIL SETUP SUMMARY
--------------------------------------------------------------------
version Pillow 2.2.1
platform linux2 2.7.5 (default, Nov 5 2013, 00:30:50)
[GCC 4.4.7 20120313 (Red Hat 4.4.7-3)]
--------------------------------------------------------------------
*** TKINTER support not available
--- JPEG support available
--- ZLIB (PNG/ZIP) support available
*** TIFF G3/G4 (experimental) support not available
*** FREETYPE2 support not available
*** LITTLECMS support not available
*** WEBP support not available
*** WEBPMUX support not available
--------------------------------------------------------------------
...
aptitude:
$ sudo ...
$ pip freeze
Django==1.5.5
Pillow==2.2.1
amqp==1.0.13
anyjson==0.3.3
billiard==2.7.3.34
boto==2.15.0
celery==3.0.24
django-appconf==0.6
django-celery==3.0.23
django-imagekit==3.0.4
django-storages==1.1.8
kombu==2.5.16
pilkit==1.1.5
python-dateutil==2.2
pytz==2013.7
redis==2.8.0
six==1.4.1
wsgiref==0.1.2
まずは django-storages を使って ImageField や FileField を使ったデータを S3 に上がるようにします。
settings.py に以下の設定はが必要になります。
# ストレージを boto を使った S3 に指定します
DEFAULT_FILE_STORAGE = 'storages.backends.s3boto.S3BotoStorage'
# https を有効にします
AWS_S3_SECURE_URLS = True
# 認証クエリーを無効にします
AWS_QUERYSTRING_AUTH = False
# アクセスキーを指定します
AWS_ACCESS_KEY_ID = ''
# シークレットキーを指定します
AWS_SECRET_ACCESS_KEY = ''
# バケット名を指定します
AWS_STORAGE_BUCKET_NAME = ''
この設定をすることで後は普通にアップロードすることで S3 にファイルが置かれるようになります。
メディアファイルは /media/ で、static ファイルは /static/ から始まるようにしたい場合は以下のようにします。
s3.py というファイルを作り location を切り換えるようにします。
from storages.backends.s3boto import S3BotoStorage
StaticRootS3BotoStorage = lambda: S3BotoStorage(location='static')
MediaRootS3BotoStorage = lambda: S3BotoStorage(location='media')
settings.py で s3.py に設定した値を読み込むようにしましょう。
DEFAULT_FILE_STORAGE = 'app.s3.MediaRootS3BotoStorage'
STATICFILES_STORAGE = 'app.s3.StaticRootS3BotoStorage'
この設定をすることで、アップロードした画像は media へ、 元々用意してた静的ファイルは /static/ から呼ばれるようになります。
django-imagekit は画像処理ライブラリですが、よく出来ているので簡単に使えます。
models.py に追加する場合は ImageSpecField を使います。
from django.db import models
from imagekit.models import ImageSpecField
from imagekit.processors import ResizeToFill
class Entry(models.Model):
title = models.CharField(max_length=255)
img = models.ImageField(upload_to='entry/%Y%m%d')
img_thumbnail = ImageSpecField(source='img',
processors=[ResizeToFill(100, 50)],
format='JPEG',
options={'quality': 60})
- source は サムネイル変換 対象フィールドを指定します
- processors は 実際に変換する処理 を指定します
- format は 画像の形式 を指定します
- options はそれ以外の設定を指定します
この書き方は ImageSpec と呼ばれる「変換処理」を直接フィールドに指定する方法ですが、 やはり変換処理自体は色々まとめて色々な場面で呼べるようにしたいと考えると思います。
汎用化する場合は ImageSpec を継承したクラスを作ります。 この場合は ImageSpecField には id= で登録した名前を指定します。
from django.db import models
from imagekit import ImageSpec, register
from imagekit.models import ImageSpecField
from imagekit.processors import ResizeToFill
class Entry(models.Model):
title = models.CharField(max_length=255)
img = models.ImageField(upload_to='entry/%Y%m%d')
img_thumbnail = ImageSpecField(source='img',
id='core:profile:image_thumbnail')
class ImageThumbnail(ImageSpec):
processors = [ResizeToFill(100, 50)]
format = 'JPEG'
options = {'quality': 60}
register.generator('core:profile:image_thumbnail', ImageThumbnail)
これを使う事で画像変換処理を綺麗にまとめておくことが出来る用になります。
django-imagekit はデフォルトで Celery に対応しています。
settings.py の IMAGEKIT_DEFAULT_CACHEFILE_BACKEND を imagekit.cachefiles.backends.Async に切り換えるだけで対応が可能です。
# デフォルトが imagekit.cachefiles.backends.Simple なので Async に切り換えます
IMAGEKIT_DEFAULT_CACHEFILE_BACKEND = 'imagekit.cachefiles.backends.Async'
import djcelery
djcelery.setup_loader()
# ローカルの redis をブローカーに使った例
BROKER_URL = 'redis://localhost:6379/0'
あとは celery worker を起動すれば動きます。
デフォルトのキャッシュファイル戦略が imagekit.cachefiles.strategies.JustInTime となっているため、画像を表示されたタイミングに「いつも」生成します。もちろん既存のデータがあれば生成はしませんが「既存のデータがあるかどうか」も確認が発生します。
そこで変換処理をファイルのアップロード時限定にする戦略に切り換えることが出来ます。
imagekit.cachefiles.strategies.Optimistic を指定することで処理が保存時のみにすることが可能です。
IMAGEKIT_DEFAULT_CACHEFILE_STRATEGY = 'imagekit.cachefiles.strategies.Optimistic'
キャッシュディレクトリはデフォルトでは /CACHE/images/ が指定されます。
何か意図的に変更したい場合に使います。
IMAGEKIT_CACHEFILE_DIR = ...
- ジェネリックビューは使ってない
- HTML は凄く適当
project/core/views.py
# coding=utf8
from django.shortcuts import render, redirect
from django.views.decorators.http import require_GET, require_http_methods
from .models import Entry
from .forms import EntryForm
@require_GET
def home(request):
entries = Entry.objects.all()
return render(request, 'core.html', {'entries': entries})
@require_http_methods(["GET", "POST"])
def upload(request):
if request.method == 'POST':
form = EntryForm(request.POST, request.FILES)
if form.is_valid():
form.save()
return redirect('/')
else:
form = EntryForm()
return render(request, 'core.html', {'form': form})
project/core/models.py
# coding=utf8
from django.db import models
from imagekit import ImageSpec, register
from imagekit.models import ImageSpecField
from imagekit.processors import ResizeToFill
class Entry(models.Model):
title = models.CharField(max_length=255)
img = models.ImageField(upload_to='entry/%Y%m%d')
img_thumbnail = ImageSpecField(source='img',
id='core:profile:image_thumbnail')
class Meta:
ordering = ('title', )
def __unicode__(self):
return self.title
class ImageThumbnail(ImageSpec):
processors = [ResizeToFill(100, 50)]
format = 'JPEG'
options = {'quality': 60}
register.generator('core:profile:image_thumbnail', ImageThumbnail)
project/core/forms.py
# coding=utf8
from django import forms
from .models import Entry
class EntryForm(forms.ModelForm):
class Meta:
model = Entry
project/core/templates/core/home.html
{% for entry in entries %}
<div><img src="{{ entry.img_thumbnail.url }}"><br></div>
{% endfor %}
project/core/templates/core/upload.html
<form action="/upload" method="post" enctype="multipart/form-data">{% csrf_token %}
{% for field in form %}
<div >
{{ field.errors }}
{{ field.label_tag }}: {{ field }}
</div>
{% endfor %}
<p><input type="submit" value="アップロード" /></p>
</form>
project/core/settings.py
INSTALLED_APPS = (
...
'djcelery',
'imagekit',
'storages',
'core',
)
DEFAULT_FILE_STORAGE = 'core.s3.MediaRootS3BotoStorage'
STATICFILES_STORAGE = 'core.s3.StaticRootS3BotoStorage'
AWS_S3_SECURE_URLS = True
AWS_QUERYSTRING_AUTH = False
AWS_ACCESS_KEY_ID = ''
AWS_SECRET_ACCESS_KEY = ''
AWS_STORAGE_BUCKET_NAME = ''
IMAGEKIT_DEFAULT_FILE_STORAGE = DEFAULT_FILE_STORAGE
IMAGEKIT_DEFAULT_CACHEFILE_BACKEND = 'imagekit.cachefiles.backends.Async'
IMAGEKIT_DEFAULT_CACHEFILE_STRATEGY = 'imagekit.cachefiles.strategies.Optimistic'
import djcelery
djcelery.setup_loader()
BROKER_URL = 'redis://localhost:6379/0'
project/core/s3.py
# coding=utf8
from storages.backends.s3boto import S3BotoStorage
StaticRootS3BotoStorage = lambda: S3BotoStorage(location='static')
MediaRootS3BotoStorage = lambda: S3BotoStorage(location='media')
project/core/urls.py
# coding=utf8
from django.conf.urls import patterns, url
urlpatterns = patterns('',
url(r'^$', 'core.views.home', name='home'),
url(r'^upload$', 'core.views.upload', name='upload'),
)
アプリを起動する:
$ python manage.py runserver
redis サーバを立てる:
$ redis-server
celery ワーカを起動する:
$ python manage.py celery worker -E
flower をインストールしている場合:
$ python manage.py celery flower