DjangoでCSVアップロード、データ処理、ダウンロード機能を作成してみた


やりたいこと

CSVを複数取り込み、処理をしてから結果を同じくCSVで返す。
今回は以下のケースを使用してこれを実装する。

ケース

ある大学で、以下のCSVが入手できている。
available.csvでは、各クラスのキャパシティがどのくらいかの情報を持つ。
reserved.csvでは、各クラスで何人の生徒が授業を履修予定かの情報を持つ。
今回、これらのファイルをブラウザ上でアップロードすると、現在どのくらい各クラスに空きがあるかを示すCSVをダウンロードする様なアプリを作成する。

設計

フロント側では、主に以下の2つの動きがある。
①:ファイルアップロード
②:アップロードされたファイルを元に各クラスの空きを示すCSVを吐き出す

前提

Djangoのプロジェクトフォルダ(myproject)とアプリフォルダ(app)が既にある前提とする。ない場合はこちらを参照。

フォルダ構成

今回、以下のようなフォルダ構成を取る。

├─app
│  │  admin.py
│  │  apps.py
│  │  forms.py
│  │  functions.py
│  │  models.py
│  │  tests.py
│  │  views.py
│  │  __init__.py
│  │
│  ├─migrations
│  │    __init__.py
│  │
│  ├─static
│  │  └─upload
│  │          test.txt
│  │
│  └─templates
│       index.html
│
├─myproject
│  │  settings.py
│  │  urls.py
│  │  wsgi.py
│  │  __init__.py
│  │
│  └─static
│        humans.txt
│
└─staticfiles

フォーム作成

まず、ファイルをアップロードできるよう、以下のようにforms.pyでフォームを定義する。

from django import forms
#ClassroomFormを定義
class ClassroomForm(forms.Form):    
    availablity = forms.FileField() 
    reservation = forms.FileField()

フォームをHTMLへ渡す

次に、作成したフォームをフロント側で表示するため、以下のようにviews.pyでフォームをHTMLに渡す。

from django.shortcuts import render  
from app.forms import ClassroomForm  

def index(request): 
    ###ClassroomFormをindex.htmlに渡す
    classroom = ClassroomForm()  
    return render(request,"index.html",{'form':classroom}) 

これでindex.html{{ form.as_p }}を使用し、以下のようにフォームを引っ張ってこれる。

<body>  
    <form method="POST" class="post-form" enctype="multipart/form-data">  
            {% csrf_token %}  
            {{ form.as_p }}  
            <button type="submit" class="save btn btn-default">Save</button>  
    </form>  
</body> 

POST処理

このままフォームを提出すると、views.pyindexメゾットにPOSTされた情報が渡される。なので、POST処理をするようindexを以下のように書き換える。

from django.shortcuts import render  
from django.http import HttpResponse  
from app.functions import process_files
from app.functions import write_into_csv
from app.forms import ClassroomForm  
from django.template import loader
import csv

def index(request):  
    if request.method == 'POST':  
        classroom = ClassroomForm(request.POST, request.FILES)
        #classroomにデータがある場合
        if classroom.is_valid():  
            availability = request.FILES['availablity']
            reservation = request.FILES['reservation']
            #implement process_files
            csv_data = process_files(availability, reservation)
            #download result as a csv format             
            response = write_into_csv(csv_data)

            return response
    else:       
        ###ClassroomFormをindex.htmlに渡す
        classroom = ClassroomForm()  

        return render(request,"index.html",{'form':classroom})  

ここでは、functions.pyで定義されたprocess_fileswrite_into_csvという2つのメゾットを呼び出している。次でこのメゾットを定義する。

ロジックを定義

functions.pyにてviews.pyで使用するロジックを定義する。

import csv
from django.http import HttpResponse  

### Process csv files
def process_files(availability, reservation):  

        """ Description
        :type availability:
        :param availability:

        :type reservation:
        :param reservation:

        :raises:

        :rtype:
        """
        availability_dict = convert_to_dict(availability)
        reservation_dict = convert_to_dict(reservation)

        csv_data = []

        courses = list(availability_dict)

        for course in courses:
            remaining = availability_dict[course] - reservation_dict[course]
            row = [course, remaining]
            csv_data.append(row)
        return csv_data

def convert_to_dict(file):

        """ Description
        :type file:
        :param file:

        :raises:

        :rtype:
        """
        data = file.read().decode("utf-8-sig")
        lines = data.split("\r\n")
        dict = {}
        for line in lines:  
            fields = line.split(",")
            course = fields[0]
            capacity = int(fields[1])
            dict[course] = capacity

        return dict


def write_into_csv(csv_data):
        """ Description
        :type csv_data:
        :param csv_data:

        :raises:

        :rtype:
        """
        response = HttpResponse(content_type='text/csv')
        response['Content-Disposition'] = 'attachment; filename="download.csv"'
        writer = csv.writer(response)  

        for row in csv_data:
            writer.writerow(row)

        return response

以上で設計通りの機能を実装できた。

Demo

最後に

参考になったらぜひLikeをしてください。

Github

参考

https://www.javatpoint.com/django-file-upload
https://codepen.io/adamlaki/pen/VYpewx
https://www.pythoncircle.com/post/30/how-to-upload-and-process-the-csv-file-in-django/
https://into-the-program.com/customize-input-type-file/
https://docs.djangoproject.com/en/3.0/topics/forms/