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

