LoginSignup
5
11

More than 5 years have passed since last update.

Python + Django + ag-grid で一括処理

Last updated at Posted at 2019-02-03

「Python + Django + SlickGrid で一覧表示」で、gridライブラリを使った一覧表示を試してみましたが、Slick Gridより、簡単に実装できる「ag-grid」というライブラリに出会ったので、Djangoと連携する例を投稿しておきます。

1.インストール

ag-gridは、javascript, angular, react, vueなどで使用できると書いてありますが、今回は、javascript版を使用します。

(1)ダウンロード

GitHUBからダウンロードします。必要なcss, jsファイルは、distフォルダの中にあります。

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

Djangoなので、サンプルアプリケーションを作成します。

  • 今回のメインとなるアプリケーションを作成する
python manage.py startapp hellojson2
  • db用のアプリケーションを使います。

Python+DjangoでDBの検索処理をモジュール化してみる

python manage.py startapp commons
  • model用のアプリケーション

modelはすべてのアプリケーションで使うので、別でアプリケーションを作成しておきます。

python manage.py startapp entities
  • アプリケーションの構成

以下のようなファイル構成になります。

staticフォルダの下のcss, jsフォルダに、ダウンロードしたag-gridのcss, jsを配置します。
※ ag-gridのほかに、JQueryも使います。bootstrapは好みに合わせて配置してください。

myproject
│  db.sqlite3
│  manage.py
│
├─commons
・・・
│  │  dbutils.py
・・・
│          
├─entities
│  │  admin.py
│  │  models.py
・・・
├─hellojson2
│  │  admin.py
│  │  apps.py
│  │  models.py
│  │  tests.py
│  │  urls.py
│  │  views.py
・・・
├─myproject
│  │  settings.py
│  │  urls.py
│  │  wsgi.py
│  │  __init__.py
・・・
├─static
・・・
│  ├─css
│  │      ag-grid.css
│  │      ag-theme-balham.css
│  │      bootstrap.min.css
│  │      bootstrap.min.css.map
・・・
│  ├─js
│  │      ag-grid-community.min.noStyle.js
│  │      bootstrap.bundle.min.js
│  │      bootstrap.bundle.min.js.map
│  │      jquery-3.3.1.min.js
・・・

2.アプリケーションの概要

  • 従業員マスタのメールアドレスを一括更新するアプリケーションです。
  • 画面に、従業員マスタの一覧を表示し、ユーザは、更新対象とする行をチェックして更新します。
  • 初期表示時は、一覧は表示されません。検索ボタンで表示します。
  • 更新ボタンを押すと、従業員マスタのメールアドレスを「bbb@localhost.com」に変更します。

3.実装

(1)model

  • modelの作成
entities\models.py
from django.db import models

# Register your models here
class Department(models.Model):
    """
    所属マスタ
    """
    deptid = models.CharField(max_length=16)
    deptname = models.CharField(max_length=64)
    upperdeptid = models.CharField(max_length=16)
    def __str__(self):
        return self.deptname

class Employee(models.Model):
    """
    従業員マスタ
    """
    empid = models.CharField(max_length=16)
    empname = models.CharField(max_length=64)
    deptid = models.CharField(max_length=16)
    mailaddress = models.CharField(max_length=128)
    def __str__(self):
        return self.empname
  • プロジェクトにアプリケーションを追加する
myproject\settings.py
INSTALLED_APPS = [
・・・
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'bootstrap4', #追加
    'entities', #追加
    'hellojson2', #追加
]
  • migrate
python manage.py makemigrations entities
python manage.py migrate entities

(2)HTML

htmlのコメントにポイントを入れています。

hellojson2\templates\hellojson2\index.html
{% extends 'commons/base.html' %}

{% load static %}

{% block links %}
<link rel="stylesheet" href="{% static 'css/ag-grid.css' %}" type="text/css"/>
<link rel="stylesheet" href="{% static 'css/ag-theme-balham.css' %}" type="text/css"/>
<style media="screen">
  .form-control{border: none !important;}
</style>
<script src="{% static 'js/ag-grid-community.min.noStyle.js' %}"></script>
{% endblock %}

{% block headertitle %}
一括更新 サンプル
{% endblock %}

{% block content %}
<br/>
<div class="form-control">
  <p>選択した行のメールアドレスを一括更新します。</p>
</div>
<div class="form-control text-right">
  <button class="btn btn-sm btn-primary" onclick="search(); return false;">検索</button>
  <button class="btn btn-sm btn-primary" onclick="sendData(); return false;">更新</button>
</div>
<div id="myGrid" style="height: 600px; width:100%; margin-top:10px;" class="ag-theme-balham"></div>

<script type="text/javascript">

  // ※DjangoとAjax通信するときは、CSRF対策をしておく必要がある。
  // django CSRF対策 ここから
  function getCookie(name) {
      var cookieValue = null;
      if (document.cookie && document.cookie != '') {
          var cookies = document.cookie.split(';');
          for (var i = 0; i < cookies.length; i++) {
              var cookie = jQuery.trim(cookies[i]);
              // Does this cookie string begin with the name we want?
              if (cookie.substring(0, name.length + 1) == (name + '=')) {
                  cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
                  break;
              }
          }
      }
      return cookieValue;
  }

  var csrftoken = getCookie('csrftoken');

  function csrfSafeMethod(method) {
      // these HTTP methods do not require CSRF protection
      return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
  }

  $.ajaxSetup({
      crossDomain: false, // obviates need for sameOrigin test
      beforeSend: function(xhr, settings) {
          if (!csrfSafeMethod(settings.type)) {
              xhr.setRequestHeader("X-CSRFToken", csrftoken);
          }
      }
  });
  // django CSRF対策 ここまで

  //検索ボタン
  function search(){
    //fetchを
    fetch("{% url 'hellojson2:getlist' %}").then(function(response) {
      return response.json();
    }).then(function(data) {
      gridOptions.api.setRowData(data);
    })
  }

  //更新ボタン
  function sendData() {

    //ag-grid:選択した行の一覧を取得する。
    var selectedNodes = gridOptions.api.getSelectedNodes()
    var selectedData = selectedNodes.map( function(node) { return node.data })

    if (selectedData.length == 0) {
      alert("選択してください。")
      return;
    }
    //fetchではなく、JQuery Ajaxを使う例。
    $.ajax('/hellojson2/senddata/',
      {
        type: 'post',
        data: { query: JSON.stringify(selectedData) },
        'dataType': 'json'
      }
    )
    // 更新成功時に{"status": "OK"}が返ってくる
    .done(function(data) {
      alert(JSON.stringify(data));
    })
    // 検索失敗時には、その旨をダイアログ表示
    .fail(function( jqXHR, textStatus, errorThrown ) {
      window.alert('通信エラーが発生しました。 status=' + textStatus);
    })
    .always(function( jqXHR, textStatus ) {
    });

  }

  // ag-grid: 列定義
  // empidにcheckboxSelectionをセットすることで、checkboxを表示
  // headerCheckboxSelectionは、ヘッダにcheckboxを表示
  // empidは、リンクボタンで表示するようにしています。
  var columns = [
    {headerName: "従業員コード", field: "empid", headerCheckboxSelection: true, checkboxSelection: true, filter: true, editable: true,
      cellRenderer: function(params) {
        //将来的には、詳細表示などのURLをセットする。ひとまずgoogleのサイトを開く
        return '<a href="https://www.google.com" target="_blank">'+ params.value+'</a>'
      },
    },
    {headerName: "氏名", field: "empname", filter: true},
    {headerName: "メールアドレス", field: "mailaddress", filter: true},
    {headerName: "所属コード", field: "deptid", filter: true},
    {headerName: "所属名", field: "deptname", filter: true}
  ];

  //ag-grid: データ(JSON)
  var data = {{data|safe}}

  //ag-grid: Options
  //複数行選択にしています。
  var gridOptions = {
    columnDefs: columns,
    rowSelection: 'multiple'
//    rowData: data
  };

  // ag-grid: lookup the container we want the Grid to use
  var eGridDiv = document.querySelector('#myGrid');

  // ag-grid: create the grid passing in the div to use together with the columns & data we want to use
  new agGrid.Grid(eGridDiv, gridOptions);

  // ag-grid: 初期表示時のデータをセットしておく。
  gridOptions.api.setRowData(data);

</script>
{% endblock %}

(3)ルーティング

hellojson2\urls.py
from django.urls import path
from . import views

app_name = 'hellojson2'
urlpatterns = [
    path('', views.index, name='index'),                    # 初期表示
    path('getlist/', views.getlist, name='getlist'),        # 検索
    path('senddata/', views.senddata, name='senddata'),     # 更新
]

(4)view.py

hellojson2\views.py
from django.shortcuts import render, get_object_or_404, redirect
from django.http import HttpResponse
import json
from commons.dbutils import exec_query
from django.views.decorators.csrf import ensure_csrf_cookie
from entities.models import Employee

#list
def index(request):
    data = []
    jsondata = json.dumps(data, ensure_ascii=False, indent=2)
    return render(request,
                  'hellojson2/index.html',
                  {'form_name': 'hellojson2', 'data': jsondata})

def getlist(request):
    sqltext="""SELECT
        a.id
      , a.empid
      , a.empname
      , a.deptid
      , a.mailaddress
      , b.deptname
    FROM
      public.entities_employee a
    INNER JOIN
      public.entities_department b
      on a.deptid=b.deptid
    ORDER BY
      a.id
        ;  """
    emplist=exec_query(sqltext);

    jsondata = json.dumps(emplist, ensure_ascii=False);

    return HttpResponse(jsondata)

@ensure_csrf_cookie
def senddata(request):
    txt = request.POST['query']
    datas = json.loads(txt)

    for element in datas:
#        print(element)
        entity = Employee.objects.filter(id=element['id']).first()
        entity.mailaddress = 'bbb@localhost.com'
        entity.save()

    data = {'status': 'OK'}

    json_str = json.dumps(data, ensure_ascii=False, indent=2)

    return HttpResponse(json_str)

(5)プロジェクトのルーティング

最後に、myproject\urls.pyに、アプリのルーティングを追加します。

myproject\urls.py
urlpatterns = [
    path('hellojson2/', include('hellojson2.urls')),   # ←ここを追加
    path('admin/', admin.site.urls),
]

4.動作確認

  • webサーバ起動
python manage.py runserver
  • アプリにアクセスしてみます。

image.png

5.最後に

Slick Gridでここまでやろうと思ったら、pluginの追加やjavascriptの実装が複雑になりがちですが、ag-gridは実装も少なくてとってもいい感じです。大量データの表示も高速に表示できるので、私的にはag-gridかなと思いました。

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