Django の CRUD ジェネリックビュー (ListView, DetailView, CreateView, UpdateView, DeleteView) の簡単な使い方


Django は、Python プログラミング言語で動作するウェブアプリケーションフレームワークです。

この記事は、Django の使い方を説明するものです。

Djangoの解説: The Web framework for perfectionists with deadlines | Django


Django に最初から用意されているビュー(ジェネリックビュー) を使えば、

  • オブジェクトのリスト表示
  • オブジェクトの作成
  • オブジェクトの詳細表示
  • オブジェクトの更新
  • オブジェクトの削除

のWebアプリが簡単に作成できます。

(オブジェクト=DBのレコード=モデルインスタンス=アクティブレコードオブジェクト という意味です)

Create, Read, Update, Delete をまとめて CRUD と呼んだりしますが、Django では CRUD に対応した汎用的な基底ビューコントローラが用意されているため、それを継承してビューを作ることで、安全で、読みやすいコードを書けます。

それぞれ、以下のビューコントローラに対応しています。

Create … CreateView
Read (リスト表示) … ListView
Read (詳細表示) … DetailView
Update … UpdateView
Delete … DeleteView

django.views.generic の中にいます。

Django では、これらのさらに基底となる TemplateView, FormView もありますが、やっている仕事として「モデルインスタンスを1つ作成/更新」「モデルインスタンス1つを詳しく表示」など、今回の CRUD の基底ビューに含まれる場合は、TemplateView, FormView を使うよりこれらのジェネリックビューを継承したほうがコードを読む際に書き手の意図を理解しやすいため、これらのクラスを使うべきです。

今回は、シンプルなテキストメモを作成・更新するWeb アプリのサンプルを作ってみました。
GitHubにコードを上げてありますので、同時にご参照ください。

リポジトリ

https://github.com/ytyng/django-crud-generic-view-tutorial

核心は、views.py なのでそれを見るとよくわかります。お急ぎの方はこれだけ読んでみてください。

https://github.com/ytyng/django-crud-generic-view-tutorial/blob/master/memo/memo/views.py

環境

Django 1.11, Python 3.6

ディレクトリ構造

memo/
+ manage.py
+ db.sqlite3
+ memo /
+ settings.py (Django設定(自動生成))
+ wsgi.py (WSGIランナー(自動生成))
+ urls.py (URLマップ(自動生成))
+ models.py (Memoモデルの定義)
+ views.py (ビューコントローラ)
+ forms.py (HTMLフォームクラス)
+ migrations (マイグレーションスクリプト(自動生成))
+ templates (HTMLテンプレート)
+ memo
+ base.html
+ memo_confirm_delete.html (削除確認用テンプレート)
+ memo_detail.html (詳細表示用テンプレート)
+ memo_form.html (作成、更新フォーム用テンプレート)
+ base.html (基底テンプレート)
+ memo_list.html (リスト表示用テンプレート)

コード解説

解説はコード中のコメントに書きました

models.py

from django.db import models


class Memo(models.Model):
"""
メモモデル。実質、件名と本文のみ。
"""
subject = models.CharField(
verbose_name='件名',
max_length=100,
default='',
blank=True
)

body = models.TextField(
verbose_name='本文',
default='',
blank=True
)

# created: auto_now_add を指定すると、作成日時を自動保存する
created = models.DateTimeField(
auto_now_add=True
)

# updated: auto_now を指定すると、更新日時を自動保存する
updated = models.DateTimeField(
auto_now=True
)

def __str__(self):
return self.subject

forms.py

from django import forms

from .models import Memo


class MemoForm(forms.ModelForm):
"""
Memo モデルの作成、更新に使われる Django フォーム。
ModelForm を継承して作れば、HTMLで表示したいフィールドを
指定するだけで HTML フォームを作ってくれる。
"""

class Meta:
model = Memo
fields = ['subject', 'body']

views.py

今回の核心です。Django の ListView, DetailView, CreateView, UpdateView, DeleteView をそれぞれ継承し、CRUDのビューコントローラを作成しています。

フォームクラスは、先程の MemoForm を使います。

それぞれのビューで書かなければならない内容は少なく、実務的な処理は Django に任せることで、エンジニアは UI の開発に集中できます。

from django.contrib import messages
from django.urls import reverse_lazy
from django.views.generic import \
ListView, DetailView, CreateView, UpdateView, DeleteView

from .models import Memo
from .forms import MemoForm


class MemoListView(ListView):
"""
メモを一覧表示
テンプレートは、何も指定しないと モデル名_list.html が使われる
ListView は、パジネーションもやってくれる
"""
model = Memo
paginate_by = 10 # 1ページに表示する件数


class MemoDetailView(DetailView):
"""
1つのメモを詳細表示
テンプレートは、何も指定しないと モデル名_detail.html が使われる
"""
model = Memo


class MemoCreateView(CreateView):
"""
メモ 新規作成
完了ページを作成し、success_url で指定して表示してもいいが、
django.contrib.messages の機能で、メッセージを保存して
リストビューなんかに戻した時に表示するのも簡潔で良い。
"""
model = Memo
form_class = MemoForm
success_url = reverse_lazy('memo_list')

def form_valid(self, form):
result = super().form_valid(form)
messages.success(
self.request, '「{}」を作成しました'.format(form.instance))
return result


class MemoUpdateView(UpdateView):
"""
メモ 更新
"""
model = Memo
form_class = MemoForm

success_url = reverse_lazy('memo_list')

def form_valid(self, form):
result = super().form_valid(form)
messages.success(
self.request, '「{}」を更新しました'.format(form.instance))
return result


class MemoDeleteView(DeleteView):
"""
メモ 削除
デフォルトでは、get でリクエストすると確認ページ、
post でリクエストすると削除を実行する、という動作。
実際は、レコードを削除するのではなく有効フラグを消す(いわゆる論理削除)
のケースが多いと思うので、そんな時はdeleteをオーバーライドしてその中で処理を書く。
"""
model = Memo
form_class = MemoForm

success_url = reverse_lazy('memo_list')

def delete(self, request, *args, **kwargs):
result = super().delete(request, *args, **kwargs)
messages.success(
self.request, '「{}」を削除しました'.format(self.object))
return result

urls.py

from django.conf.urls import url
from django.contrib import admin

from . import views

urlpatterns = [
url(r'^admin/', admin.site.urls),

url(r'^$',
views.MemoListView.as_view(),
name='memo_list'),

url(r'^detail/(?P<pk>\d+)/$',
views.MemoDetailView.as_view(),
name='memo_detail'),

url(r'^create/$',
views.MemoCreateView.as_view(),
name='memo_create'),

url(r'^update/(?P<pk>\d+)/$',
views.MemoUpdateView.as_view(),
name='memo_update'),

url(r'^delete/(?P<pk>\d+)/$',
views.MemoDeleteView.as_view(),
name='memo_delete'),
]

テンプレート

Bootstrap4でさっくり作ってあります。

base.html

<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
{# Bootstrap4 で。#}
<link rel="stylesheet"
href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta.2/css/bootstrap.min.css"
integrity="sha384-PsH8R72JQ3SOdhVi3uxftmaW6Vc51MKb0q5P2rRUpPvrszuE4W1povHYgTpBfshb"
crossorigin="anonymous">
<title>{% block meta_title %}{% endblock %}</title>
<body>

{% if messages %}
{# Django のメッセージに記録している内容があればここで表示 #}
<div class="container">
<div class="row">
<div class="col-12">
<div class="messages mt-3">
{% for message in messages %}
<div class="alert alert-dismissable alert-{{ message.tags }}" data-alert="alert">
<button type="button" class="close" data-dismiss="alert" aria-hidden="true">&times;</button>
{{ message }}
</div>
{% endfor %}
</div>
</div>
</div>
</div>
{% endif %}

<div class="container">
<div class="row">
<div class="col-12">
<h1 class="mt-5">{% block page_title %}Memo{% endblock %}</h1>
</div>
</div>
</div>
{% block content %}
{% endblock %}
{% block foot_scripts %}
<script src="https://code.jquery.com/jquery-3.2.1.slim.min.js"
integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN"
crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.3/umd/popper.min.js"
integrity="sha384-vFJXuSJphROIrBnz7yo7oB41mKfc8JzQZiCq4NCceLEaO4IHwicKwpJf9c9IpFgh"
crossorigin="anonymous"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta.2/js/bootstrap.min.js"
integrity="sha384-alpBpkh1PFOepccYVYDB4do5UnbKysX5WZXm3XxPqe5iKTfUKjNkCk9SaVuEZflJ"
crossorigin="anonymous"></script>
{% endblock %}
</body>
</html>

memo_list.html

シンプルなメモ一覧テンプレート。簡易的なパジネーションつき

{% extends 'memo/base.html' %}
{% block content %}

<div class="container">
<div class="row">
<div class="col-12">
<div class="card card-default">
<ul class="list-group list-group-flush">
{% for o in object_list %}
<li class="list-group-item">
<a href="{% url 'memo_detail' o.pk %}">{{ o.subject }}</a>
</li>
{% empty %}
<li class="list-group-item">
メモはありません
</li>
{% endfor %}
</ul>
</div>
</div>
<div class="col-12">
<div class="mt-3 text-center">
{% if page_obj.has_previous %}
<a class="btn btn-secondary" href="/?page={{ page_obj.previous_page_number }}">前へ</a>
{% endif %}
<span>{{ page_obj.number }}/{{ paginator.num_pages }}ページ</span>
{% if page_obj.has_next %}
<a class="btn btn-secondary" href="/?page={{ page_obj.next_page_number }}">次へ</a>
{% endif %}
</div>
</div>
</div>
</div>

<div class="container">
<div class="row">
<div class="col-12 mt-3">
<a href="{% url 'memo_create' %}" class="btn btn-primary">新規作成</a>
</div>
</div>
</div>
{% endblock %}

memo_detail.html

詳細表示。MemoDetailView で使われる。

{% extends 'memo/base.html' %}
{% block meta_title %}{{ object.subject }}{% endblock %}
{% block page_title %}{{ object.subject }}{% endblock %}
{% block content %}
<div class="container">
<div class="row">
<div class="col-12">
<div class="card card-default">
<div class="card-body">
{{ object.body|linebreaksbr }}
</div>
<div class="card-footer">
<a class="btn btn-primary" href="{% url 'memo_update' object.pk %}">修正</a>
<a class="btn btn-danger" href="{% url 'memo_delete' object.pk %}">削除</a>
</div>
</div>
</div>
</div>
</div>
{% endblock %}

memo_form.html

新規作成/更新フォーム。 MemoCreateView, MemoUpdateView で使われる。
HTMLフォームは、MemoForm のインスタンスを bootstrap フィルタにかければ自動的にいい感じで作ってくれる。バリデーション〜エラー対応もやってくれる。

{% extends 'memo/base.html' %}
{% load bootstrap %}
{% block meta_title %}{% if object.pk %}{{ object.subject }}{% else %}新規作成{% endif %}{% endblock %}
{% block page_title %}{% if object.pk %}{{ object.subject }}{% else %}新規作成{% endif %}{% endblock %}
{% block content %}
<form method="post">
<div class="container">
<div class="row">
<div class="col-12">
{{ form|bootstrap }}
</div>
</div>
<div class="row">
<div class="col-12">
<button class="btn btn-primary">保存</button>
</div>
</div>
</div>
{% csrf_token %}
</form>
{% endblock %}

memo_confirm_delete.html

削除確認フォーム

{% extends 'memo/base.html' %}
{% load bootstrap %}
{% block meta_title %}削除確認{% endblock %}
{% block page_title %}削除確認{% endblock %}
{% block content %}
<div class="container">
<div class="row">
<div class="col-12">
<p>{{ object }} を削除していいですか?</p>
<form method="post">
<button class="btn btn-danger">削除する</button>
{% csrf_token %}
</form>
</div>
</div>
</div>
{% endblock %}

現在の評価: 4.5

コメント

コメントを投稿
コメントするには TORICO-ID にログインしてください。
ログイン コメント利用規約