SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

DeveloperZine(デベロッパージン)- エンジニアの意思決定を支える技術情報メディア ProductZine

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

モダンDjango入門

Django 2.0リリース! 最新のDjangoで作るカンバンボードアプリ ~ モデルの定義とDjango REST frameworkの実装

モダンDjango入門 第2回

カンバンボード アプリケーション開発(1)

 この連載ではサンプルアプリケーションとして、カンバンボードを作成します。カンバンはアジャイル開発の現場でよく使われているタスク管理手法の1つです。完成イメージは以下の図のようになります。

カンバンボード イメージ図
カンバンボード イメージ図

 各チケットは以下のデータを持っています。

  • チケット名
  • 説明
  • ステータス(ToDo、InProgress、Done)
  • 担当者
  • 開始日
  • 終了日

アプリケーションの作成

 以下のコマンドでboardアプリケーションの初期構築を行います。このプロジェクトとアプリケーションの設計思想や構成については、前回記事で詳しく解説しています。

(env) $ mkdir kanban
(env) $ cd kanban
(env) $ django-admin startapp board

 以下のようなファイル構成になったことを確認してください。

modern-django
└──kanban
   └── board
       ├── __init__.py
       ├── admin.py
       ├── apps.py
       ├── migrations
       │   └── __init__.py
       ├── models.py
       ├── tests.py
       └── views.py

 生成された各ファイルは次のような役割を持っています。

ファイル 役割
admin.py 管理サイトの設定を記述するファイル
apps.py アプリケーションの構成情報を記述するファイル
migrations マイグレーションファイルを格納するディレクトリ
models.py データベースのスキーマを定義するファイル
tests.py テストを記述するファイル
views.py view関数を記述するファイル

アプリケーションの登録

 作成したアプリケーションをプロジェクトに登録して、プロジェクト内で使えるようにします。このためには、config/settings/base.py内のINSTALLED_APPSを更新します。

# Application definition

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'kanban.board.apps.BoardConfig', # BoardConfigを追加
]

 またここで追加したBoardConfigを、現在のプロジェクト構成に沿うように編集しておく必要があります。

from django.apps import AppConfig


class BoardConfig(AppConfig):
    name = 'kanban.board' # 初期値の'board'から変更

 以上でアプリケーションの作成および初期設定が完了しました。

モデルの定義

 カンバンボードのチケットに必要となるデータについては、先ほど確認しました。それをもとにしたER図は次のようになります。

カンバンボード ER図
カンバンボード ER図

 このテーブル定義をもとに、kanban/board/models.pyの実装を進めていきます。まずは以下のように編集しましょう。

from django.conf import settings
from django.db import models


class Ticket(models.Model):
    name = models.CharField(max_length=100)
    description = models.TextField(blank=True, default='')
    status = models.SmallIntegerField()
    assignee = models.ForeignKey(settings.AUTH_USER_MODEL, blank=True, null=True, on_delete=models.SET_NULL)
    start = models.DateField(null=True, blank=True)
    end = models.DateField(null=True, blank=True)
    created = models.DateTimeField(auto_now_add=True)
    modified = models.DateTimeField(auto_now=True)

    def __str__(self):
        return f'{self.name}'

 namedescriptionなどのモデルのフィールドは、テーブルのカラムに対応します。フィールドはテーブルのカラムがどのような型で値を保持するかを決定しています。またフィールドには、nullblankdefaultなどのオプション引数を指定できます。それぞれのオプションの意味は以下のとおりです。

  • null=True:データベースにNULLを保存することを許可します。デフォルトはFalse。
  • blank=True:フィールドの値が空であることを許容します。デフォルトはFalse。
  • default=...:デフォルト値を指定します。

 null=Trueblank=Trueは、似ていて紛らわしいので注意してください。nullはデータベースについての設定であるのに対し、blankはあくまでフォームなどでのバリデーションについての設定になります。

 これらに加えて、フィールド固有のオプション引数を指定できます。たとえばCharFieldの場合には、max_length=100のようにデータベースに格納する文字数の最大値を指定できます。

 フィールドには基本的な型だけではなく、テーブル間の関連付け(リレーション)を表現する型も用意されています。このフィールドを使うことで、テーブル間のリレーションを定義できます。

フィールド名 リレーションの種類
ForeignKey 多対1のリレーション
ManyToManyField 多対多のリレーション
OneToOneField 1対1のリレーション

 Django 2.0からForeignKeyOneToOneFieldにはon_deleteオプションを指定することが必須となりました。on_deleteは、レコードを削除する際のリレーション先への振る舞いを定義します。

on_delete値 振る舞い
CASCADE リレーション先もすべて削除される
PROTECT リレーションがある場合は削除できなくする
SET_NULL NULLがセットされる(null=Trueの設定が必要)
SET_DEFAULT デフォルト値がセットされる(default=...の設定が必要)
SET() 引数として渡した値がセットされる
DO_NOTHING 何もしない

 Django 1系におけるデフォルト値はCASCADEです。Django 2.0からはこのうちのどれかを自分で指定する必要があります。

 モデルフィールドについてのさらに詳しく知りたい方は、公式ドキュメント Model field referenceを参照してください。

 Djangoのモデル定義について理解を深めたうえで、Ticketモデルを振り返りましょう。

  • name:最大文字数が100のCharFieldとして定義。
  • description:入力任意、デフォルト値は空文字。文字数制限をせず、TextFieldとして定義。
  • status:ToDo、InProgress、Doneのステータスを数字で扱うので、SmallIntegerFieldとして定義。
  • assigneeUserモデルを参照に持つForeignKeyとして定義。入力任意、NULL許可。担当者だったユーザーが削除された場合にチケットも消えてしまうことは避けたいので、on_delete=models.SET_NULLを設定。
  • start:Pythonコードではdatetime.dateインスタンスとして表されるDateFieldとして定義。入力任意、NULL許可。
  • endstartと同様にDateFieldとして定義。入力任意、NULL許可。
  • created:Pythonコードではdatetime.datetimeインスタンスとして表されるDateTimeFieldとして定義。作成された時刻が自動でセットされるauto_now_add=Trueを指定。
  • modifiedcreatedと同様に、更新日時をDateTimeFieldとして定義。更新された時刻が自動でセットされるauto_now=Trueを指定。

enum

 statusフィールドは、ToDo、InProgress、Doneのステータスを扱うSmallIntegerFieldとして定義しました。値はそれぞれ以下の通りとします。

ステータス
ToDo 1
InProgress 2
Done 3

 statusはこの3つの値以外にはしたくないので、choicesオプションを使って値を制限するようにしましょう。choicesを設定しておくと、バリデーションやフィルタリングの際に役立ちます。

 choicesオプションには、(値, 表示用の文字列)のタプルで構成されるiterable(反復可能なオブジェクト)を渡します。以下のようにそれぞれの値とchoices用のiterableを定数として定義するやり方が、Djangoでは一般的です。

class Ticket(models.Model):
    ToDo = 1
    InProgress = 2
    Done = 3
    STATUS_CHOICES = (
        (ToDo, 'ToDo'),
        (InProgress, 'InProgress'),
        (Done, 'Done'),
    )
...
    status = models.SmallIntegerField(choices= STATUS_CHOICES, default=ToDo)
...

 ただこのやり方だと、choicesの選択肢が増えていったり、複数のchoicesが必要になったりすると、コードが煩雑になっていきます。Modelクラス内がchoicesのための定数で溢れてしまい、本来集中したいフィールドの定義がぼやけてしまいます。

 Python3.4から導入されたenum(列挙型)を使ってchoicesを定義することをお勧めします。enumは単純に定数として使うだけでなくメソッドを実装できるため、再利用の幅が広がります。

 このenumを使って、TicketStatusクラスを実装しましょう。kanban/board/constants.pyを作成して、以下のように編集します。

import enum


class TicketStatus(enum.IntEnum):
    ToDo = 1
    InProgress = 2
    Done = 3

    @classmethod
    def get_choices(cls):
        return tuple((x.value, x.name) for x in cls)

 このTicketStatusを用いて、kanban/board/models.pystatusフィールドを以下のように書き直します。

from django.conf import settings
from django.db import models

from .constants import TicketStatus


class Ticket(models.Model):
    name = models.CharField(max_length=100)
    description = models.TextField(blank=True, default='')
    status = models.SmallIntegerField(choices=TicketStatus.get_choices(), default=TicketStatus.ToDo.value)
...

 enumを導入することで、保守性が高まり再利用がしやすいコードが書けるのでぜひ試してみてください。

django-model-utils

 Ticketモデルに作成日時(created)、更新日時(modified)のフィールドを定義しましたが、これらはほかのモデルでも汎用的に使えそうです。django-model-utilsというパッケージは、このニーズに応えるTimeStampedModelという抽象ベースクラスを用意してくれています。

 django-model-utilsをインストールして、Ticketモデルに適用してみましょう。

(env) $ pip install django-model-utils
(env) $ vi requirements/base.txt
(env) $ cat requirements/base.txt
Django==2.0.2
django-model-utils==3.1.1
from django.conf import settings
from django.db import models
from model_utils.models import TimeStampedModel

from .constants import TicketStatus


class Ticket(TimeStampedModel):
    name = models.CharField(max_length=100)
    description = models.TextField(blank=True, default='')
    status = models.SmallIntegerField(choices=TicketStatus.get_choices(), default=TicketStatus.ToDo.value)
    assignee = models.ForeignKey(settings.AUTH_USER_MODEL, blank=True, null=True, on_delete=models.SET_NULL)
    start = models.DateField(null=True, blank=True)
    end = models.DateField(null=True, blank=True)

    def __str__(self):
        return f'{self.name}'

 Ticketクラスの継承元をdjango.db.models.Modelからmodel_utils.models.TimeStampedModelに変更しています。このおかげで、Ticketクラスからcreatedmodifiedフィールドの定義を省略できました。django-model-utilsには、TimeStampedModel以外にも便利な抽象ベースクラスやフィールドが用意されています。ぜひ一度チェックしてみてください。

次のページ
カンバンボード アプリケーション開発(2)

この記事は参考になりましたか?

モダンDjango入門連載記事一覧

もっと読む

この記事の著者

新井 正貴(アライ マサタカ)

 東京大学文学部卒業、アライドアーキテクツ株式会社にて勤務。2016年4月、株式会社SQUEEZEに入社。コミュニティ活動として、PyCon JP 2015〜 スタッフ、Pythonもくもく会の主催を行う。趣味はラクロスとPerfume。 Site:http://massa142.github.io/ Twitter:@massa142 Facebook:新井 正貴

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

CodeZine(コードジン)
https://codezine.jp/article/detail/10722 2018/03/15 14:00

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー