Django de BBS
昨日友達からの要望でーBBSを作成してくれと…. ということでDjangoで!!
GenericViewsを今まであえて使わないで練習していたのでー今日はGenericViewsを使ってみた!! views.pyを一切書かないで終えたので,やっぱPythonの勉強にはならないねっ!!
とりあえず,書き込みできればいいのでーまた,メインは携帯からのアクセスなのでデザインなんてーシンプルこの上ない!!
まぁ?とりあえず書き込みできる形になったのでーapache(mod_wsgi)でアクセスっ!! ブラウザチェックOK!!
じゃー携帯からテスト…と思ったらーアクセスできない….アレ!?と思ったらー携帯はDigest認証になっているとーダメだったようで…(Basic認証はOK).
さてさて,めちゃくちゃシンプルなBBSはできたのですが…携帯の場合,認証・セッションとかどうするのだろう….あと端末固有IDで認証したいんだけどなぁ~.またPHPでいう「use_trans_sid=1」みたいなのってあるのかなぁ….
Django + 画像アップロード
Djangoで画像アップロードを試してみました!! 自分の環境が今まで,0.96だったのですがーnewformsがImageFieldと連動できていないのでー0.97(-pre-SVN-6085)に変えちゃいましたっ!!
# models.py
class Image(models.Model):
title = models.CharField(maxlength=30)
image = models.ImageField(upload_to='photos')
created_at = models.DateTimeField(editable=False, default=datetime.now)
updated_at = models.DateTimeField(editable=False)
is_active = models.BooleanField(editable=False, default=True)
def save(self):
self.updated_at = datetime.now()
super(Image, self).save()
ここで注目するのはimageカラムだけ!! upload_toオプションでMEDIA_ROOT配下にphotosディレクトリが作成され画像が保存されるように設定.
# upload.html
<form action="" method="post" enctype="multipart/form-data">
<label>Title : </label>
<p>{{ form.title }}</p>
{% if form.errors.title %}
{% for e in form.errors.title %}
<div class="error">{{ e }}</div>
{% endfor %}
{% endif %}
<label>Image : </label>
<p>{{ form.image }}</p>
{% if form.errors.image %}
{% for e in form.errors.image %}
<div class="error">{{ e }}</div>
{% endfor %}
{% endif %}
<input type="submit" value="Submit" />
</form>
Django-0.97preならばー「{{ form.image }}」とするだけでー 「<input id="id_image" type="file" name="image"/>」となる!!やっぱこうでないと!!
# views.py
def upload(request):
f = forms.form_for_model(Image)
if request.method == 'POST':
form = f(request_post, request.FILES)
if form.is_valid():
form.save()
return HttpResponseRedirect('/imagedb/')
else:
form = f(None)
t = loader.get_template('imagedb/upload.html')
c = RequestContext(request, {'form':form})
return HttpResponse(t.render(c))
- ここで,自分が失敗してしまったのがー
- form = f(request_post, request.FILES)
- を最初,
- request_post = request.POST.copy() request_post.update(request.FILES) form = f(request_post)
としていた….これだとimageが常にNoneとなってしまい.毎度毎度バリデートエラー(このフィールドは必須です)となってしまっていた!!
省略して書いている部分もありますが,てな感じでアップロードすることができました.
個人的には,アップロードする画像名はランダムな文字列に変更したい.また,アップロード先(upload_to)をユーザーIDなどで動的に変更したい. 日付にすることはできるようですが…. あとはーやっぱリサイズしないとねっ!! こちらのサイト(http://d.hatena.ne.jp/piro_suke/20070704/1183559610)で試されているようなのでーPhotoFieldにして今度やってみようかと思ってます.
最近,Djangoいじれていてー面白いね!!
さらに しつこく 画像アップロード
また,画像アップロードです. 今回は,都合によりPhotoFieldをカスタマイズしてみました.
というのも,前回アップロード作成のあと削除メソッドも….と思ったら説明は難しいのですがーなんともしっくり来ない! しかも,newformsからは「save_file()」なんて使われてないし….じゃー勉強ついでに色々いじってみることにしました.
仕様は, 1度に複数のリサイズファイルを作成する. ファイル名をランダムにする. 削除時はちゃんとリサイズされた複数のファイルを削除する.
なんつーのを目標にトライ!!
import Image as PILImage
import cStringIO, random, string, re, os
from django.db.models.fields import ImageField
from django.conf import settings
from django.utils.functional import curry
FIT = 0
CROP = 1
class PhotoField(ImageField):
EMPTY_VALUES = (None, (), '',)
def __init__(self, verbose_name=None, name=None, width_field=None, height_field=None, thumbs=None, mode=FIT, quality=None, **kwargs):
super(PhotoField, self).__init__(verbose_name, name, width_field, height_field, **kwargs)
self.thumbs, self.mode, self.quality = thumbs, mode, quality
def get_internal_type(self):
return 'ImageField'
# 動的に各リサイズされたイメージURL取得メソッド作成しようと思いつつどぼん….
#def contribute_to_class(self, cls, name):
# super(PhotoField, self).contribute_to_class(cls, name)
# for thumb in self.thumbs:
# setattr(cls, 'get_%s%s_url' % (self.name, thumb[2]), curry(cls._get_FIELD_SUFFIX_url, field=self, suffix=thumb[2]))
# #setattr(cls, 'get_%s%s_url' % (self.name, thumb[2]), curry(self._get_FIELD_SUFFIX_url, suffix=thumb[2]))
#def _get_FIELD_SUFFIX_url(self, suffix):
# return file_url
def save_form_data(self, instance, data):
if data:
extention = re.search(r"(?P<ext>\.[a-zA-Z]+)$", data.filename).group('ext')
data.filename = ''.join([random.choice(string.letters+string.digits) for x in range(15)])
for width, height, suffix in self.thumbs:
resized_filename = '%s%s%s' % (data.filename, suffix, extention)
getattr(instance, 'save_%s_file' % self.name)(resized_filename, self.resize(data.content, width, height), False)
data.filename += extention
getattr(instance, 'save_%s_file' % self.name)(data.filename, data.content, False)
def delete_file(self, instance):
file_name = getattr(instance, 'get_%s_filename' % self.name)()
directory, filename = os.path.split(file_name)
f = re.search(r"(?P<file>[a-zA-Z0-9]+)(?P<ext>\.[a-zA-Z]+)$", filename)
for thumb in self.thumbs:
thumb_file_name = os.path.join(directory, '%s%s%s' % (f.group('file'), thumb[2], f.group('ext')))
if os.path.exists(thumb_file_name):
os.remove(thumb_file_name)
if os.path.exists(file_name):
os.remove(file_name)
def resize(self, data, width, height):
data_file = cStringIO.StringIO(data)
pil_obj = PILImage.open(data_file)
if pil_obj.mode != 'RGB':
pil_obj = pil_obj.convert('RGB')
if self.mode == FIT:
pil_obj.thumbnail((int(width), int(height)), PILImage.ANTIALIAS)
elif self.mode == CROP:
x, y = pil_obj.size
if x >= width and y >= height:
aspect = float(width) / float(height)
crop_width = min(int(aspect*y), x)
crop_height = min(int(float(x)/aspect), y)
crop_point_x = (x-crop_width)/2
crop_point_y = (y-crop_height)/2
pil_obj = pil_obj.crop((crop_point_x, crop_point_y, crop_point_x+crop_width, crop_point_y+crop_height))
pil_obj.thumbnail((width, height), PILImage.ANTIALIAS)
else:
crop_width = min(width, x)
crop_height = min(height, y)
crop_point_x = (x-crop_width)/2
crop_point_y = (y-crop_height)/2
pil_obj = pil_obj.crop((crop_point_x, crop_point_y, crop_point_x+crop_width, crop_point_y+crop_height))
out_file = cStringIO.StringIO()
if self.quality:
pil_obj.save(out_file, 'JPEG', quality=self.quality)
else:
pil_obj.save(out_file, 'JPEG')
out_file.reset()
return out_file.read()
てな感じ. リサイズ部分は,オリジナルのPhotoFieldをそのまま.そのためJPEGにしか変換されていない!これはとりあえず無視!!! だもんで拡張子の処理はー今は意味ない!
では早速使ってみよう.(startapp image)
■ models.py
from django.db import models
from verdjnlib.fields import PhotoField, FIT # カスタマイズ済み
THUMBS = ((75, 75, '_thumb'), (250, 250, '_middle'))
class Image(models.Model):
image = PhotoField(upload_to='photos', thumbs=THUMBS)
# 暫定対応
def get_image_thumb_url(self):
return self.get_image_suffix_url(THUMBS[0][2])
# 暫定対応
def get_image_middle_url(self):
return self.get_image_suffix_url(THUMBS[1][2])
# 暫定対応
def get_image_suffix_url(self, suffix):
import re
filename = self.get_image_url()
extention = re.search(r'(?P<ext>\.[a-zA-Z]+$)', filename).group('ext')
return re.sub(r'\.[a-zA-Z]+$', suffix + extention, filename)
このようにすれば,2つのサムネイルを作成してくれるはず….
■ views.py
from django.http import HttpResponse, HttpResponseRedirect
from django.shortcuts import get_object_or_404
from django.template import RequestContext, loader
from django import newforms as forms
from ENDLESS.image.models import Image
def view(request):
object_list = Image.objects.all()
t = loader.get_template('image/view.html')
c = RequestContext(request, {'object_list':object_list})
return HttpResponse(t.render(c))
def upload(request):
f = forms.form_for_model(Image)
if request.method == 'POST':
form = f(request.POST, request.FILES)
if form.is_valid():
form.save()
return HttpResponseRedirect('/image/')
else:
form = f(None)
t = loader.get_template('image/upload.html')
c = RequestContext(request, {'form':form})
return HttpResponse(t.render(c))
def delete(request, id):
object = get_object_or_404(Image, id=id)
object.delete()
return HttpResponseRedirect('/image/')
■ view.html
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html>
<head>
<title>ENDLESS IMAGE TEST</title>
</head>
<body>
<p><a href="/image/upload/">UPLOAD</a></p>
{% for object in object_list %}
<p><img src="/site_media{{ object.get_image_url }}" /></p>
<p><img src="/site_media{{ object.get_image_middle_url }}" /></p>
<p><img src="/site_media{{ object.get_image_thumb_url }}" /></p>
<a href="/image/delete/{{ object.id }}/">Delete</a>
{% endfor %}
</body>
</html>
アップロード,削除ちゃんと出来ているようです. とりあえず,今回の目標は達成!! しかしーやっぱリサイズURLは動的に生成させたいね. また,JPEGのみ(?)ってーのはまずいねぇ?PILってどうなっているのかわからないのでー今度はそっちの使い方もみてゆかねば…. 拡張子やファイル名の処理部分がスッキリしていないのでーあれもなんとかしてみたいね…Python勉強しないと….
んーーー.個人的には 今回のこの作業を通じてー色んなことを得た気がします. Djangoのコードを追いかけていたんですがーやっぱ読めない!!(何度もnobuさんに聞いちゃいましたね.)何やっているのか?急にわからなくなってー推測になってしまう. あれをスラスラと読めたらー面白いのだろうなぁー.
環境構築2
最近,めっちゃ暑かったですわぁ.そんな中今日は比較的涼しくて過ごしやすかったですわっ!!
さーてさて, こないだのテストインストールを生かして環境構築した. まだ,中途半端なところだらけなのだが….忘れぬよう個人的メモとして箇条書き程度に記しておくことにした.
■ FedoraCore5 手元にメディアがあったのでこちらのOSにした. インストールは,ほぼ最小インストール. Cコンパイラなどからインストール(こういったいくつかはyum使用). これかったるいねっ!!
■ Mecab-0.96
wget http://downloads.sourceforge.net/mecab/mecab-0.96.tar.gz?modtime=1181487226&big_mirror=0
tar xzvf mecab-0.96.tar.gz
./configure \
--prefix=/usr/local/mecab \
--with-charset=utf8
make
make install
■ Mecab-ipadic-2.7.0
wget http://downloads.sourceforge.net/mecab/mecab-ipadic-2.7.0-20070801.tar.gz?modtime=1185895550&big_mirror=0
tar xzvf mecab-ipadic-2.7.0-20070801.tar.gz
./configure \
--prefix=/usr/local/mecab \
--with-charset=utf8 \
--with-mecab-config=/usr/local/mecab/bin/mecab-config
make
make install
■ Senna-1.0.8
wget http://keihanna.dl.sourceforge.jp/senna/26563/senna-1.0.8.tar.gz
tar xzvf senna-1.0.8.tar.gz
./configure \
--prefix=/usr/local/senna \
--with-mecab \
--with-mecab-config=/usr/local/mecab/bin/mecab-config
make
make install
■ MySQL-5.0.41(tritonn)
wget http://globalbase.dl.sourceforge.jp/tritonn/26391/mysql-5.0.41-tritonn-1.0.3.tar.gz
tar xzvf mysql-5.0.41-tritonn-1.0.3.tar.gz
./configure \
--prefix=/usr/local/mysql \
--localstatedir=/home/mysql/data \
--libexecdir=/usr/local/mysql/bin \
--with-mysqld-user=mysql \
--with-charset=utf8 \
--with-collation=utf8_general_ci \
--with-unix-socket-path=/home/mysql/data/mysql.sock \
--enable-thread-safe-client \
--enable-local-infile \
--enable-assembler \
--with-zlib-dir=bundled \
--with-big-tables \
--with-readline \
--with-innodb \
--with-senna=/usr/local/senna \
--with-mecab=/usr/local/mecab \
--with-extra-charsets=complex
make
make install
■ Apache2.2(apr, apr-util, apache)
wget http://www.meisei-u.ac.jp/mirror/apache/httpd/httpd-2.2.4.tar.gz
tar xzvf httpd-2.2.4.tar.gz
○ apr
cd /httpd-2.2.4/srclib/apr
./configure \
--prefix=/usr/local/apr \
--enable-threads
make
make install
○ apr-util
cd /httpd-2.2.4/srclib/apr-util
./configure \
--prefix=/usr/local/apr \
--with-apr=/usr/local/apr
make
make install
○ apache
./configure \
--enable-mods-shared=most \
--enable-dav \
--enable-dav-fs \
--enable-dav-lock \
--enable-cgi \
--enable-cgid \
--enable-so \
--with-apr=/usr/local/apr \
--with-apr-util=/usr/local/apr
make
make install
■ PHP-5.2.3
wget http://jp.php.net/get/php-5.2.3.tar.gz/from/this/mirror
./configure \
--prefix=/usr/local/php5 \
--with-apxs2=/usr/local/apache2/bin/apxs \
--with-config-file-path=/usr/local/php5/etc \
--with-config-file-scan-dir=/usr/local/php5/etc/php.d \
--with-layout=GNU \
--enable-mbstring \
--with-libmbfl \
--enable-mbregex (省略)
make
make install
■ Python-2.5
wget http://www.python.jp/pub/ftp.python.org/python/2.5/Python-2.5.tgz
./configure --prefix=/usr/local/python
make
make install
■ SetupTool
wget http://cheeseshop.python.org/packages/2.5/s/setuptools/setuptools-0.6c6-py2.5.egg
sh ./setuptools-0.6c6-py2.5.egg
■ 色々
easy_install-2.5 -U -Z docutils
easy_install-2.5 -U -Z MySQL_Python
easy_install-2.5 -U -Z ipython
easy_install-2.5 -U -Z pysqlite
easy_install-2.5 -U -Z TracPygments
■ neon
wget http://www.webdav.org/neon/neon-0.25.5.tar.gz
./configure --prefix=/usr/local/neon
make
make install
■ swig
wget http://jaist.dl.sourceforge.net/sourceforge/swig/swig-1.3.29.tar.gz
./configure \
--prefix=/usr/local/swig \
--with-python=/usrlocal/python/bin/python2.5 \
--without-java \
--without-ruby \
--without-php4
make
make install
■ clearsilver
wget http://www.clearsilver.net/downloads/clearsilver-0.10.5.tar.gz
tar xzf clearsilver-0.10.5.tar.gz
./configure \
--prefix=/usr/local/cs \
--with-apache=/usr/local/apache2 \
--with-python=/usr/local/python/bin/python2.5 \
--disable-csharp
make
make install
「--disable-csharp」を最初つけておらず,エラーがでていた.C#あたりのインストールもこころみたのだが結局 disable .
■ SVN
./configure \
--prefix=/usr/local/svn\
--enable-dso \
--with-apxs=/usr/local/apache2/bin/apxs \
--with-apr=/usr/local/apr \
--with-apr-util=/usr/local/apr \
--with-editor=/usr/bin/vim \
--without-jdk \
--with-swig=/usr/local/swig \
--with-neon=/usr/local/neon
make
make install
vim /etc/ld.so.conf.d/svn.conf
ldconfig
make swig-py
make install-swig-py
vim /usr/local/python/lib/python2.5/site-packages/svn-python.pth
■ mod_wsgi
wget http://modwsgi.googlecode.com/files/mod_wsgi-1.0c3.tar.gz
tar xzf mod_wsgi-1.0c3.tar.gz
./configure \
--with-apxs=/usr/local/apache2/bin/apxs \
--with-python=/usr/local/python/bin/python2.5
make
make install
■ Trac
wget http://ftp.edgewall.com/pub/trac/trac-0.10.4.tar.gz
tar xzf trac-0.10.4.tar.gz
python2.5 ./setup.py build
python2.5 ./setup.py install
■ Django
wget http://www.djangoproject.com/download/0.96/tarball/
tar xzf Django-0.96.tar.gz
python2.5 setup.py install
ざざっとこんなんかなぁ?.間々で,ldconfig, path設定やuser作成なども行っている. 何が面倒だったってー前PCがEUC-JPだったもんでー文字コードが変わり,色んなソースコードなどの変更に追われた感じ….
個人的に忘れそうだからーlinux漢字フォントのインストールはこれで…. yum install fonts-japanese
画像アップロード + リサイズ
昨日に引き続きファイルアップロード! 色々と中途半端な部分もあるのですがーBLOGへ….
昨日のmodelとは,カラム数が増えてしまい見づらいかもしれません. とりあえず,やってみたかったのはーviews.py は
def upload(request):
f = forms.form_for_model(Image)
if request.method == 'POST':
request_post = request.POST.copy()
request_post.update({'user':request.user.id})
form = f(request_post, request.FILES)
if form.is_valid():
form.save()
return HttpResponseRedirect('/imagedb/')
else:
form = f(None)
t = loader.get_template('imagedb/upload.html')
c = RequestContext(request, {'form':form})
return HttpResponse(t.render(c))
とほとんど変えず….つまり form.save() を実行するだけで thumbnail を作成 しちゃって欲しいなぁーと…. また,ファイル名をランダム文字列に! ということで,下記のような model にカスタマイズしてみました.
class Image(models.Model):
user = models.ForeignKey(User)
title = models.CharField(maxlength=30)
description = models.TextField()
filecode = models.CharField(maxlength=100, editable=False)
extention = models.CharField(maxlength=100, editable=False)
image = PhotoField(upload_to='photos')
labels = models.ManyToManyField(Label)
created_at = models.DateTimeField(editable=False, default=datetime.now)
updated_at = models.DateTimeField(editable=False)
is_active = models.BooleanField(editable=False, default=True)
class Admin:
pass
def __unicode__(self):
return self.title
def save(self):
self.updated_at = datetime.now()
super(Image, self).save()
def save_resize_file(self, image_content, suffix, height, width, mode=FIT):
resized_image = PhotoField.resize(image_content, height, width, mode)
filename = self.filecode + suffix + self.extention
return filename, resized_image
def save_thumbnail_file(self, image_content):
return self.save_resize_file(image_content, '_thumb', 75, 75)
def get_thumbnail_url(self):
return '/'+self._meta.get_field('image').upload_to+'/' + self.filecode + '_thumb' + self.extention
def save_middle_file(self, image_content):
return self.save_resize_file(image_content, '_middle', 250, 250)
def get_middle_url(self):
return '/'+self._meta.get_field('image').upload_to+'/' + self.filecode + '_middle' + self.extention
def save_image_file(self, filename, raw_contents, save=True):
self.filecode = ''.join([random.choice(string.letters+string.digits) for x in range(15)])
m = re.search(r'(?P<ext>\.[a-zA-Z]+$)', filename)
self.extention = m.group('ext')
# get image field
image = self._meta.get_field('image')
self.image = image.upload_to + self.filecode + self.extention
# create upload thumbnail
filename, resized_image = self.save_thumbnail_file(raw_contents)
super(Image, self)._save_FIELD_file(image, filename, resized_image, False)
# create upload middle
filename, resized_image = self.save_middle_file(raw_contents)
super(Image, self)._save_FIELD_file(image, filename, resized_image, False)
# upload save original
return super(Image, self)._save_FIELD_file(image, self.filecode + self.extention, raw_contents, True)
- これで,「xxxxx.jpg」なんてファイルをアップロードすると,
- RANDOMSTRINGZZZ.jpg, RANDOMSTRINGZZZ_thumb.jpg, RANDOMSTRINGZZZ_middle.jpg というファイルが3つアップロードされ,
データベースには,
+----+-…+-----------------+-----------+-------------------------------+-
| id | …| filecode | extention | image |
+----+-…+-----------------+-----------+-------------------------------+-
| 1 | …| RANDOMSTRINGZZZ | .jpg | photos/RANDOMSTRINGZZZ.jpg |
+----+-…+-----------------+-----------+-------------------------------+-
なんて情報が登録される.
改善点はまだありますが,とりあえず目的は達成!! nobu さんのパワーも沢山お借りしました☆ありがとうございます.
方向としてはどうなのか…邪道なのか….どなたからかの突っ込みがあればなんて思ってます.