python3.doc_63
R e i - D r e a m
for Laravel
TOP > Python3 > Django(CSV UpDownLoad)
Guest
login

最終投稿日:2022年6月18日

Django(CSV UpDownLoad)
CSV ダウンロード、CSV アップロードして DB 登録
[要件]
    DB の内容をダウンロードする、又はアップロードして DB に反映させる
    ダウンロード、アップロード共に【タイトル行】【ダブルクォーテーション】の有無を選択できる

[DB 内容]
    テーブル名:books
    項目内容:
                ID      連番
                title   タイトル
                price   金額
                memo    備考
アプリ名は【booktitle】とします

● アプリの作成
    python manage.py startapp booktitle

● settings.py
    INSTALLED_APPS = [
        ~ 省略 ~
        'booktitle.apps.BooktitleConfig'
    ]

● models.py
    from django.db import models
    class Books(models.Model):
        class Meta:
            #本当の DB 名
            db_table = 'books'
            verbose_name = '本管理'
        title = models.CharField(max_length=50)
        price = models.IntegerField()
        memo = models.TextField(null=True)

    ※定義の後はお約束をコマンドプロンプトから叩く
        python manage.py makemigrations
        python manage.py migrate

● プロジェクト/urls.py
    from django.contrib import admin
    from django.conf.urls import include, url
    from django.views.generic import RedirectView
    from config import backends
    urlpatterns = [
        url(r'^$', RedirectView.as_view(url='booktitle/')),
        url(r'^booktitle/', include('booktitle.urls', namespace='booktitle')),
    ]

● アプリ/urls.py
    from django.conf.urls import url
    from django.views.generic import RedirectView
    from .views import *
    app_name = 'booktitle'
    urlpatterns = [
        url(r'^$', RedirectView.as_view(url='index/')),
        url(r'^index/$', IndexView.as_view(), name='index'),
    ]

● my_lib/my_common/file_common.py
    """
    モデルのフィールド名をリストで返却する
    """
    def csvTitleCreate(ct):
        return [v.name for v in ct._meta.fields]

● forms.py
    from django import forms
    class BookForm(forms.Form):
        dq_chk = forms.BooleanField(
            label = '「"」で囲う',
            required = False,
        )
        tit_chk = forms.BooleanField(
            label = 'タイトル行を含む',
            required = False,
        )
        csv = forms.FileField(
            label = 'CSVインポート',
            required = False,
        )

● views.py
    from django.shortcuts import render
    from django.http.response import HttpResponse
    from django.http.response import HttpResponseRedirect
    from django.urls import reverse
    from django.views.generic import View
    from .models import Books
    from .forms import BookForm
    from io import TextIOWrapper, StringIO
    import csv
    import codecs
    from my_common import file_common as fcmmon

    class IndexView(View):
        def get(self, request):
            form = BookForm()
            row = Books.objects.all()
            d = {'form':form,'row':row}
            return render(request, 'booktitle/index.html', d)

        def post(self, request):
            # ダウンロード
            #ダウンロードボタンが押された場合
            if 'down' in request.POST:
                #StringIO クラスを使いバッファに CSV の内容をため込む準備をする
                memory_file = StringIO()
                #ダブルクォーテーションの有無の確認
                if 'dq_chk' in request.POST:
                    #StringIO クラスのインスタンスを引数として
                    #CSV クラスの writer メソッドを実行する
                    writer = csv.writer(memory_file,quoting=csv.QUOTE_ALL)
                else:
                    writer = csv.writer(memory_file)
                #タイトル行の有無の確認
                if 'tit_chk' in request.POST:
                    # タイトル作成
                    #タイトル行が必要な場合は独自メソッドよりタイトルのカラム名をリスト型で取得
                    title = fcmmon.csvTitleCreate(Books)
                    #インスタンス writer の writerow メソッドにタイトル行を追加する
                    writer.writerow(title)
                #DB の内容を全て取得する
                for row in Books.objects.all():
                    #レコード単位でリスト型にする
                    line = [row.id, row.title, row.price, row.memo]
                    #インスタンス writer の writerow メソッドに
                    #リストを追加する(DB の全内容ループ)
                    writer.writerow(line)
                #HTTP ヘッダーの設定
                response = HttpResponse(memory_file.getvalue(), content_type='text/csv')
                #HTTP ヘッダーの設定
                response['Content-Disposition'] = 'attachment; filename=books.csv'
                #設定した HTTP ヘッダーを出力する
                return response
            # アップロード
            else:
                #ファイルが選択されているか確認
                if 'csv' not in request.FILES:
                    form = BookForm(request.POST)
                    form.add_error('csv', 'CSVファイルを選択してください。')
                    d = {'form':form}
                    #ファイルが選択されていない場合は、index.html に戻す
                    return render(request, 'booktitle/index.html', d)
                #TextIOWrapper クラスを使いメモリ上のファイルを引数に初期化する
                form_data = TextIOWrapper(request.FILES['csv'], encoding='utf-8')
                #CSV クラス reader メソッドで初期化したインスタンスを読み込む
                csv_file = csv.reader(form_data)
                #タイトル行の有無を確認
                if 'tit_chk' in request.POST:
                    #タイトル行がある場合はイテレータを1行スキップする
                    next(csv_file)
                try:
                    #CSV ファイルを1行単位で処理
                    for line in csv_file:
                        #get_or_create メソッドで DB を主キーで検索しなければ新規登録する
                        #(books, _ の _ には、新規作成した場合は True が入る(使わない))
                        books, _ = Books.objects.get_or_create(
                            id=line[0],
                            #新規登録の際は、初期値を設定しないと
                            #NOT NULL 制約のカラムがあるとエラーとなる
                            defaults = {'title':' ','price':0,'memo':' '}                   
                        )
                        #それぞれのカラムに CSV ファイルの内容を登録する
                        books.title = line[1]
                        books.price = line[2]
                        books.memo = line[3]
                        #レコードを登録する
                        books.save()
                except:
                    print('失敗!タイトル行とか含んでないかな?')
                return HttpResponseRedirect(reverse('booktitle:index'))

● layout/base.html
    <html>
        <head>
            <title>{{my_title}}</title>
            <meta http-equiv="content-type" content="text/html; charset=utf-8">
        </head>
        <body>
            {% block my_body %}{% endblock %}
        </body>
    </html>

● booktitle/index.html
    {% extends 'layout/base.html' %}
    {% block my_body %}
    <div class='hoge'>いちらん画面</div>
        <table border='1'>
            <tr>
                <th>ID</th><th>タイトル</th><th>価格</th><th>備考</th>
            </tr>
        {% for col in row %}
            <tr>
                <td>{{col.id}}</td>
                <td>{{col.title}}</td>
                <td>{{col.price}}</td>
                <td>{{col.memo}}</td>
            </tr>
        {% endfor %}
        </table>
        <form method="POST" action="{% url 'booktitle:index' %}" enctype="multipart/form-data">
            {{form.as_p}}
            {% csrf_token %}
            <BR>
            <input type='submit' name='down' value='ダウンロード'>
            <input type='submit' name='up' value='アップロード'>
        </form>
    <br>
    {% endblock %}
            
ログインしてコメントを残そう!!


きっぷる