あったらしくるえるはてなくしょん

id:kskmeuk あったらしく

dJango-storages で、S3に画像ファイルを保存

python の django を Amazon ec2 で複数台構成とってるときに画像のupload を考えると... - くるえるはてなくしょん
ちょっと空いてしまいましたが、続きです。
AmazonAWS のサービス群のなかで、EC2もすごいところはあるんですけど、S3 のほうがすごいかな..と最近は思っています。
それで、例えばWebサービスでupload されたものを S3 に保存しておくと、サーバを複数台にして分散させた時でも、共有できるのでちょっと楽だったりします。

それで、前回のエントリで、

id:nullpobug 2010/04/09 01:30
rsyncなどで動機するのでも良いと思います。
DjangoのStorageAPIを使ってS3にアップロードするようにする方法もあります。
http://code.welldev.org/django-storages/
python の django を Amazon ec2 で複数台構成とってるときに画像のupload を考えると... - くるえるはてなくしょん

こういうコメントを頂けると本当にうれしいですね。ありがとうございました。おかげさまでかなり使えました。


そこで、welldev.org を試しに設定してみたら、良い事と悪い事があったという話です。悪い事は挽回できそうになってきましたが。
環境は Ubuntu 9.04 Python 2.6.4 Django 1.1 でした。

django-storages

easy_install でもインストールが可能でした。インストールして、welldev.org ここの設定に従って、setting.py を編集します。冒頭の DEFAULT_FILE_STORAGE だけ間違えないように要注意でしょうか。

Fields

Once you're done, default_storage will be the S3 storage:

>>> from django.core.files.storage import default_storage
>>> print default_storage.__class__

ここが、たしか、私の環境では、 とかでかえって来てたような気もしますが*1 、取り急ぎは動きます。

ついで、実験用には同じく、Storage のsection のテストをちょっとやってみるのがおすすめです。同時に、S3のほうで、storage_test が出来てたらOK。私でも出来たからきっと大丈夫w

Storage

Standard file access options are available, and work as expected:

>>> default_storage.exists('storage_test')
False
>>> file = default_storage.open('storage_test', 'w')
>>> file.write('storage contents')
>>> file.close()

>>> default_storage.exists('storage_test')
True
>>> file = default_storage.open('storage_test', 'r')
>>> file.read()
'storage contents'
>>> file.close()

>>> default_storage.delete('storage_test')
>>> default_storage.exists('storage_test')
False

これで、upload したものが S3 まで跳んで行きます。EC2 使ってるとS3とのアクセスも速いのでその当たりは尚快適ですね。EC2 若干危なっかしいですが、S3はちょっとくせがあるけど、手っ取り早くて楽しいですね。

ImageField の動きがちょっとヘンになるかもしれないが、私たちにはPILがある!!

まだきちんと調べていないのですが、Django の ImageField で、height_field や width_field を設定するとエラーになったので、そこはdefault_storageを変更したせいかもしれませんでした。ちょっと追っかけてちゃんと調べたいのですが、いろいろ時間がなくてorz。かなしい。
それから、よく画像がupされたときに、resize して、サムネイル用の画像とかをとっておくとかもやると思うんだけれど、そこの途中で今日は力つきてしまいました。
upload されたファイルを、一度保存。再度読み出して、PIL の Image にしておいて、画像編集のところまでは、たぶん出来たのだけど、今度、その画像を、改めてImageField にかえすところが今日はうまく書けなかった。ここが最後の難関かな...

で、さきほど、唐突に直せました。StringIO() つくっておいて、PILの画像を一度そこに保存して、そこから getvalue() するとok なんですね。

photo という、ImageField を持つ、Photo(models.Model) で、PhotoForm(ModelForm) とかだとすると、ちょいとまだ冗長なんですが、以下で、とりあえず、上がったものを一度保存して、それをリサイズして保存とかができました。
ここに、容量チェックや中身チェックを入れるとokかな。

def upload(request):
    form = PhotoForm(request.POST or None, request.FILES)
    if request.method == "POST":
        if form.is_valid():
             image = Photo()
             imageobj = form.cleaned_data["photo"]
             name = imageobj.name
             message = name + str(imageobj.size)
             image.photos.save(imageobj.name,imageobj)
             import PIL
             import StringIO
             from django.core.files.base import ContentFile
             data = image.photos.read()
             #p = PIL.ImageFile.Parser()
             #p.feed(data)
             #virtualfile = PIL.ImageFile._ParserFile(p.data)  
             #im = PIL.Image.open(virtualfile)
             #im = p.close()
             im = PIL.Image.open(StringIO.StringIO(data))
             im = im.resize((128,128),PIL.Image.ANTIALIAS)
             if im.mode != "RGB": im = im.convert("RGB")
             thum = StringIO.StringIO()
             im = im.filter(ImageFilter.DETAIL)
             im.save(thum,"JPEG",quality=90)
 
             image.photos.save("small_"+imageobj.name,ContentFile(thum.getvalue()))
        else: message = "form invalid"
    else:
        message = "upload NG/no POST"

    return render_to_response('profile/upload.html',{'form':form,'message':message})

ちょっと訂正しました。以前の方法だと png が読めなかったのです。それから、mode がちがうと、jpg に出来なかったりするので、そちらも修正。ありがとうございました。=> http://d.hatena.ne.jp/perezvon/20090421/1240307348

Django は楽しいですね。

いままで、ORMとかWebのフレームワークが恥ずかしながら、全然ピンとこなかったのだけど、DjangoPython の考え方で作るのは、私にはほんとうにピンとくるので楽しいです。どうしてもちょっと複雑なことをしないと行けないなんかはまだうまく使えてないところもあるのだけど、urls => views => models => templates な考え方に則って、データのシンプルなやり取りをするのには、これはかなり素敵だと思います。あと、objects.filter とかも、これまた便利なんですよね。。。

修行はつづきます。

読了

初めてのPython 第3版

初めてのPython 第3版

Pythonチュートリアル 第2版

Pythonチュートリアル 第2版

この2冊を書きながら、読み進めてました。この2冊が、例えば、2.x対応と3対応のように、片方の本でのってない事を反対では詳しく、というのがよくのっていてためになりました。
>>>L=[1,2,3]
>>>L.append(L)
したときの挙動とか、共有リファレンスの話とか、とにかく、よい高速道路でした。

Django はオンラインのドキュメントがかなり充実してて、そのあたりを参考に進んできたし、基本的な使い方はわかって来たけど、よい設計のための工夫や、上記みたいなひっかかりポイントはまだまだなので、まだまだこれからです。

Python たのしいれす。

*1:print 忘れただけかも..