管理画面でファイル名に日本語を含むファイルをアップロードしてみる
前提とする環境は http://d.hatena.ne.jp/re_guzy/20070424/p1 と同じ。
Windows 2003 Server、Python 2.4.4、 Django 0.96(SQLite使用) で動作確認済み。
※oldformsを使っているので、Django 0.96 より後のリリースでは動かないはず。
困ったこと
FileFieldを使うと、アップロードしたファイルのファイル名のうち、日本語はすべて除去される。
(「新規テキスト文書.txt」→「.txt」)
参考URL
- FileFieldでオリジナルのファイル名を保存する
→ r4150 に対するパッチがあるけど、フレームワーク側を変更したくなかったので見送り。 - newformsのFileFiledについて(求むヘルプ )
→ これはnewformsのものなので、0.96 の管理画面に関係ない。結論もでてないみたいだし。
無理やりなんとかしてみる
http://d.hatena.ne.jp/re_guzy/20070330/p1 と同じように メタクラスを利用する。
(ManyToManyの対応が不要ならdispatcher周りはなくてもいいかな。)
仕組みとしてはこんな感じ。
- Manipulator.saveをデコレートする。
- オリジナルのファイル名がManipulator.saveの引数にあるので、それを使う。
- アップロードしたファイルが保存されてから、リネームする。
やってることは単純なんだけど、文字コード周りで苦労した。
DBにSQLiteを使っている関係で、オリジナルのファイル名を保存するときにUTF-8にエンコードしている。他のDBならいらないかも。
#models.py # -*- encoding: utf-8 -*- from django.db import models from django.db.models.base import ModelBase from django.db.models.manipulators import * from django.db.models import * from django.dispatch import dispatcher import re, sys, os CODE_MOD = 'utf-8' #モジュールの文字コード CODE_SYS = sys.getfilesystemencoding() def save_decorator(func): def save_decorator_main(self, new_data): saved = func(self, new_data) for key in new_data.keys(): m = re.search(r'^(\w+)_file$', key) if not m or not 'filename' in new_data[key]: continue field = m.groups(1)[0] original_name = new_data[key]['filename'].decode(CODE_MOD) path = getattr(saved, "get_%s_filename" % (field))().decode(CODE_MOD) dirname, filename = os.path.split(path) #パスはOSの文字コードにエンコードしておく(os.path.existsなどが意図したとおりに動かないので) newpath = os.path.join(dirname, original_name).encode(CODE_SYS) #すでにファイルが存在する場合は、ファイル名先頭に「_」をつける while os.path.exists(newpath): original_name = '_' + original_name newpath = os.path.join(dirname, original_name).encode(CODE_SYS) #SQLiteはUTF-8しか対応していないのでエンコードしておく newfield = getattr(saved, field).decode(CODE_MOD).replace(filename, original_name).encode(CODE_MOD) os.rename(path, newpath) setattr(saved, field, newfield) saved.save() #ファイルパスとDB上のデータの不整合を防ぐためにここで保存する dispatcher.send(signal=post_manipulator_save, sender=saved.__class__, instance=saved) return saved return save_decorator_main def get_filename_decorator(func): def get_filename_decorator_main(self): return func(self).decode(CODE_MOD) return get_filename_decorator_main class CustomModelBase(ModelBase): def __new__(cls, name, bases, attrs): meta = super(CustomModelBase, cls).__new__(cls, name, bases, attrs) for manipulator in [getattr(meta, 'AddManipulator'), getattr(meta, 'ChangeManipulator')]: # 複数回デコレートを防いでおく if not "save_decorator_main" in str(manipulator.save): manipulator.save = save_decorator(manipulator.save) for attr in dir(meta): m = re.search('^get_\w+_filename$', attr) if m: funcname = m.group() func = getattr(meta, funcname) # 複数回デコレートを防いでおく if not "get_filename_decorator_main" in str(func): setattr(meta, funcname, get_filename_decorator(func)) return meta post_manipulator_save = object() class Upload(models.Model): __metaclass__ = CustomModelBase content = models.FileField(upload_to='uploaded/%Y/%m', blank=True, null=True) def __str__(self): return self.content class Admin:pass
#settings.py MEDIA_ROOT = 'd:/www/site_media/' MEDIA_URL = 'http://servername/site_media/'
残課題
- apacheで静的ファイルを配信している場合は、管理画面からアップロードしたファイルを開くことができるけど、開発サーバの場合はできない。
- 一度アップロードしたファイルを変更できない。
上は開発時に不便なだけなのでまあいいけど、下は結構痛いかな。