[Django] 日本ひげ男協会のサイトを作成する。その4
注意
この記事は、id:SumiTomohiko:20070126:1169768661の続きです。
画像を表示する。
日本ひげ男協会のサイトでは、会員のひげの写真をアップロードして、みんなで自慢しあえるようにしたいと思っています。写真をアップロードする機能は既にあるので、今回は表示する機能を実装します。
まず始めに、ファイルをアップロードしたときのDjangoの挙動について説明します。django.db.models.ImageFieldオブジェクトをデータベースに追加したとき、データベースにはファイルのパスだけが登録されます。ファイル自体は、次の場所に保存されます。
<settings.pyのMEDIA_ROOT>/<ImageFieldのupload_to>/<ファイル名>
例えば、settings.pyに、
MEDIA_ROOT = '/home/tom/projects/juma/media/'
とあり、user/model.pyのUserクラスの定義で、
image = models.ImageField(blank=True, upload_to='image')
としていたとき、foo.jpgというファイルをアップロードしたら、ファイルは/home/tom/projects/juma/media/image/foo.jpgに保存されます。データベースに保存されるファイルのパスは、MEDIA_ROOTからの相対パスになります。先ほどの例の場合だと、/image/foo.jpgです。
ここで、アップロードしたファイルのパスを得るためには、get_<フィールド名>_urlメソッドを使用します。このメソッドは、ImageFieldクラスの親クラスであるFileFieldクラスのcontribute_to_classメソッドで、動的に定義されます(ファイルでいうと、/usr/lib/python2.4/site-packages/Django-0.95-py2.4.egg/django/db/models/fields/__init__pyにあります)。
def contribute_to_class(self, cls, name): super(FileField, self).contribute_to_class(cls, name) setattr(cls, 'get_%s_filename' % self.name, curry(cls._get_FIELD_filename, field=self)) setattr(cls, 'get_%s_url' % self.name, curry(cls._get_FIELD_url, field=self)) setattr(cls, 'get_%s_size' % self.name, curry(cls._get_FIELD_size, field=self)) setattr(cls, 'save_%s_file' % self.name, lambda instance, filename, raw_contents: instance._save_FIELD_file(self, filename, raw_contents)) dispatcher.connect(self.delete_file, signal=signals.post_delete, sender=cls)
このメソッドを使用すると、次の値が得られます。
<settings.pyのMEDIA_URL>/<MEDIA_ROOTからの相対パス>
先ほどの例でいうと、settings.pyに、
MEDIA_URL = '/user/media/'
とあった場合、Userクラスのget_image_urlメソッドを呼び出すと、/user/media/image/foo.jpgが得られます。ただし、Djangoがやってくれるのはここまでです。実際にファイルをブラウザに返すコードは、自分で実装することになります。
では、実装に入ります。まずURLを設計する必要があります。関わる設定が、settings.pyのMEDIA_URLと、user/mode.pyのUserクラスのImageFieldオブジェクトのupload_toの2つであることが、少しいやらしいです。今回はひげの画像の1種類しか扱いませんが、複数の種類のファイルを扱うようになった場合、
- ファイルを扱うURLは、ひとつにまとめたい。
- その下で、ファイルの種類ごとにディレクトリを分割したい。
と思ったので、上の例のままでいくことにしました。
ただし、このURL設計には問題があります。MEDIA_ROOTやMEDIA_URLがプロジェクトのルートディレクトリにあるsettings.pyで設定されていることから、ファイルは/userといったアプリケーションごとに管理するものではないと、Djangoでは設計されています。そのため、ファイルをダウンロードするURLが/user/mediaであり、userアプリケーションに任されるというのは、Djangoの設計思想と食い違います。もっとDjangoらしい解決方法があるのかもしれません(ファイルのダウンロードを行うmediaアプリケーションを追加しようともしたのですが、/mediaというURLはまた別のことに使われていたので、見送りました)。
さて、URLの設定は、user/urls.pyに追加します。
from django.conf.urls.defaults import * from juma.user.models import User urlpatterns = patterns('', (r'^add/$', 'juma.user.views.add'), (r'^list/$', 'django.views.generic.list_detail.object_list', { 'queryset': User.objects.all(), 'allow_empty': True, 'paginate_by': 20 }), (r'^media/image/(?P<file_name>[-\w\d_\.]+)$', 'juma.user.views.image'), ) # vim: tabstop=4 shiftwidth=4 expandtab
ビューuser/views.pyは、次のようになります。エラーの判定を除けば、単にファイルを読んで送り返しているだけです。
from django.http import HttpResponseForbidden, HttpResponse from django.http import HttpResponseRedirect, HttpResponseNotFound from django.shortcuts import render_to_response from juma.user.models import User import os.path import juma.user.manipulators import juma.settings (略) def image(request, file_name): if file_name.find("..") != -1: return HttpResponseForbidden("<h1>403 Forbidden</h1>") file_path = os.path.join(juma.settings.MEDIA_ROOT, "image", file_name) if not os.path.exists(file_path): return HttpResponseNotFound("<h1>404 Not Found</h1>") f = open(file_path, "r") s = f.read() f.close() return HttpResponse(s)
これで、画像の表示ができるようになりました。
日本語のファイル名を扱えるのかとか、画像のサイズ(容量)を制限しなくていいのかとか、画像が大きい場合は縮小して表示した方がいいのではないかとか、懸念される事項はありますが、いったん保留にして先に進むことにします。