- はじめに
- モデルビューテンプレート
- テンプレートの配備場所
- ベーステンプレート
- 静的ファイルの取り扱い
- index ページのテンプレート
- index ビューの作成
- ブックリストページの作成
- ブックリストページへの遷移
- ブック詳細ページの作成
- ブック詳細ページへの遷移
- 一覧ページへ Pagination を追加
- 著者ページの作成
- まとめ
はじめに
前回は Django の導入からモデルの定義まで説明しました。
今回は、モデルを利用するビューの作成方法について見ていきましょう。
モデルビューテンプレート
Django で作成するアプリケーションは、モデルビューテンプレート(Model View Template, MVT)の構成を取ります。
urls.py
による HTTP リクエストのルーティングと、models.py
によるモデル定義については前回説明した内容になります。
urls.py
でルーティングされたリクエストは、ビュー(views.py
) でモデルを介してデータベースを操作し、テンプレートを使ってレスポンスを構築します。
Template の定義からはじめて、views.py
でリクエストに応じたレスポンスを返す流れを見ていきましょう。
テンプレートの配備場所
Django のテンプレートは、アプリケーションの template
ディレクトリに配備します。今回の例では、/catalog/templates/
にテンプレートを配備することになります。
この設定は、settings.py
の以下の項目でカスタマイズできます。
TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': [], 'APP_DIRS': True, 'OPTIONS': { ... }, }, ]
'APP_DIRS': True
とすることで template ディレクトリの検索が有効になります。DIRS
には検索対象のディレクトリを追加することができます。
ここでは、デフォルトの設定のまま進めるものとします。
ベーステンプレート
Django では、アプリケーション共通のベーステンプレートを定義し、個々の画面ではベーステンプレートを拡張(extends)することでマークアップを共通化することができます。
最初にベーステンプレートとして catalog/templates/base_generic.html
を作成しましょう。
<!DOCTYPE html> <html lang="en"> <head> {% block title %}<title>Local Library</title>{% endblock %} <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous"> <!-- Add additional CSS in static file --> {% load static %} <link rel="stylesheet" href="{% static 'catalog/css/styles.css' %}"> </head> <body> <div class="container-fluid"> <div class="row"> <div class="col-sm-2"> {% block sidebar %} <ul class="sidebar-nav"> <li><a href="{% url 'index' %}">Home</a></li> <li><a href="">All books</a></li> <li><a href="">All authors</a></li> </ul> {% endblock %} </div> <div class="col-sm-10 mt-2"> {% block content %}{% endblock %} </div> </div> </div> </body> </html>
テンプレートには、{% block title %} ... {% endblock %}
{% block sidebar %} ... {% endblock %}
{% block content %}{% endblock %}
というテンプレートタグがあります。
このブロックには、extends で派生したページでコンテンツを埋め込むことになります(デフォルトのコンテンツを含めることができます)。
サードバーには {% url 'index' %}
としてURLリンクを定義しています。
これは、 urls.py
で定義したURL Conf path('', views.index, name='index'),
に付けた名前で、URLパスを参照するものになります。
ベーステンプレートでは、{% load static %}
と {% static 'catalog/css/styles.css' %}
により、CSSファイルを読み込んでいます。これについて少しだけ詳細を見ておきましょう。
静的ファイルの取り扱い
Django では通常、静的ファイルを扱いません。静的ファイルは、フロントに配置した Apache や Nginx で処理したり、専用サーバや CDN から配信することになります。
Django では、このような静的ファイルの取り扱いを行うために django.contrib.staticfiles
が提供されています。settings.py
で以下のようになっていれば django.contrib.staticfiles
が有効化されています。
INSTALLED_APPS = [
...
'django.contrib.staticfiles',
]
django.contrib.staticfiles
では、それぞれのアプリケーションから静的ファイルを集め、運用環境で公開しやすくするものです。ここでは詳細には立ち入りませんが、必要に応じて Deploying static files を参照してください。
django.contrib.staticfiles
では、DEBUG = True
(settings.py
の設定)となっていた場合、runserver
を実行すれば、開発サーバで静的ファイルを扱えるように自動的に処理されます。ですので、開発時には意識せずに作業をすすめることができますが、極めて非効率であり、セキュリティ上の問題がある可能性が高いため、運用環境での利用は推奨されていません。
このように、開発時と運用時の静的ファイルの扱いがことなるため、テンプレート上では、{% load static %}
と {% static 'catalog/css/styles.css' %}
により、STATIC_URL というグローバルな設定値を基準にしてCSSを読み込むようになっています。
STATIC_URL は settings.py
にて、デフォルトで STATIC_URL = '/static/'
として定義されています。そのため、アプリケーション用の CSS は、catalog/static/
以下に配備することになります。
catalog/static/catalog/css/styles.css
として以下のように定義しましょう。
.sidebar-nav { margin-top: 20px; padding: 0; list-style: none; }
なお、catalog/static/
以下にアプリケーション名のディレクトリを設けることで、django.contrib.staticfiles
で複数アプリケーションの静的ファイルを扱う際の名前の競合を防ぐことができます。
index ページのテンプレート
ベーステンプレートを extends した index ページのテンプレートを作成します。
catalog/templates/index.html
を以下の内容で作成してください。
{% extends "base_generic.html" %} {% block content %} <h1>Local Library Home</h1> <p>Welcome to LocalLibrary</p> <h2>Dynamic content</h2> <p>The library has the following record counts:</p> <ul> <li><strong>Books:</strong> {{ num_books }}</li> <li><strong>Copies:</strong> {{ num_instances }}</li> <li><strong>Copies available:</strong> {{ num_instances_available }}</li> <li><strong>Authors:</strong> {{ num_authors }}</li> </ul> {% endblock %}
1 行目の {% extends "base_generic.html" %}
で、先ほど作成したベーステンプレートを指定しています。
{% block content %} ... {% endblock %}
が、このページで定義するコンテンツで、ベーステンプレートに埋め込まれます。
{{ num_books }}
や {{ num_instances }}
といった記載は、テンプレート変数で、この後作成する views.py
のメソッドから dictionary 形式で受け渡すことで、名前で参照できます。
index ビューの作成
テンプレートの準備が終わったので、ビューを定義していきます。
catalog/views.py
を以下のように編集します。
from django.shortcuts import render from .models import Book, Author, BookInstance, Genre def index(request): num_books = Book.objects.all().count() num_instances = BookInstance.objects.all().count() num_instances_available = BookInstance.objects.filter(status__exact='a').count() num_authors = Author.objects.count() context = { 'num_books': num_books, 'num_instances': num_instances, 'num_instances_available': num_instances_available, 'num_authors': num_authors, } return render(request, 'index.html', context=context)
モデルの取得は、Entry.objects
として取得したモデルマネージャを介してクエリできます。
上記では、Book.objects.all()
のように取得した QuerySet から count()
で件数を取得しています。
BookInstance.objects.filter(status__exact='a')
として抽出条件を指定して QuerySet を取得することもできます。exact
は完全一致を意味し、その他 contains
だったり in
だったり gt
といった条件を指定しています。他の条件指定は Field lookups を参照してください。
フィールドは二重アンダースコアで連結し、例えばリレーションを辿る必要がある場合には genre__name__icontains='fiction'
のように複数を連結します。
取得した情報は、dictionary として context に設定し、render()
によりテンプレートへバインドしてレスポンスを返します。
では、ここまでの内容でサーバを起動しましょう。
$ python manage.py runserver
ブラウザで http://localhost:8000/catalog/ にアクセスすれば、以下のような画面が表示されます。
ブックリストページの作成
続いて書籍の一覧ページに移りましょう。
Djangoでは、一覧ページ用の汎用リストビュー(ListView)が用意されており、これを継承したクラスを定義することで簡単に一覧ビューが作成できます。
catalog/views.py を開いて以下のように編集します。
from django.views import generic class BookListView(generic.ListView): model = Book
これだけで、Book の一覧を取得して catalog/templates/catalog/book_list.html
というテンプレートをレンダリングする処理が行われます。テンプレートでは object_list
または book_list
という名前でデータベースから取得した値を参照できます。
デフォルトの動作は、作成した BookListView
を変更することでカスタマイズできます(例えば、テンプレートファイルを指定したり、データベースから取得するレコード数を制限するなど)。
では、テンプレートファイルを catalog/templates/catalog/book_list.html
を作成しましょう。
{% extends "base_generic.html" %} {% block content %} <h1>Book List</h1> {% if book_list %} <ul> {% for book in book_list %} <li> <a href="">{{ book.title }}</a> ({{book.author}}) </li> {% endfor %} </ul> {% else %} <p>There are no books in the library.</p> {% endif %} {% endblock %}
テンプレートは Indexページで作成したものと大差ありませんが、if条件 {% if book_list %}
や forループ {% for book in book_list %}
を使っていることに注意してください。
ブックリストページへの遷移
ベーステンプレートを更新してブックリストページへのリンクを挿入しましょう。
catalog/templates/base_generic.html
のリンクを編集します。
<li><a href="{% url 'index' %}">Home</a></li> <li><a href="{% url 'books' %}">All books</a></li> <li><a href="">All authors</a></li>
books
のURLでアクセスした場合のURLマッピング定義の追加も必要です。
catalog/urls.py
を以下のように変更しましょう。
urlpatterns = [ path('', views.index, name='index'), path('books/', views.BookListView.as_view(), name='books'), ]
クラスベースのビューを利用するため、views.BookListView.as_view()
という形でビューを指定している点に注意してください。
クラスメソッド as_view() を呼び出して、適切なビュー関数にアクセスします。これは、クラスのインスタンスを生成したり、HTTP リクエストの着信時に適切なハンドラメソッドが呼び出されるようにしたりする作業をすべて行います。
変更内容を確認しておきましょう。
$ python manage.py runserver
一覧画面が表示できました。
ブック詳細ページの作成
詳細ページについても Django の提供する DetailView を使うことができます。
catalog/views.py
に以下のクラスを追加します。
class BookDetailView(generic.DetailView): model = Book
テンプレートは catalog/templates/catalog/book_detail.html
として以下のように作成します。
{% extends "base_generic.html" %} {% block content %} <h1>Title: {{ book.title }}</h1> <p><strong>Author:</strong> <a href="">{{ book.author }}</a></p> <p><strong>Summary:</strong> {{ book.summary }}</p> <p><strong>ISBN:</strong> {{ book.isbn }}</p> <p><strong>Language:</strong> {{ book.language }}</p> <p><strong>Genre:</strong> {{ book.genre.all|join:", " }}</p> <div style="margin-left:20px;margin-top:20px"> <h4>Copies</h4> {% for copy in book.bookinstance_set.all %} <hr> <p class="{% if copy.status == 'a' %}text-success{% elif copy.status == 'm' %}text-danger{% else %}text-warning{% endif %}"> {{ copy.get_status_display }} </p> {% if copy.status != 'a' %} <p><strong>Due to be returned:</strong> {{ copy.due_back }}</p> {% endif %} <p><strong>Imprint:</strong> {{ copy.imprint }}</p> <p class="text-muted"><strong>Id:</strong> {{ copy.id }}</p> {% endfor %} </div> {% endblock %}
少し長いですが、ほとんど以前に説明したとおりです。
注目すべき点は、book.bookinstance_set.all
という記載です。これは、 Django によって自動的に作られたメソッドで、Book に関連する BookInstance レコードのセットを返すものです。
Book と BookInstance のリレーションは、Book ← BookInstance の方向で、Book から BookInstance をたどることができません。このような場合、Django はForeignKey が宣言されたモデル名を小文字にして、その後に _set を付けた名前の逆引き関数を提供します。
もう一つ注意すべきものは、{{ copy.get_status_display }}
という記載です。
ここで copy は、BookInstance のオブジェクトとなりますが、 BookInstance には get_status_display というメソッドを定義していません。
Django は、モデル内の choices フィールドに対して、get_フィールド名_display()
というメソッドを自動的に作成します。
念のため、BookInstance の該当箇所を載せておきます。
LOAN_STATUS = ( ('d', 'Maintenance'), ('o', 'On loan'), ('a', 'Available'), ('r', 'Reserved'), ) status = models.CharField( max_length=1, choices=LOAN_STATUS, blank=True, default='d', help_text='Book availability')
ブック詳細ページへの遷移
ブック詳細ページへのリンクを catalog/templates/catalog/book_list.html
に追加しましょう。{{ book.get_absolute_url }}
を追加します。
{% for book in book_list %} <li> <a href="{{ book.get_absolute_url }}">{{ book.title }}</a> ({{book.author}}) </li> {% endfor %}
このメソッドは、Book で以下のように定義したものです。
class Book(models.Model): ... def get_absolute_url(self): return reverse('book-detail', args=[str(self.id)])
book-detail
という名前のパスに自身のIDを付けたURLを生成するメソッドです。
book-detail
を追加するために catalog/urls.py
を以下のように変更しましょう。
urlpatterns = [ path('', views.index, name='index'), path('books/', views.BookListView.as_view(), name='books'), path('book/<int:pk>', views.BookDetailView.as_view(), name='book-detail'), ]
URLパスは、book/<int:pk>
となっています。これは、URLパスパラメータを整数型で pk
というキーでキャプチャする意味となります。型はその他 str
, slug
, uuid
, path
などが指定できます。
DetailView ではこのキーで対応するモデルの詳細画面を処理します。
内容を確認しておきましょう。
$ python manage.py runserver
一覧画面から詳細画面へ遷移できるようになりました。
一覧ページへ Pagination を追加
ここまでで作成した一覧画面は、全てのレコードを一覧するものでしたので、Pagination を追加していきます。
一覧用のビューは ListView を使っているので、Pagination の追加は簡単に行うことができます。
catalog/views.py
の BookListView
に paginate_by
を追加します。
class BookListView(generic.ListView): model = Book paginate_by = 5
テンプレートは、ベーステンプレート catalog/templates/base_generic.html
を編集します。
{% block content %}{% endblock %}
の下に Pagination 用のブロックを追加します。
... {% block content %}{% endblock %} {% block pagination %} {% if is_paginated %} <div class="pagination"> <span class="page-links"> {% if page_obj.has_previous %} <a href="{{ request.path }}?page={{ page_obj.previous_page_number }}">previous</a> {% endif %} <span class="page-current"> Page {{ page_obj.number }} of {{ page_obj.paginator.num_pages }}. </span> {% if page_obj.has_next %} <a href="{{ request.path }}?page={{ page_obj.next_page_number }}">next</a> {% endif %} </span> </div> {% endif %} {% endblock %}
内容を確認しておきましょう。
$ python manage.py runserver
Pagination ができるようになりました。
著者ページの作成
ブックページと同様に、著者ページも作成しましょう。
作業はブックページと同じなので、コードだけ示します。
catalog/urls.py
urlpatterns = [ ... path('authors/', views.AuthorListView.as_view(), name='authors'), path('author/<int:pk>', views.AuthorDetailView.as_view(), name='author-detail'), ]
catalog/views.py
class AuthorListView(generic.ListView): model = Author paginate_by = 5 class AuthorDetailView(generic.DetailView): model = Author
catalog/templates/catalog/author_list.html
{% extends "base_generic.html" %} {% block content %} <h1>Author List</h1> {% if author_list %} <ul> {% for author in author_list %} <li> <a href="{{ author.get_absolute_url }}"> {{ author }} ({{author.date_of_birth}} - {% if author.date_of_death %}{{author.date_of_death}}{% endif %}) </a> </li> {% endfor %} </ul> {% else %} <p>There are no authors available.</p> {% endif %} {% endblock %}
catalog/templates/catalog/author_detail.html
{% extends "base_generic.html" %} {% block content %} <h1>Author: {{ author }} </h1> <p>{{author.date_of_birth}} - {% if author.date_of_death %}{{author.date_of_death}}{% endif %}</p> <div style="margin-left:20px;margin-top:20px"> <h4>Books</h4> <dl> {% for book in author.book_set.all %} <dt><a href="{% url 'book-detail' book.pk %}">{{book}}</a> ({{book.bookinstance_set.all.count}})</dt> <dd>{{book.summary}}</dd> {% endfor %} </dl> </div> {% endblock %}
サイドバーのリンクを追加します。catalog/templates/base_generic.html
の All authors のリンクURLを編集します。
<ul class="sidebar-nav"> <li><a href="{% url 'index' %}">Home</a></li> <li><a href="{% url 'books' %}">All books</a></li> <li><a href="{% url 'authors' %}">All authors</a></li> </ul>
ブック詳細ページ catalog/templates/catalog/book_detail.html
からのリンクも編集します。
<p><strong>Author:</strong> <a href="{{ book.author.get_absolute_url }}">{{ book.author }}</a></p>
これで、著者ページの完成です。
まとめ
今回は、Django の ビューとテンプレートの簡単な利用方法についてみてきました。
次回は、フォーム操作を扱っていきます。