Help us understand the problem. What is going on with this article?

Django×PinaxでWebサービス入門⑥|アプリの実装

More than 1 year has passed since last update.

前回はVPSサーバーにて、ローカルで構築したアプリをインターネット公開するところまで進めました。
環境構築周りの面倒なところは全て終了したので、最後にWebアプリケーションを開発していきます。

今回の対象は、全体像で見てみるとこの緑色で囲った部分です。

02.png

  • Djangoアプリケーションの基本構成
  • Modelを定義
  • Modelを有効化
  • Viewを作成
  • Templateを作成

Django アプリケーションの基本構成

Webアプリケーションは通常MVC(Model-View-Controller)という構造で、

  • Model: データの定義・ビジネスロジック
  • View: ブラウザに返すフロントの定義
  • Controller: ModelとViewの制御

といった形でアプリケーション内の機能が大別されます。
DjangoではMVT(Model-View-Template)という分類でそれぞれの機能を呼んでいますが、基本的に呼び方は違いますが振る舞いはMVCと同じなので、MVC:MVTの対応は

  • Model: Model
  • View: Template
  • Controller: View

という風に理解します。

今回TODOアプリを実装するにあたって、まず管理するデータを定義し、どうやってデータをフロントに返すかを定義し、フロントでどう表示するかを定義していきます。
そのため、順番的にはModel→View→Templateという流れで進みます。

Modelの定義

Modelはmodels.pyというファイルにデータの定義を記載したファイルを追加し、その後データベースのテーブル作成・編集用のMigrationファイルを作成し、Migrateコマンドを用いて実際にデータベースに反映させていきます。
models.pyは下記のようになります。

todo/todo/models.py
from django.db import models
from django.contrib.auth.models import User


class Todo(models.Model):
    title = models.CharField("タイトル", max_length=256)
    content = models.TextField("内容", blank=True)

    def __str__(self):
        return str(self.content)

ここでは、Todoというエンテティ(データの塊の概念)を定義し、その中にtitle(タイトル)content(コンテンツ)というフィールドを持つようにしています。
titlemax_lengthは最大文字数を、contentblank=Trueは空欄(Nullではない)を許すという意味です。

なお、Djangoは各データのレコードのUUIDをidというフィールド名で自動的に生成するので、自らここで定義する必要はありません。

最後のdef __str__(self):という関数は、Todoデータを参照した場合にデフォルトでどのフィールドを表示するかを定義しています。

ついでにこのTodoというモデルをDjangoの管理画面から確認できるようにしましょう。
このためにはadmin.pyというファイルをmodels.pyファイルと同じディレクトリに配置します。

todo/todo/admin.py
from django.contrib import admin
from todo.models import Todo


class TodoAdmin(admin.ModelAdmin):
    list_display = ('id', 'title', 'content')

admin.site.register(Todo, TodoAdmin)

Modelの有効化

続いて、Djangoの機能であるmakemigrationsコマンドを叩き、マイグレーションファイルを作成します。

terminal
$ python manage.py makemigrations todo
Migrations for 'todo':
  todo/migrations/0001_initial.py
    - Create model Todo

todo/migrations/0001_initial.pyというファイルが生成されたので中身を見てみます。

todo/migrations/0001_initial.py
from django.db import migrations, models


class Migration(migrations.Migration):

    initial = True

    dependencies = [
    ]

    operations = [
        migrations.CreateModel(
            name='Todo',
            fields=[
                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
                ('title', models.CharField(max_length=256)),
                ('content', models.TextField(blank=True)),
            ],
        ),
    ]

先程models.pyで定義した内容をもとに、DBにテーブルを作成するための処理が記載されています。
これを実際にデータベースに反映するためにmigrateコマンドを実行します。

terminal
$ python manage.py migrate
Operations to perform:
  Apply all migrations: account, admin, auth, contenttypes, eventlog, sessions, sites, todo
Running migrations:
  Applying todo.0001_initial... OK

これで先程のマイグレーションファイルをもとにTodoのテーブルが構築されました。これはDjangoの管理画面から確認できます。(以前作成したスーパーユーザーでログインして見てみてください。)

このように、データベース上の処理は全てmodels.pyファイルとDjangoのコマンドのみで完結しました。これによって、個別のデータベースの使い方等に影響されることなく、アプリケーションの開発に専念することができます。
さらに管理画面でデータの確認・追加・修正・削除ができるため、アプリケーションが正しく動作しているかを確かめるためにデータベースにアクセスするといった必要性もなくなります。

Viewの作成

続いて、Viewを作成していきます。
ブラウザからのアクセスを受けて、アクセスされたURLに応じてデータベースとやりとりを行い、ブラウザに対してTemplateを返す部分です。

Generic View

通常、最初は簡単な関数を定義してそれを呼び、データベースに接続して取得したデータをフロントに返却する、という処理をなぞってみると思います。
ただそういった内容はDjango Girls (Djangoの入門用コンテンツがあるサイト。日本語あり)などに説明が豊富にあります。
さらに、この1回から6回までで個人的に書きたかったことは、Pinaxを使った初期構築コストの削減と、VPSにDjangoアプリケーションをデプロイするまでの部分でした。
そのため、今回は引き続きDjangoで簡単にWebアプリケーションを作れるという文脈で、Generic Viewというものを使って作っていきます。

Generic Viewとは、Webアプリケーションを作るうえで頻繁に利用される機能を共通化したクラスの総称で、

  • CreateView: データの作成
  • DetailView: データの表示
  • UpdateView: データの更新
  • ListView: データの一覧表示
  • DetailView: データの削除

といったいわゆるCRUD的なものから、TemplateViewという単純にTemplateを返す目的のものや、FormViewというフォーム(コンタクトフォーム等)を表示するためのものなど17種類が定義されています。
今回はCreateView,UpdateView,ListView,DetailViewを使っていきます。

Formの作成

Djangoにはフォームという機構があります。これはユーザーがデータを登録したり編集する時に表示するフォームを、models.pyで定義したモデルをもとに作成し、バリデーション等の機能を持たせたものです。このフォームをテンプレートの中で呼び出すと、入力編集用の入力欄がまとまって生成されます。

フォームはfomrs.pyという名前のファイルで作成します。

todo/todo/forms
from django import forms
from todo.models import Todo


class TodoForm(forms.ModelForm):

    class Meta:
        model = Todo # Todoモデルを扱うFormと定義
        fields = ("title", "content") # Formでユーザーが入力可能なフィールド

TodoFormという名前のフォームを作っています。Todoモデルをもとにして、その中でtitle,contentフィールドを利用するということを定義しています。

Viewの作成

それではビューを作っていきます。モデルやフォーム同様、views.pyというファイルを作成します。

todo/todo/views.py
from django.views.generic import CreateView, UpdateView, ListView, DeleteView # 必要なGeneric Viewをインポート
from todo.models import Todo
from todo.forms import TodoForm # 先程作ったフォームをインポート


class TodoCreateView(CreateView): # 新規追加
    model = Todo
    form_class = TodoForm # 新規追加に利用するFormを定義
    success_url = '/todo' # 処理が成功した後のリダイレクト先


class TodoUpdateView(UpdateView): # 更新
    model = Todo
    form_class = TodoForm
    success_url = '/todo'


class TodoListView(ListView): # 一覧
    model = Todo


class TodoDeleteView(DeleteView): # 削除
    model = Todo
    success_url =  '/todo'

これだけです。新規追加、更新、一覧、削除のそれぞれにクラスを作成し、どんな働きをするかを明示しています。

ソースが圧倒的に短いですが、これはGeneric Viewによって大半が補完されているためです。
例えば一覧用のTodoListViewは利用するモデルを定義するだけで、

  • 対応するモデルのデータを取得する
  • データを返却する先のHTMLファイルをアプリ名/モデル名_list.htmlという名前で探す。(今回はtodo/todo_list.html)
  • 対象のHTMLファイルが見つかったら、そのファイルに対してデータをobject_listという名前で格納して返す

ということを自動的にやってくれます。
(このあたりのイメージを掴むためには、基礎的なことから自身で書いてみることがやはり大切ですが。。)

URLパターンの定義

アクセスされるURLと上記のビューを対応させるため、url.pyを下記のように編集します。

todo/todo/urls.py
from django.conf import settings

from django.conf.urls.static import static
from django.urls import include, path
from django.views.generic import TemplateView
from django.contrib import admin
from todo.views import TodoCreateView, TodoUpdateView, TodoListView, TodoDeleteView # 利用するViewをインポート

urlpatterns = [
    path("", TemplateView.as_view(template_name="homepage.html"), name="home"),
    path("admin/", admin.site.urls),
    path("account/", include("account.urls")),
    path("todo/create", TodoCreateView.as_view(), name='todo-create'), # Todo作成ページ用のURLを追加
    path("todo/update/<pk>/", TodoUpdateView.as_view(), name='todo-update'), # Todo更新ページ用のURLを追加
    path("todo/delete/<pk>/", TodoDeleteView.as_view(), name='todo-delete'), # Todo更新ページ用のURLを追加
    path("todo/", TodoListView.as_view(), name='todo-list') # Todo一覧ページ用のURLを追加
]

urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

4つの定義を追加しています。

少し細かく見ると、例えばpath("todo/update/<pk>/", TodoUpdateView.as_view(), name='todo-update')という部分はデータを更新するときのためのURLでして、

  • todo/update/<pk>/というURLにアクセスがあると(このpkはプライマリーキーの意味です。更新対象のデータを一意に特定する必要があるので)
  • TodoUpdateViewas_view()関数を呼び出す

ということを意味しています。

ちなみにname='todo-update'という部分はテンプレートで画面を遷移する時に利用します。

Templateの作成

最後にテンプレートを作成します。
テンプレートはtodo/todo/templatesというディレクトリを作成してその中に格納していきます。

一覧画面

todo/todo/templates/todo_list.html
{% extends "site_base.html" %}

{% block body_base %}

    <main>
        <div class="container">
            <h1 class="h4">Todoリスト</h1>
            <a href="{% url 'todo-create' %}" class="btn btn-primary mb-3">作成する</a>
            <ul class="list-group">
                {% for todo in object_list %}
                    <li class="list-group-item"><a href="{% url 'todo-update' todo.id %}">{{ todo.title }}</a>
                        <a href="{% url 'todo-delete' todo.id %}" class="float-right"><i class="text-dark fa fa-times"></i></a>
                    </li>
                {% endfor %}
            </ul>
        </div>
    </main>

{% endblock %}

TodoListViewから渡されたTodoの一覧はobject_listという名前で呼び出せます。これをループで回し、リストに表示しています。
またTodo新規作成用のリンク、Todo更新用とTodo削除用のリンクを各リスト内に持っています。
例えば更新だと{% url 'todo-update' todo.id %}とありますが、このtodo-updateは先程urls.pyで定義したname, todo.idは一意のキーをURLに埋め込むためのものです。(ループ内でtodoという名前で利用しているため、todo.idでidが取得できます。)

新規作成・更新画面

新規作成と更新は同じHTMLファイルを利用します。

html/todo/templates/todo_form.html
{% extends "site_base.html" %}

{% load bootstrap %}

{% block body_base %}

    <main>
        <div class="container">
            <h1 class="h4">Todo作成</h1>
            <form method="post">
                {% csrf_token %}
                    {{ form|bootstrap }}
                <button type="submit">保存する</button>
            </form>
        </div>
    </main>

{% endblock %}

{{ form|bootstrap }}という部分で、forms.pyで定義したTodoFormを呼び出しています。
上部でBootstrapをロードしているため、form-controlといったClassが利用できていますが、ここは{{ form }}とするだけでも動作します。

削除の確認画面

最後に、Todoを削除しようとした時用の確認画面を作ります。

todo/todo/templates/todo_confirm_delete.html
{% extends "site_base.html" %}

{% load bootstrap %}

{% block body_base %}

    <main>
        <div class="container">
            <form method="post">{% csrf_token %}
                <p>"{{ object.title }}" を削除します。よろしいですか"?</p>
                <input type="submit" value="削除する" class="btn btn-primary"/>
            </form>
        </div>
    </main>

{% endblock %}

ここでは削除する対象のTodoは一つのデータなので、object_listではなくobjectという名前で呼べるようになっています。

動かしてみる

ここまでいくと、http://127.0.0.1:8000/todo/にアクセスすると一覧画面が、そこからそれぞれに遷移することができます。

3y1tvTbz1w.gif

こんな感じで動きます。

最後に

これで一応Django製Webアプリケーションを作り、VPSにデプロイし、さらに開発を進めていくという土台までが終了しました。
今後Djangoに関する記事を書く時は、ここまでつくったものを拡張する形で進めていければと考えています。

mtitg
データサイエンスの企業でWebエンジニアやってます
datamix
データサイエンスに関わる最適なサービスを継続的に提供することで、企業・地域・社会に属するひとりひとりが、客観的に意思決定する力を高め、自由に、そして平等に活躍できる世界を実現します。
https://datamix.co.jp/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした