【Django】CSVファイルをインポートしてDBにデータを登録する

はじめに

(MySQLとか使ってて)初期データをインポートしたい時なんかはHeidiSQLみたいなクライアントソフトを使えばいいんですが、勉強がてら作ってみました。

モデルの例

こんなモデルを用意してみました。
WeaponテーブルのデータをCSVでインポートしたいと思います。
外部キーも使いたかったので、サブウェポンとスペシャルを無理やりテーブルにして外部キーとして参照させています。

models.py
class SubWeapon(models.Model):
    # TODO: Define fields here
    name = models.CharField('名前', blank=True, max_length=100)
    overview = models.TextField('概要', blank=True)


    class Meta:
        verbose_name = 'サブウェポン'
        verbose_name_plural = 'サブウェポン一覧'

    def __str__(self):
        return(self.name)


class Special(models.Model):
    # TODO: Define fields here
    name = models.CharField('名前', blank=True, max_length=100)
    overview = models.TextField('概要', blank=True)

    class Meta:
        verbose_name = 'スペシャル'
        verbose_name_plural = 'スペシャル一覧'

    def __str__(self):
        return(self.name)


class Weapon(models.Model):
    # TODO: Define fields here
    no = models.IntegerField('番号', blank=True, null=True)
    name = models.CharField('ブキ名', blank=True, max_length=100)
    CATEGORY = (
        ('0', 'シューター'),
        ('1', 'マニューバー'),
        ('2', 'ブラスター'),
        ('3', 'スピナー'),
        ('4', 'ローラー'),
        ('5', 'フデ'),
        ('6', 'チャージャー'),
        ('7', 'スロッシャー'),
        ('8', 'シェルター'),
        ('9', 'ガイザー'),
    )
    category = models.CharField(blank=True, max_length=2, choices=CATEGORY)
    sub = models.ForeignKey(SubWeapon, on_delete=models.PROTECT, null=True, blank=True)
    special = models.ForeignKey(Special, on_delete=models.PROTECT, null=True, blank=True)
    gauge = models.IntegerField('スペシャル必要ポイント', blank=True, null=True)
    release_condition = models.CharField('解放条件', blank=True, max_length=100)
    range = models.IntegerField('射程', blank=True, null=True)
    continuous_power = models.IntegerField('連射力', blank=True, null=True)
    fixed_num = models.IntegerField('確定数', blank=True, null=True)

    class Meta:
        verbose_name = 'ブキ'
        verbose_name_plural = 'ブキ一覧'

    def __unicode__(self):
        return(self.name)

ちなみにスプラトゥーン2のブキは2018/4/17時点で80種類近く登場しているので、これらの情報をDjangoの管理画面から1つ1つ登録するなんてのは愚の骨頂です。

CSVファイルの用意

作ったモデルを元に、以下のようなCSVファイルを用意します。
※実際にインポートするときはヘッダー行は消してください。

また、models.pycategoryをchoicesで指定し、その選択肢を0~9の数字で設定したため、カテゴリの列には数字を入力する必要があります。
選択肢を数字ではなく、文字列にしておけばそのまま文字列で大丈夫なはずです。(未検証)

#番号,名前,カテゴリ,サブウェポン,スペシャル,スペシャル必要ポイント,解放条件,射程,連射力,確定数
101,わかばシューター,0,スプラッシュボム,インクアーマー,180,最初から,35,75,4
102,もみじシューター,0,ロボットボム,アメフラシ,180,ランク4,35,75,4
103,スプラシューター,0,クイックボム,スーパーチャクチ,200,ランク2,50,60,3
104,スプラシューターコラボ,0,スプラッシュボム,ジェットパック,210,ランク4,50,60,3
:
:

views.py

続いて views.py を書いていきます。

views.py
from django.shortcuts import render
from .models import SubWeapon, Special, Weapon
import csv
from io import TextIOWrapper, StringIO


def upload(request):
    if 'csv' in request.FILES:
        form_data = TextIOWrapper(request.FILES['csv'].file, encoding='utf-8')
        csv_file = csv.reader(form_data)
        for line in csv_file:
            weapon, created = Weapon.objects.get_or_create(name=line[1])
            weapon.no = line[0]
            weapon.name = line[1]
            weapon.category = line[2]
            weapon.sub = SubWeapon.objects.get(name=line[3])
            weapon.special = Special.objects.get(name=line[4])
            weapon.gauge = line[5]
            weapon.release_condition = line[6]
            weapon.range = line[7]
            weapon.continuous_power = line[8]
            weapon.fixed_num = line[9]
            weapon.save()

        return render(request, 'YpurApp/upload.html')

    else:
        return render(request, 'YourApp/upload.html')

最初にモデルやら必要なモジュールやらを読み込んでおきます。
ポイントはhtmlの<input>タグで指定したnameを拾ってくるのでここの指定を間違えないことです。(上の例ではcsvと指定しています。)
request.FILESで受け取ったCSVファイルをcsv.readerで読み込み、読み込んだファイルをfor文で回し、1行ずつ取り出します。

lineはリスト形式でCSVの1行のデータが1つずつ入っています。
line[0]は番号、line[1]は名前、といった感じ。

weapon, created = Weapon.objects.get_or_create(name=line[1])

データの重複登録を避けるため、get_or_createを使ってWeaponテーブルに既に同じ名前のものが存在していれば新たにレコードは登録せずにgetするだけにします。

外部キーであるsubspecialはそれぞれのテーブルからCSVに書かれたデータと一致するものをgetすればセット可能です。

weapon.sub = SubWeapon.objects.get(name=line[3])
weapon.special = Special.objects.get(name=line[4])

for文の処理が全て終わればテンプレートを描画します。
今回はサクセスメッセージなどは表示させず、単純にアップロード画面を表示するだけにしています。

urls.py

ブラウザからアクセスできるようにマッピングします。

urls.py
from django.urls import path
from . import views

app_name = 'YourAppName'
urlpatterns = [
    # :
    # :
    path('upload/', views.upload, name='upload'),
    # :
    # :
]

upload.html

標準のファイル選択ボタンは味気ないのでDropifyを利用しています。
↓このサイトを参考にさせて頂きました。
あのダサいデザインとはおさらば! input[type=file] をかっこ良く表示できる jQuery プラグイン『Dropify』の使い方

あとCSSフレームワークにMaterializeも利用しています。

{% extends "base.html" %}
{% load static %}
{% block title %}ブキデータアップロード{% endblock %}
{% block extrahead %}
<script>
$(function(){
  $('.dropify').dropify();
})
</script>
{% endblock %}

{% block content %}
<h3>アップロードするよ</h3>
<form action="{% url 'YourAppName:upload' %}" method="post" enctype="multipart/form-data">
  {% csrf_token %}
  <input type="file" class="dropify" data-default-file="ファイルを選択してください" name="csv">
  <button type="submit" class="btn" name="button">アップロード</button>
</form>

{% endblock content %}

2018-04-17_15h15_18.png

完成

あとはこの画面からCSVファイルをインポートしてエラーが出なければOKです。
Djangoの管理画面にログインし、データがと登録されているか確認しましょう。

2018-04-17_15h42_47.png

無事登録できました。
※Django IDが3桁になってるのは色々試行錯誤した結果です。

終わりに

本当はアップロードされたファイルの拡張子がちゃんとCSVかどうかとか、入力データが正しいかとか、ちゃんとバリデーションチェックしないといけませんが、今回は一切考慮していません。
本番で提供するアプリケーションではもっとちゃんと実装しましょう(自戒)

あとCSVデータ作るのが一番しんどかった。

Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account log in.