Edited at

Python + Django でCSVのダウンロード/アップロード

Python + Django でデータベースのテーブルをCSV形式でダウンロードし、そのCSVを編集してアップロードし、データベースのテーブルを更新します

マスタの一括登録処理を作成するようなケースです。

以下の続きです。

Python + DjangoをWindowsにインストールする

Python + Django + psycopg2でposgreSQLに接続する

Python+Django+psycopg2で内部結合クエリを試す

※プロジェクト名やbase.htmlは少し変更しています。


1.開発環境

OS=windows10 64bit home

python=3.6.5

Django=2.0.7

psycopg2=2.7.4


2.概要

下図のような処理を実装します。

image.png

【解説】

(1)インポート

まず、テーブルの一覧画面で、①インポートボタンを押して、ファイル選択画面を表示し、②CSVを選択し

③送信ボタンを押すと、CSVのデータをテーブルに登録します。

(2)エクスポート

同様に、テーブル一覧画面で①エクスポートを押すと、テーブルを全件検索してCSVを作成して画面にダウンロードします。(1)で利用するCSVファイルとして利用するイメージです。


3.アプリケーション作成

(1)アプリケーションを作成する

(venv) C:\data\python\myproject>python manage.py startapp csvdownload

(2)アプリの構成

以下、作成後のフォルダ、ファイル構成です。「#追加」は、startappでは作成されないので追加しています

csvdownload

│ admin.py
│ apps.py
│ forms.py #追加
│ models.py
│ tests.py
│ urls.py #追加
│ views.py
│ __init__.py

・・・
├─templates #追加
│ └─csvdownload #追加
│ import.html #追加
│ list.html #追加
・・・
├─myproject
│ │ settings.py
│ │ urls.py
│ │ wsgi.py
・・・
├─static
│ ├─css
│ │ bootstrap.min.css
│ │ bootstrap.min.css.map
│ │ style.css
│ │
│ └─js
│ bootstrap.bundle.min.js
│ jquery-3.3.1.min.js

├─templates
・・・
│ ├─commons
│ │ base.html


4.実装

(1)csvdownload\models.py


csvdownload\models.py

from django.db import models

class Post(models.Model):
"""役職マスタ"""
name = models.CharField('役職名', max_length=50)

def __str__(self):
return self.name


(2)myproject\setting.py

プロジェクトにアプリを追加します


myproject\setting.py

・・・

INSTALLED_APPS = [
・・・・・
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'bootstrap4',
・・・・・
'csvdownload', #追加
]
・・・

(3)migrate

migrateで、modelの追加と、データベース(postgreSQL)にもテーブルを追加しておきます。


cmd.prompt

(venv) C:\data\python\myproject>python manage.py makemigrations csvdownload

(venv) C:\data\python\myproject>python manage.py migrate csvdownload

(4)csvdownload\forms.py


csvdownload\forms.py

from django import forms

class CSVUploadForm(forms.Form):
file = forms.FileField(label='CSVファイル')


(5)csvdownload\views.py


csvdownload\views.py

import csv

import io
import urllib
from django.http import HttpResponse
from django.urls import reverse_lazy
from django.views import generic
from .forms import CSVUploadForm
from .models import Post

class Index(generic.ListView):
"""
役職テーブルの一覧表作成
"""

model = Post
template_name = 'csvdownload/list.html'

def get_context_data(self, **kwargs):
ctx = super().get_context_data(**kwargs)
ctx['form_name'] = 'csvdownload'
return ctx

class PostImport(generic.FormView):
"""
役職テーブルの登録(csvアップロード)
"""

template_name = 'csvdownload/import.html'
success_url = reverse_lazy('csvdownload:index')
form_class = CSVUploadForm

def get_context_data(self, **kwargs):
ctx = super().get_context_data(**kwargs)
ctx['form_name'] = 'csvdownload'
return ctx

def form_valid(self, form):
"""postされたCSVファイルを読み込み、役職テーブルに登録します"""
csvfile = io.TextIOWrapper(form.cleaned_data['file'])
reader = csv.reader(csvfile)
for row in reader:
"""
役職テーブルを役職コード(primary key)で検索します
"""

post, created = Post.objects.get_or_create(pk=row[0])
post.name = row[1]
post.save()
return super().form_valid(form)

def PostExport(request):
"""
役職テーブルを全件検索して、CSVファイルを作成してresponseに出力します。
"""

response = HttpResponse(content_type='text/csv; charset=Shift-JIS')
filename = urllib.parse.quote((u'CSVファイル.csv').encode("utf8"))
response['Content-Disposition'] = 'attachment; filename*=UTF-8\'\'{}'.format(filename)
writer = csv.writer(response)
for post in Post.objects.all():
writer.writerow([post.pk, post.name])
return response


(6)csvdownload\urls.py


csvdownload\urls.py

from django.urls import path

from . import views

app_name = 'csvdownload'
urlpatterns = [
path('', views.Index.as_view(), name='index'),
path('import/', views.PostImport.as_view(), name='import'),
path('export/', views.PostExport, name='export'),
]


(7)csvdownload\templates\csvdownload\list.html


list.html

{% extends 'commons/base.html' %}

{% block headertitle %}
CSVインポートエクスポート
{% endblock %}

{% block content %}
<div class="col-sm">
<nav class="pull-right header">
<ul id="menu">
<li><a href="{% url 'csvdownload:import' %}" class="">インポート</a></li>
<li><a href="{% url 'csvdownload:export' %}" class="">エクスポート</a></li>
</ul>
</nav>
</div>
<table class="table table-sm table-striped">
<thead>
<tr class="text-secondary bg-warning">
<th class="" style="width:25%;">役職コード</th>
<th class="">役職名</th>
</tr>
</thead>
<tbody>
{% for post in post_list %}
<tr>
<td>{{ post.pk }}</td>
<td>{{ post.name }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endblock %}


(8)csvdownload\templates\csvdownload\import.html


import.html

{% extends 'commons/base.html' %}

{% block headertitle %}
CSVインポート
{% endblock %}

{% block content %}
<form action="" method="POST" enctype="multipart/form-data">
<p>ファイルを選択ボタンをクリックして、アップロードするCSVファイルを選択します</p>
{% csrf_token %}
<div class="form-group">
{{form.file}}
</div>
<br/>
<button type="submit">送信</button>
</form>
{% endblock %}


(9)templates\commons\base.html

再掲しておきます。


templates\commons\base.html

{% load staticfiles %}

{% load bootstrap4 %}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" type="text/css" href="{% static 'css/bootstrap.min.css' %}">
<link rel="stylesheet" type="text/css" href="{% static 'css/style.css' %}">
<title>
{% block title %}{% endblock %}
</title>
<style>
body {
padding : 3px;
}
div.container {
min-width:600px;
}
nav.header ul#menu {
margin:0px;
padding:0px;
}
nav.header ul#menu li {
float:right;
list-style-type:none;
text-decoration: underline;
padding-left: 10px;
}
div.header {
padding: 3px;margin-bottom: 5px;
}
div.header * {
color: white !important;
}
div.header .title {
font-size: 1.2em;
}
{% block styles %}{% endblock %}
</style>
</head>
<body>
<div class="container">
<div class="inner">
<div class="text-right">{{ form_name }}</div>
<div class="row header mysystem-header">
<div class="col-sm title">
{% block headertitle %}{% endblock %}
</div>
<div class="col-sm">
<nav class="pull-right header">
<ul id="menu">
<li><a href="" onclick="javascript:history.go(-1); return false;">戻る</a></li>
{% if user.is_authenticated %}
<li><a href="{% url 'logout' %}" class="logout">ログアウト</a></li>
{% if form_name == "password_change" %}
{% else %}
<li><a href="{% url 'accounts:password_change' %}" class="">パスワード変更</a></li>
{% endif %}
{% else %}
{% endif %}
<li><a href="{% url 'accounts:index' %}" class="">TOP</a></li>
</ul>
</nav>
</div>
</div>
<div class="row content">
{% block content %}{% endblock %}
</div>
</div>
</div>

<script src="{% static "js/jquery-3.3.1.min.js" %}"></script>
<script src="{% static "js/bootstrap.bundle.min.js" %}"></script>
{% block scripts %}{% endblock %}

</body>
</html>



5.プロジェクトにアプリのURLを追加する

(1)myproject\urls.py


myproject\urls.py

from django.contrib import admin

from django.urls import path, include

urlpatterns = [
・・・・
path('csvdownload/', include('csvdownload.urls')), #"追加")
path('admin/', admin.site.urls),
]



6.動作確認

(1)(venv) C:\data\python\myproject>manage.py runserver

http://localhost:8000/csvdownload/にアクセスします

image.png

(2)エクスポート

エクスポートでCSVをダウンロードします

image.png

(3)CSV

ダウンロードしたファイルに1行追加して、インポートしてみます。

image.png

(4)インポート

編集したファイルを指定して送信します。

image.png

(5)一覧画面で登録確認

更新に成功しています

image.png