DjangoからのExcelファイルのダウンロード
Excel互換ファイルをPythonで作成する方法を覚えたところで、次はそのファイルをDjangoでダウンロード可能にする方法を見ていきましょう。手順は簡単です。また、同じ手順でほとんどすべての種類のバイナリファイルを生成し、提供することができます。ファイルを作成できれば、それをユーザーにダウンロードさせるのは簡単です。
鍵を握るのは「content-type」というHTTPヘッダーです。ブラウザからサーバー上のファイルを要求する場合、トランザクションは次のようになります。
GET /wp-content/uploads/2007/10/cropped-dsc_0020.jpg HTTP/1.1 Host: weblog.lonelylion.com User-Agent:Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.8.1.11) Gecko/20071204 Ubuntu/7.10 (gutsy) Firefox/2.0.0.11 Accept: image/png,*/*;q=0.5 Accept-Language: en-us,en;q=0.5 Accept-Encoding: gzip,deflate Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7 Keep-Alive: 300 Connection: keep-alive Referer: http://lonelylion.com/ HTTP/1.x 200 OK Date: Sat, 02 Feb 2008 17:53:58 GMT Server: Apache/1.3.37 (Unix) mod_throttle/3.1.2 DAV/1.0.3 mod_fastcgi/2.4.2 mod_gzip/1.3.26.1a PHP/4.4.8 mod_ssl/2.8.22 OpenSSL/0.9.7e Last-Modified: Thu, 01 Nov 2007 03:22:12 GMT Etag: "798f213-5c88-47294664" Accept-Ranges: bytes Content-Length: 23688 Keep-Alive: timeout=2, max=97 Connection: Keep-Alive Content-Type: image/jpeg
最後の行のContent-Type: image/jpegに注目してください。この行は、後続のデータが何であるかをブラウザに伝えています。ブラウザはこの行に基づいて、データの表示方法や、どの外部アプリケーションを開いてデータを表示するかを判断します。
上記の例ではJpegの画像を要求しているため、Content-Typeに「image/jpeg」が設定されています。このヘッダーを「application/ms-excel」に変更すると、ファイルがExcelで開かれます。Djangoでヘッダーの記述を変更するのは非常に簡単です。以下に、csvファイルをブラウザに送信し、それをExcelで開くようブラウザに指示するDjangoビューの例を示します。
import csv
from StringIO import StringIO
from django.http import HttpResponse
def show_excel(request):
# use a StringIO buffer rather than opening a file
output = StringIO()
w = csv.writer(output)
for i in range(10):
w.writerow(range(10))
# rewind the virtual file
output.seek(0)
return HttpResponse(output.read(),
mimetype='application/ms-excel')
最初のcsv作成の例にいくつか修正を加えました。まず、実際にファイルを開くのではなく、ファイルに似たStringIOオブジェクトを使用しています。また、応答をDjangoのHttpResponseオブジェクトでラッピングしています(HttpResponseオブジェクトはDjangoビュー標準の戻り値のオブジェクトタイプです)。さらに、HttpResponseにcontent_typeを渡すことによって、Content-Typeヘッダーを「application/ms-excel」に設定しています。
このパターンを使用して、ほとんどすべての種類のバイナリデータをWebブラウザから取得することができます。PDF、画像、音声、動画など、あらゆるデータに対して生成用のライブラリが用意されているので、Content-Typeヘッダーの値さえわかれば、バイナリデータを生成することが可能です。
それでは、同じテクニックを使ってpyExceleratorで生成したExcelファイルを出力してみましょう。
from pyExcelerator import *
from django.http import HttpResponse
def show_excel(request):
wb = Workbook()
ws0 = wb.add_sheet('0')
for x in range(10):
for y in range(10):
# writing to a specific x,y
ws0.write(x,y,"this is cell %s, %s" % (x,y))
wb.save('output.xls')
return HttpResponse(open('output.xls','rb').read(),
mimetype='application/ms-excel')
これもやはり、先ほどのpyExceleratorを使用してExcelファイルを作成した例に似ています。ただこのコードには1つ難があります。一時ファイルを作成してデータを書き込み、その後あらためてファイルを開き、そこからデータを読み込んでいるのです。この方法だと、トラフィックが多い場合に問題が起こる可能性があります。また、あるユーザーがファイルを読み込んでいる間に別のユーザーがそこにアクセスすると、ファイルが破損してしまうかもしれません。ファイル名にタイムスタンプを付加するという解決方法もありますが、最も理想的なのは、Workbookのsaveメソッドにファイルに似たオブジェクトを渡してそこにデータを保存するという方法です。現在のpyExceleratorではこの方法は使用できませんが、必ずパッチが提供されると予想しています。
筆者はたいていの場合、csvファイルを「Content-Type: application/ms-excel」としてブラウザに送信します。StringIOを使用すれば実装がシンプルになりますし、ダウンロード可能なスプレッドシートの作成にかかる時間は通常5分以内なので、ほとんどのエンドユーザーはおかしいとも感じません。これでユーザーの希望はかなえられますし、すぐにデータをいじってもらうことができます。ユーザーはデータをあれこれ操作することで、彼らが実際に使用するHTML表示についてより明確なビジョンを持つようになり、的確な要求をしてくるようになるでしょう。良いことだらけです。
