71
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 1 year has passed since last update.

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

Last updated at Posted at 2018-07-12

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.概要

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

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 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

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

71
80
12

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
71
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?