前回はVPSサーバーにて、ローカルで構築したアプリをインターネット公開するところまで進めました。
環境構築周りの面倒なところは全て終了したので、最後にWebアプリケーションを開発していきます。
今回の対象は、全体像で見てみるとこの緑色で囲った部分です。
- 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
は下記のようになります。
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(コンテンツ)
というフィールドを持つようにしています。
title
のmax_length
は最大文字数を、content
のblank=True
は空欄(Nullではない)を許すという意味です。
なお、Djangoは各データのレコードのUUIDをid
というフィールド名で自動的に生成するので、自らここで定義する必要はありません。
最後のdef __str__(self):
という関数は、Todo
データを参照した場合にデフォルトでどのフィールドを表示するかを定義しています。
ついでにこのTodo
というモデルをDjangoの管理画面から確認できるようにしましょう。
このためにはadmin.py
というファイルをmodels.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
コマンドを叩き、マイグレーションファイルを作成します。
$ python manage.py makemigrations todo
Migrations for 'todo':
todo/migrations/0001_initial.py
- Create model Todo
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
コマンドを実行します。
$ 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
という名前のファイルで作成します。
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
というファイルを作成します。
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
を下記のように編集します。
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はプライマリーキーの意味です。更新対象のデータを一意に特定する必要があるので) -
TodoUpdateView
のas_view()
関数を呼び出す
ということを意味しています。
ちなみにname='todo-update'
という部分はテンプレートで画面を遷移する時に利用します。
Templateの作成
最後にテンプレートを作成します。
テンプレートはtodo/todo/templates
というディレクトリを作成してその中に格納していきます。
一覧画面
{% 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ファイルを利用します。
{% 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を削除しようとした時用の確認画面を作ります。
{% 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/
にアクセスすると一覧画面が、そこからそれぞれに遷移することができます。
こんな感じで動きます。
最後に
これで一応Django製Webアプリケーションを作り、VPSにデプロイし、さらに開発を進めていくという土台までが終了しました。
今後Djangoに関する記事を書く時は、ここまでつくったものを拡張する形で進めていければと考えています。