Python + Django でデータベースのテーブルをCSV形式でダウンロードし、そのCSVを編集してアップロードし、データベースのテーブルを更新します
マスタの一括登録処理を作成するようなケースです。
以下の続きです。
Python + DjangoをWindowsにインストールする
[Python + Django + psycopg2でposgreSQLに接続する]
(https://qiita.com/tiguchi919/items/0fd7bf799571330f0219)
[Python+Django+psycopg2で内部結合クエリを試す]
(https://qiita.com/tiguchi919/items/827865481e82bb32ad04)
※プロジェクト名やbase.htmlは少し変更しています。
###1.開発環境
OS=windows10 64bit home
python=3.6.5
Django=2.0.7
psycopg2=2.7.4
###2.概要
下図のような処理を実装します。
【解説】
(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
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
プロジェクトにアプリを追加します
・・・
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)にもテーブルを追加しておきます。
(venv) C:\data\python\myproject>python manage.py makemigrations csvdownload
(venv) C:\data\python\myproject>python manage.py migrate csvdownload
(4)csvdownload\forms.py
from django import forms
class CSVUploadForm(forms.Form):
file = forms.FileField(label='CSVファイル')
(5)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
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
{% 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
{% 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
再掲しておきます。
{% load static %}
{% 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
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/にアクセスします
(2)エクスポート
エクスポートでCSVをダウンロードします