インフラジスティックス・ジャパン株式会社Blog

インフラジスティックス・ジャパン株式会社のチームメンバーが技術トレンド、製品Tips、サポート情報からライセンス、日々の業務から感じることなど、さまざまなトピックについてお伝えするBlogです。

Python + Django Rest framework + igGrid で REST API を利用したCRUDの実装

https://cdn-ak.f.st-hatena.com/images/fotolife/c/cleverdog/20200803/20200803200033.png

本記事では Python + Django で構築したウェブアプリケーションと Ignite UI for jQuery を組み合わせて、CRUD の機能を持った igGrid を実装する方法をご紹介します。

Django には Django REST framework という Web API の実装がかなり簡単にできるフレームワークがあります。

Django REST framework
https://www.django-rest-framework.org/

また、Ignite UI for jQuery の igGrid には データソースとして REST で取得したデータをバインディングすることが可能です。グリッド上の新規行追加、更新、削除の内容に基づいて適切な処理を REST に対して行います。

REST の更新 (igGrid)
https://jp.igniteui.com/help/iggrid-rest-updating

この2つの機能を組み合わせることでとても簡単に CRUD の機能を持った igGrid を実装することが可能です。

Python + Django で REST を実装する

Django において、データベースとアプリの連携までは実装できているという前提で進めていきます。 もし今回はじめて Django を触ってみようという方がいらっしゃいましたら、以下のドキュメントを参考に、「はじめての Django アプリ作成、その2」までの内容を進めたうえで以降の内容をご覧ください。

Django 2.2 ドキュメント
https://docs.djangoproject.com/ja/2.2/contents/

また今回は、

  • python 3.7
  • Django 2.2.14
  • Ignite UI for jQuery 20.1

にて実装を行なっています。

Django REST framework のインストールと初期設定

django-filter も合わせてインストールします。

$ pip install djangorestframework
$ pip install django-filter
# setting.py

INSTALLED_APPS = [
    ...
    'rest_framework',
]

API 用のアプリを作成

$ python manage.py startapp api

モデルの設定

今回は以下ような注文管理用のモデルを作成しました。

# api/models.py

from django.db import models

# Create your models here.
class Ordering(models.Model):
    Shop = models.CharField(max_length=100) #出荷名
    Address = models.CharField(max_length=200) #配送先住所
    TotoalNumber = models.IntegerField(default=0) #項目の合計数
    TotalPrice = models.FloatField(default=0) #総額

また、すでにデモ用のデータは入れ込んでいる前提で進めます。

Serializer の定義

新規に serializer.py というファイルを作成して、以下のように設定します。

# api/serializer.py
# coding: utf-8

from rest_framework import serializers
from .models import Ordering

class orderingSerializer(serializers.ModelSerializer):
    class Meta:
        model = Ordering
        fields = ('id', 'Shop', 'Address', 'TotoalNumber', 'TotalPrice')

View の設定

View ではモデルと先ほど用意した Serializer を組み合わせた ViewSet を定義します。

# api/views.py
# coding: utf-8

import django_filters
from rest_framework import viewsets, filters

from .models import Ordering
from .serializer import orderingSerializer

class orderingViewSet(viewsets.ModelViewSet):
    queryset = Ordering.objects.all()
    serializer_class = orderingSerializer

URL の設定

URL の設定をします。 まず api/urls.py を新規作成し、以下のように記述します。

# api/urls.py
# coding: utf-8

from rest_framework import routers
from .views import orderingViewSet

router = routers.DefaultRouter()
router.register(r'order', orderingViewSet)

ルートの urls.py には以下のようなルーティング設定を行います。

# urls.py

from django.contrib import admin
from django.conf.urls import url, include
from django.urls import include, path

from api.urls import router as api_router

urlpatterns = [
    path('admin/', admin.site.urls),
    url(r'^api/', include(api_router.urls)),
]

以上で Django を利用した API の準備が整いました。サーバーを起動して以下の URL にアクセスしてみます。

http://127.0.0.1:8000/api/order/

Image from Gyazo

JSON 形式でデータの一括取得が出来ていることを確認できます。 同じ要領で、以下のように ID を指定した URL とすると、

http://127.0.0.1:8000/api/order/1

Image from Gyazo

該当のデータのみ表示することが出来ますし、PUT による情報の更新や DELETE による削除の処理も行うことが可能で、データベースとの連携も対応してくれます。

これで Django 側の準備が整いましたので igGrid の実装に移っていきます。

igGrid の実装と、REST との連携

バックエンドの受け入れ態勢は整いましたので、フロントエンドの組み込みを行なっていきます。 まず igGrid 用のアプリを新たに追加します。

igGrid 用のアプリを作成

$ python manage.py startapp grid

igGrid 用のテンプレートを作成

grid/templates/grid ディレクトリにテンプレート用の html を新規作成し、編集していきます。今回は head 部分やスクリプトをパーツ分けするなどの処理はせずに、全て1枚の index.html に収める形で記述していきます。

まず必要なライブラリの読み込みは以下のように CDN から読み込む形としました。

<!-- grid/templates/grid/index.html -->

<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta http-equiv="Content-type" content="text/html; charset=utf-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>ig-grid on Django</title>

    <script src="https://ajax.aspnetcdn.com/ajax/modernizr/modernizr-2.8.3.js"></script>
    <script src="https://code.jquery.com/jquery-2.1.3.min.js"></script>
    <script src="https://code.jquery.com/ui/1.10.3/jquery-ui.min.js"></script>
    <script src="https://cdn-na.infragistics.com/igniteui/2020.1/latest/js/i18n/infragistics-ja.js"></script>
    <script src="https://cdn-na.infragistics.com/igniteui/2020.1/latest/js/infragistics.core.js"></script>
    <script src="https://cdn-na.infragistics.com/igniteui/2020.1/latest/js/infragistics.lob.js"></script>
    <script src="https://igniteui.github.io/help-samples/js/apiviewer.js"></script>
    <script src="https://cdn-na.infragistics.com/igniteui/2020.1/latest/js/modules/i18n/regional/infragistics.ui.regional-ja.js"></script>

    <!-- Used to add modal loading indicator for igGrid -->
    <script src="https://www.igniteui.com/js/grid-modal-loading-inicator.js"></script>

    <link href="https://cdn-na.infragistics.com/igniteui/2020.1/latest/css/themes/infragistics/infragistics.theme.css" rel="stylesheet">
    <link href="https://cdn-na.infragistics.com/igniteui/2020.1/latest/css/structure/infragistics.css" rel="stylesheet">
    <link href="https://igniteui.github.io/help-samples/css/apiviewer.css" rel="stylesheet" type="text/css">

    <style type="text/css">
        input.button-style
        {
            margin-top: 10px;
        }
    </style>

  </head>
  ...

また、今回の実装方法として、グリッドの各行への変更内容を一旦保持して、保存ボタンのクリックによって全ての変更をまとめてデータベースにコミットするような形としております。 本記事では一括でのコミットに関しての説明は省略いたします。以下のドキュメントに詳細が書かれておりますので参考にしていただければ幸いです。

グリッド - 編集
https://jp.igniteui.com/grid/basic-editing

更新の概要 (igGrid)
https://jp.igniteui.com/help/iggrid-updating

REST データのデータバインディング

REST をサポートするために DataSource から拡張された RESTDataSource を用いてグリッドにデータバインディングを行います。

ig.RESTDataSource
https://jp.igniteui.com/help/api/2020.1/ig.restdatasource

<!-- grid/templates/grid/index.html -->

<script type="text/javascript">
    $(function () {
        var ds = new $.ig.RESTDataSource({
            dataSource : "/api/order/",
            restSettings: {
                create: {
                    url: "/api/order/", //APIのエンドポイントを指定
                },
            }
        });
        var grid = $("#grid").igGrid({
            dataSource: ds, //RESTDataSource をバインド
            type: "json",
            columns: [
                ...
            ],
            primaryKey: "id",
            autoGenerateColumns: false,

上記の例では restSettings の create の エンドポイントに /api/order/ を指定していますので、グリッドで新規に行を作成した際に /api/order/ に対して POST を行なってくれます。 また、その他のエンドポイントを特に指定しなくても、DELETE や PUT なども適宜エンドポイントを /api/order/1 のように解釈してくれます。

ルーティングの調整をして、こちらの状態で一度テストしてみます。

Image from Gyazo

グリッドにデータの一覧を表示することが出来ました。これはREST によって GET 処理が成功したことを意味しています。

では POST の処理はどうでしょうか。新規行の追加処理のテストをしてみます。

Image from Gyazo

POST の処理はエラーとなります。 これは ajax を通して POST の処理をした際に Django 側でセキュリティの保護処理が行われたためのエラーです。クロスサイトリクエストフォージェリ(CSRF)対策のために ajax を利用する場合にはトークンを生成して付与する必要があります。

しかしながら ig.RESTDataSource にはトークンを指定する組み込みのオプションはないため、別の方法での実装が必要です。

CSRF トークンの設定

jQuery の ajaxSetup() メソッド の beforeSend パラメーターを利用することで、ajax 通信が発生する前の処理を設定することが可能です。以下のようにリクエストヘッダーの X-CSRFToken に対してトークンを指定します。

jQuery.ajaxSetup() | jQuery API Documentation

Django には CSRF 対策用のトークンが簡単に取得できるテンプレートタグがはじめから用意されているため、以下の記述をスクリプトに追加します。

//grid/templates/grid/index.html

$.ajaxSetup({
    beforeSend: function(xhr, settings) {
        xhr.setRequestHeader("X-CSRFToken", "{{ csrf_token }}");
    }
});

もう一度テストしてみましょう。

Image from Gyazo

無事新しいデータが POST されました。 データベースでも新しいデータの存在が確認できます。

% python manage.py shell
>>> from api.models import Ordering
>>> Ordering.objects.filter(id=21)
<QuerySet [<Ordering: Ordering object (21)>]>

続けて PUT と DELETE の動作テストをします。 先ほど新規に追加したデータを削除し、ID20 のデータの総額を変更します。

Image from Gyazo

すると以下のエラー文とともに、PUT の処理にエラーが発生していることが確認できます。

You called this URL via PUT, but the URL doesn't end in a slash and you have APPEND_SLASH set. Django can't redirect to the slash URL while maintaining PUT data. Change your form to point to 127.0.0.1:8000/api/order/20/ (note the trailing slash), or set APPEND_SLASH=False in your Django settings.

Django は PUT のエンドポイントの URL がスラッシュで終わっていない形の場合、エラーを返します。また、ig.RESTDataSource のデフォルトの PUT のエンドポイントは最後にスラッシュがつかない /api/order/20 のような形になるため、このエラーが発生します。

Django 側の設定変更によってスラッシュなしを受け入れることも可能ですが、ig.RESTDataSource もエンドポイントのテンプレートをカスタマイズ可能ですので、ig.RESTDataSource を修正していきます。

Django の仕様に合わせて ig.RESTDataSource の restSettings を変更

// grid/templates/grid/index.html

var ds = new $.ig.RESTDataSource({
    dataSource : "/api/order/",
    type: "json",
    restSettings: {
        create: {
            url: "/api/order/",
            template: "/api/order/"
        },
        update: {
            template: "/api/order/${id}/" //スラッシュで終わる形に変更
        },
        remove: {
            url: "/api/order/"
        }
    }
});

この時、update に対して行なったテンプレート設定が create にも及ぶため、create にも元々与えているエンドポイントと同じ URL を template の URL としても指定しておきます。

最後に、保存ボタンを押した時の処理として以下の2行を加えます。

// grid/templates/grid/index.html

grid.igGrid("commit");
grid.igGrid("dataBind");

グリッドの ID 列は、データベースのテーブル上のプライマリキーである id と対応していますが、フロントエンドで割り振られた id が、データベース上で割り振られるべき id と一致するとは限りません。そのためデータベースへのコミットを行った後に再度データバインドを行うことによって、データベース上で割り当てられた id をグリッドで反映するという処理を行っています。

今回作成したデモの最終的な振る舞いは以下のようになります。

Image from Gyazo

GET, POST, PUT, DELETE の処理を REST と igGrid によってスムーズに一括で行えるようになりました。また、今回 Django REST framework を利用することによってバックエンドとフロントエンドの役割分担が出来ている点が特徴かと思います。

今回作成したデモアプリケーションは以下にアップロードしておりますのでご興味のある方は触ってみていただければと思います。

github.com