Djangoでデータをcsv形式でダウンロードする方法

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()">&lt; 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()">&lt; 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

python の yield。サクッと理解するには return と比較

http://ailaby.com/yield/

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です