80
80

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

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

Posted at

はじめに

(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データ作るのが一番しんどかった。

80
80
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
80
80

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?