見出し画像

django+pandasによるcsv/excelファイル入出力

こちらの記事でdjangoの管理画面からcsv/Excelデータを一括入力しデータベースへ反映させる事が出来ました。次は、管理画面を使わずにcsv/Excelファイルをアップロードし、何かしらの処理を行った後にダウンロードが出来るようにしていきます。今回の機能を使えば、例えばデータクレンジング処理をdjangoで実現する事も出来ます。Pyinstallerを使って重たいexeファイルで配布しなくても、社内の色々な人に使ってもらう事が出来るかもしれません(djangoをデプロイするというハードルがありますので、どちらが簡単かは環境によると思います)。

今回は、こちらの記事の続きとなりますので、開発の前提などはそちらを確認ください。新たなモデルは必要ありません。必要なものは、ファイルのアップロードフォームと対応するビューそしてテンプレートになります。

百聞は一見にしかずですので、出来上がりイメージを以下のGifで確認して下さい。処理の流れば、csvファイルを選択⇨ファイル形式(csv/xlsx)を選択⇨実行ボタンでdjangoへアップロード⇨django+pandasでファイルを受け取りデータ操作⇨django+pandasでファイル書き出し⇨ファイル自動ダウンロードとなります。

ファイル入出力

目標

① csv/xlsx形式のファイルをアップロード出来る。
② アップロードされたファイルをpandasで操作しcsv/xlsx形式で書き出せる。
③ csv/xlsx形式のファイルを自動ダウンロード出来る。

開発環境&必要なパッケージ

・Python 3.9.4
・django 3.2.4
・Mac M1 Big Sur
・Pycharm(Visual Studio Codeでも可)
・ブラウザはSafari(もしくはChrome)
・miniforge(ARMアーキテクチャに最適化されているため)
↑は、Windows環境であればAnacondaでも構いません。windows環境でエクセルデータの入出力が出来ることは確認しています。
・pandas
・django-bootstrap4

具体的な使い方

今回はモデルは必要ありません。以下の手順で説明していきます。

①ファイルをアップロードする FileUploadForm作成
FileUploadViewでファイル入出力機能とデータ操作を実装
③ファイル入出力画面fileupload.htmlの作成

 ①ファイルをアップロードするFileUploadForm作成

まずは、ファイルをアップロードするフォームをapp/forms.pyに定義していきます。django-bootstrap4のラジオセレクトボタンでファイル形式をcsv/xlsxで切り替え選択できるようにしますので、必要なライブラリをインポートします。
 from bootstrap4.widgets import RadioSelectButtonGroup
FileUploadForm
forms.Formを継承して作成します。file = forms.FileField()でファイルの選択フォームを設定します。csv/xlsxのファイル形式により、pandasでの入出力処理が変わってきますので、FileTypeとしてファイル形式を受け取ります。choices=(('csv', 'csv'), ('xlsx', 'xlsx'))で選択可能なファイル形式を設定します。initial='csv'で初期値をcsv形式にします。ファイル形式は、必須入力としますのでrequired=Truecsv/xlsxはラジオセレクトボタンを使用したいので、widget=RadioSelectButtonGroupとしてウィジェットを設定します。

app/forms.py

from bootstrap4.widgets import RadioSelectButtonGroup
from django import forms

class FileUploadForm(forms.Form):
   file = forms.FileField(label='ファイル')
   FileType = forms.ChoiceField(
       help_text="Select file type.",
       choices=(('csv', 'csv'), ('xlsx', 'xlsx')),
       initial='csv',
       required=True,
       widget=RadioSelectButtonGroup,
   )

 ②FileUploadViewでファイル入出力機能とデータ操作を実装

app/views.pyを開き必要なライブラリ等をインポートします。pandas、先程作成したFileUploadForm、reverse_lazyそしてファイルをダウンロードするためにHttpResponseをインポートします。
 次にFormViewを継承したFileUploadViewを作成し、ファイル入出力機能とデータ操作を設定していきます。ファイル入出力画面は、fileupload.htmlとし入力フォームは先程作成したFileUploadFormとなります。入出力後の画面は元の画面を設定しますので、reverse_lazyを用いますので以下のようにそれぞれ設定します。
  template_name = 'app/fileupload.html'
 form_class = FileUploadForm
 success_url = reverse_lazy('file-upload')

def form_valid(self, form):でフォーム入力を受け付けた後の処理を記載していきます。まずは、file = io.TextIOWrapper(form.cleaned_data['file'])でファイルをfileへ入れいます。filetype = form.cleaned_data['FileType']では、ファイル形式を受け取り、その後のif文処理でcsv/xlsxの場合でそれぞれpandasを用いでファイルをデータフレームに変換します。
 if filetype == 'csv':
  df = pd.read_csv(file, dtype=str)
 elif filetype == 'xlsx':
  df = pd.read_excel(file, dtype=str, index_col=0)

データフレームにいずれかの処理を加えます。今回はpostalCodeの先頭3桁を取り出した'pc_3dig'という列を新たにデータフレームへ追加することにします。
 df['pc_3dig'] = df['postalCode'].str[:3]

ファイルの保存とダウンロード処理を記載していきます。こちらも、csv/xlsxで若干異なりますのでif文で条件分岐させます。まずは、ファイル名をf_nameとします。日本語を使いたかったのですが、日本語ですとchromeもsafariも上手くいかなかったのでファイル名はアルファベットとします。
 HttpResponseは、csvの場合は(content_type='text/csv')とし、xlsxの場合は(content_type='application/vnd.ms-excel')としてresponseに指定します。
response['Content-Disposition'] は、ファイルとファイル名の指定となりますので最後の拡張子以外は同じとなります。pandasでのファイル書き出しは、形式毎で若干異なります。csvの場合は、df.to_csv(path_or_buf=response, sep=",", index=False, encoding="utf-8")とします。 windowsで使いたい場合は、encodingのところをuff-8⇨cp932へ変更してください。
 エクセル書き出しはdf.to_excel(excel_writer=response, sheet_name = "result" , index = False)とします。シート名やインデックス有無を設定したければ、該当箇所を変更ください。
 最後にreturn responseをhtmlへ返して書き出されたファイルが自動でダウンロードされるようになります。

app/views.py

from .forms import FileUploadForm
import pandas as pd
from django.urls import reverse_lazy
from django.http import HttpResponse

class FileUploadView(FormView):
   template_name = 'app/fileupload.html'
   form_class = FileUploadForm
   success_url = reverse_lazy('file-upload')

   def form_valid(self, form):
       # フォームから受け取ったデータをデータフレームへ変換
       # ファイル形式に応じた読み込み
       filetype = form.cleaned_data['FileType']
       file = io.TextIOWrapper(form.cleaned_data['file'])
       if filetype == 'csv':
           df = pd.read_csv(file, dtype=str)
       elif filetype == 'xlsx':
           df = pd.read_excel(file, dtype=str, index_col=0)

       # データフレームへの処理を記載
       # (今回は、postalCodeの先頭3桁を取り出した'pc_3dig'という列を追加)
       df['pc_3dig'] = df['postalCode'].str[:3]

       # ファイルの保存とダウンロード処理
       f_name = 'your_favourite_filename' # 日本語は使えない
       # ファイル形式に応じた保存とダウンロード処理
       if filetype == 'csv':
           response = HttpResponse(content_type='text/csv')
           response['Content-Disposition'] = 'attachment; filename ="' + f_name + '.csv"'
           df.to_csv(path_or_buf=response, sep=",", index=False, encoding="utf-8")
       elif filetype == 'xlsx':
           response = HttpResponse(content_type='application/vnd.ms-excel')
           response['Content-Disposition'] = 'attachment; filename ="' + f_name + '.xlsx"'
           df.to_excel(excel_writer=response, sheet_name="result", index=False)

       return response

ビューが出来ましたので、パスを以下のように通します。特に説明は不要かと思います。

app/urls.py

from django.urls import path
from .views import (
   PizzaListView,FileUploadView,# 追加
)
app_name = 'app'
urlpatterns = [
   path('pizzalist/', PizzaListView.as_view(), name='pizzalist'),  
   path('fileupload/', FileUploadView.as_view(), name='file-upload'),
]

 ③ファイル入出力画面fileupload.htmlの作成

最後にテンプレートを作成します。こちらも特別なことはしておリません。ファイルをアップするので、formタグ内はmethod="post"、enctype = "multipart/form-data"を指定します。{% csrf_token %}も忘れずに記入します。

fileupload.html

{% extends 'base.html' %}
{% block content %}
   <div class="row">
       <div class="col-sm-6 offset-sm-3">
           <form action="" method="post" enctype="multipart/form-data">
               <h4>アップロードするファイルを選択</h4>
               {% csrf_token %}
               <div class="form-group">
                {{ form.file }}
                {{ form.FileType }}
               </div>
               <button type="submit" class="btn btn-primary">実行</button>
           </form>
       </div>
   </div>
{% endblock %}

全てのコーディングが完了しましたので、ターミナルからサーバーを立ち上げてfileuploadhttp://127.0.0.1:8000/app/fileupload へアクセスし、csvをアップロードしてみます。csvのデータ(処理前)も画像で確認ください。

スクリーンショット 2021-07-11 午後00.10.16 午前

画像2

画像3

ファイル形式はデフォルトでcsvとなっていますので、実行ボタンを押せばdjangoへアップロードされ、内部でpc_3dig列を追加する処理が行われてcsv形式で書き出しが行われ、ダウンロードが自動で始まります。

画像5

ダウンロードされたファイルを開きます。ファイル名は先程FileUploadViewで設定したyour_favourite_filenamで保存され、確かにpc_3dig列が追加されている事が確認できれば成功です。

画像5


社内でデータの入力フォーマットは決まっているけれど、半角と全角がバラバラだったり、日付の入力が和暦と西暦が混ざっていたり、基幹システムの出力と部内固有のシステムの入力が揃っていないなどデータを繋ぎ活用するためにはデータクレンジングが必要になります。これまでは、都度処理してきたかもしれません。もしかしたら、VBAやマクロで一括変換してきたかもしれません。
今回の機能を使えば、上記のような処理もdjangoで非常に気軽に実現する事が出来ます。エクセルを配布する必要もなく、ブラウザ上で完結させる事が出来ます。
DB内の情報ともpandasで連携させれば活用の幅はますます広がります。社内の簡単なDB連携システムとして活用してみても良いかもしれません。

この記事が気に入ったらサポートをしてみませんか?