Django の Storage の exists が SuspiciousOperation でハマったけど別にそれ問題じゃなかった話
Storage を使う必要があるからつかってるんだけど、その Storage で実装されているものと os モジュールで実装されているものでは内部的に若干挙動が異なることがある。というかことなってハマった。
絶対パスか相対パスかって話
具体的には MySQL でこんなエラーがでた。エラー内容は参考URLのものだけど、似たようなもの。
参考::API Only - Stack Exchange
参考::Google Groups
File "/home/grmadmin/webapps/django/lib/python2.5/django/core/files/storage.py", line 204, in path raise SuspiciousOperation("Attempted access to '%s' denied." % name) SuspiciousOperation: Attempted access to '/img/avatars/default.gif' denied.
ん? access denied ってことは権限か?っておもったら権限は適切だし、なんでだろうといろいろさぐったらどうやら Storage でファイルを参照するときは相対パス( relative path ) にする必要があるらしい。そうか、なら相対パスにして返してあげればいいんだな、とこんな感じのコードにしてあげた
abspath = os.path.join(BASE_DIR, hogemoge) # 絶対パスを storage.exists(os.path.join(abspath + branch)) # 相対パスにした storage.exists(os.path.relpath(abspath + branch))
これでめでたしめでたし……かとおもったらそううまくはいかなかったのだった。
Django の Storage の exists は絶対パスを参照しようとする
なんかおかしいとおもったんだ。丁寧にデバッグして内部のコードに pdb なり仕込んでわかった
class FileSystemStorage(Storage): """ Standard filesystem storage """ ... def exists(self, name): return os.path.exists(self.path(name)) ... def path(self, name): try: path = safe_join(self.location, name) except ValueError: raise SuspiciousOperation("Attempted access to '%s' denied." % name) return os.path.normpath(path) ...
みたところドキュメント見た感じだと Storage というインターフェイス的なのを継承して必要なものを実装した FileSystemStorage クラス。こいつの exists がそれなんだけど、このとき self.path をみている。 self はこの場合 storage インスタンスなので、 path は下のメソッドだ。で、 safe_join なることをして os.path.normpath という os 互換のための関数をかましてパスを返している、という処理だ。safe_join はなんかよくわからんが正規化してよしなにして絶対パスを返してやる関数だそうだ。ということで、どうしても絶対パスが帰ってきてしまう==相対パスじゃないと SuspiciousOperation エラーが起こるので困る、という事態にあった。
解決策
結局 os.path.exists をつかった。挙動としてはそれでよい感じだったのでもういいやと。そもそも Storage インスタンスをつかってたのはテストのためで、テストのときに InMemoryStorage というモジュールを使うことで擬似的に Storage を再現していたからだった。テストを捨てるのは(コメントアウトだけど)正直こわいなーって思うけど、挙動として正しく動いてくれないとこまるので、まあしかたないかなぁと。
ちなみにこれとは別件だけど Storege まわりはテストがなんか書きづらい感じがあって非常にやりづらい。なんとかならないものか。
そもそもそういう path をわたさないで実装するということでこの問題はなかったことになりました!!!!よかったね!!!!