Djangoでデータをcsv形式でダウンロードする方法をご紹介します。
以下2つの記事で作成したモジュールに、csvダウンロード機能を追加します。
条件
- Django 2.2
- Python 3.7.0
モデル
場所:気象データ = 1:N になるようモデルを作成します。
from django.db import models from django.urls import reverse from datetime import datetime as dt class Location(models.Model): """場所モデル""" class Meta: db_table = 'location' name = models.CharField(verbose_name='ロケーション名', max_length=255) memo = models.CharField(verbose_name='メモ', max_length=255, default='', blank=True) author = models.ForeignKey( 'auth.User', on_delete=models.CASCADE, ) created_at = models.DateTimeField(verbose_name='登録日時', auto_now_add=True) updated_at = models.DateTimeField(verbose_name='更新日時', auto_now=True) def __str__(self): return self.name @staticmethod def get_absolute_url(self): return reverse('monitor:index') class WeatherData(models.Model): """気象データモデル""" class Meta: db_table = 'weather_data' unique_together = (('location', 'data_datetime'),) location = models.ForeignKey(Location, verbose_name='ロケーション', on_delete=models.PROTECT) data_datetime = models.DateTimeField(verbose_name='データ日時', default=dt.strptime('2001-01-01 00:00:00', '%Y-%m-%d %H:%M:%S')) temperature = models.FloatField(verbose_name='気温') humidity = models.FloatField(verbose_name='湿度') created_at = models.DateTimeField(verbose_name='登録日時', auto_now_add=True) updated_at = models.DateTimeField(verbose_name='更新日時', auto_now=True) def __str__(self): return self.location.name + ":" + str(self.data_datetime)
urls.py
アプリ単位のurls.pyに、データダウンロード用のURLを設定します。
# urls.py抜粋 from django.urls import path from . import views app_name = 'monitor' urlpatterns = [ # トップ画面 path('', views.IndexView.as_view(), name='index'), # 詳細画面 path('monitor/<int:pk>/', views.DetailView.as_view(), name='detail'), ・・・ # データダウンロード path('monitor/<int:pk>/download/', views.download, name='download'), ]
views.py
views.pyにデータダウンロード用の関数を追加します。
レスポンスにはcsvファイルを返すよう設定し、csvのヘッダーおよびデータを書き込む処理を記述します。
# views.py抜粋 import csv # データのダウンロード def download(request, pk): # レスポンスの設定 response = HttpResponse(content_type='text/csv') filename = 'data.csv' # ダウンロードするcsvファイル名 response['Content-Disposition'] = 'attachment; filename={}'.format(filename) writer = csv.writer(response) # ヘッダー出力 header = ['id','data_datetime','temperature','humidity','location_id'] writer.writerow(header) # データ出力 weather_data = WeatherData.objects.select_related('location').filter(location_id=pk) # 対象ロケーションの気象データを取得 for data in weather_data: writer.writerow([data.id, data.data_datetime, data.temperature, data.humidity, data.location_id]) return response
テンプレート
詳細画面に「データのダウンロード」リンクを追加します。
<!-- detail.html --> {% extends 'base.html' %} {% block content %} <h1>{{ object.name }}</h1> <section class=""> {{ object.memo|linebreaksbr }} <p> <a href="{% url 'monitor:download' object.pk %}" class="">データのダウンロード</a> </p> </section> <img src='{% url 'monitor:plot' object.pk %}' width=1200 height=800> <section> <p><a href="javascript:history.back()">< Back</a></p> </section> {% endblock %}
DBデータ
Django 管理サイトを用いて、Locationレコードを2つ追加します。
作成済みである”データのアップロード処理”で以下のようなデータをcsvアップロードしておきます。
data_datetime,temperature,humidity,location_id 2018-12-16 00:00:00,5,10,1 2018-12-16 01:00:00,1,50,1 2018-12-16 02:00:00,5,20,1 2018-12-16 03:00:00,10,70,1 2018-12-16 04:00:00,15,20,1 2018-12-16 00:00:00,25,80,2 2018-12-16 01:00:00,12,20,2 2018-12-16 02:00:00,5,50,2 2018-12-16 03:00:00,30,30,2 2018-12-16 04:00:00,15,70,2
実行結果
ダウンロード
”データのダウンロード”リンクをクリックすると、csvファイルのダウンロードが行われます。
ダウンロードされたcsvファイル
以下のように、csv形式でデータがダウンロードされます。
大きなデータをダウンロードする場合の対応
大きなデータをダウンロードする場合、サーバーのメモリを大量に消費する等の負荷がかかってしまいます。
そこで、「yield」と「StreamingHttpResponse」を用いて少しずつ結果を返すようにします。
urls.py
アプリ単位のurls.pyに、データダウンロード(ストリーミング)用のURLを設定します。
from django.urls import path from . import views app_name = 'monitor' urlpatterns = [ # トップ画面 path('', views.IndexView.as_view(), name='index'), # 詳細画面 path('monitor/<int:pk>/', views.DetailView.as_view(), name='detail'), ・・・ # データダウンロード(ストリーミング) path('monitor/<int:pk>/streaming/', views.some_streaming_csv_view, name='streaming'), ]
views.py
views.pyにデータダウンロード(ストリーミング)用の関数を追加します。
クエリは「yield」を用いて、レスポンスは「StreamingHttpResponse」を用いることで、結果を少しずつ返すよう実装します。
from django.http import StreamingHttpResponse class Echo: def write(self, value): return value # クエリを少しずつ実行 def yield_query(query): filter_id = 0 # ヘッダー出力 yield ['id', 'data_datetime', 'temperature', 'humidity', 'location_id'] while True: rows = list(query.filter(id__gt=filter_id).order_by('id')[:2]) if len(rows) == 0: break for row in rows: yield [row.id, row.data_datetime, row.temperature, row.humidity, row.location_id] # データ filter_id = rows[-1].id # データのダウンロード(ストリーミング) def some_streaming_csv_view(request, pk): query = WeatherData.objects.select_related('location').filter(location_id=pk) rows = yield_query(query) # クエリを少しずつ実行 pseudo_buffer = Echo() writer = csv.writer(pseudo_buffer) response = StreamingHttpResponse((writer.writerow(row) for row in rows), content_type="text/csv") filename = 'data.csv' # ダウンロードするcsvファイル名 response['Content-Disposition'] = 'attachment; filename={}'.format(filename) return response
テンプレート
詳細画面に「データのダウンロード(ストリーミング)」リンクを追加します。
{% extends 'base.html' %} {% block content %} <h1>{{ object.name }}</h1> <section class=""> {{ object.memo|linebreaksbr }} <p> <a href="{% url 'monitor:download' object.pk %}" class="">データのダウンロード</a> </p> <p> <a href="{% url 'monitor:streaming' object.pk %}" class="">データのダウンロード(ストリーミング)</a> </p> </section> <img src='{% url 'monitor:plot' object.pk %}' width=1200 height=800> <section> <p><a href="javascript:history.back()">< Back</a></p> </section> {% endblock %}
実行結果
”データのダウンロード(ストリーミング)”リンクをクリックすると、csvファイルのダウンロードが行われます。
以下のように、csv形式でデータがダウンロードされます。
サンプルソース
GitHubにサンプルソースを公開しています。
https://github.com/kzmrt/download
参考
Django公式:Django で CSV を出力する
https://docs.djangoproject.com/ja/2.2/howto/outputting-csv/
Qiita:Python + Django でCSVのダウンロード/アップロード
https://qiita.com/t-iguchi/items/d2862e7ef7ec7f1b07e5
Qiita:【Python3】Djangoで登録されたデータをCSV出力する
https://qiita.com/otera05/items/7883a51c20b72729c73e
Qiita:Djangoで取得件数の多いSQLの結果をcsvとしてエクスポートさせる
https://qiita.com/fumihiko-hidaka/items/0be8e8933525395de665