やったーーーー!!
Djangoのアプリができたぞ〜〜〜〜!!!!
チュートリアルアプリかな?
どうだ! いいだろ!
この日記アプリさえあれば、いちいちQiitaだのZennだのに書かなくても大丈夫。
どんどん機能追加していって、
ゆくゆくはQiitaを超えるプラットフォーマーになる!
うん、根拠なき自信って大切だよな。。。
クラスベースビュー使った?
ところでこの画面、クラスベースビューで作ったのか?
クラスベースビュー? なんだそれ??
クラスベースビューは、少ない記述でCRUDの機能を作れる、汎用クラスだ
今のお前のviews.py
を見てみよう
from django.shortcuts import render
# Create your views here.
from .models import Post
# Create your views here.
def index(request):
return render(request, 'index.html', {
'posts': Post.objects.all(),
})
これは関数ベースビューというやつだ
関数ベースビューは、自由に記述できる代わりに
実装者によって書き方が違ったり、いろんな画面で同じような処理を繰り返し書いたり
どこに何を書いてもいいが故に、冗長になりがちだ
クラスベースビューを使うと、CRUDでよく使う機能を簡単に実装できる
例えばXXの機能であればこう書く、みたいなお作法が決まっているので、
オレオレ実装になりにくい
大人数で開発したときに、実装スタイルが統一されるってわけ
え〜、めんどくさいな。関数ベースビューにゴリゴリ書いてもよくない??
うむ。確かにめんどくさい場面はあるが
似たような一覧画面
似たような編集画面
似たような新規作成画面
こんなもんをコピペで作りまくるのはナンセンスだし
実装者によっては書き方がしっちゃかめっちゃかになる
そんなとき、画面実装に一定の秩序を与えてくれるのがクラスベースビューだ
簡単ではないが、やってみようぜ
簡単じゃないんだ。。。
完成形の確認
今回は一覧の実装ということで、ListViewというクラスベースビューを使ってみる
ListViewを使って機能を加え、デザインもいい感じにしたのが下記だ
え。。。めっちゃいい感じになってない?
最初がクソだからいい感じに見えるだけだ
悪口言われた??
下記の機能を実装した
- カテゴリーと表示件数をFormで絞り込む
- ログイン済みの場合各日記に編集リンクをつける
- ページネーション
関数ベースビューでこの機能をまともに作ろうとすると、めちゃくちゃめんどくさい。
ListViewを使うとどれだけ分かりやすく、簡単に書けるかやってみるぞ!
ベースアプリの準備
リポジトリのクローン
まずは日記アプリのリポジトリをクローンする
$ git clone https://github.com/yheihei/base_diary.git
$ cd base_diary
マイグレーションと初期データ投入
次にマイグレーションとloaddataによる初期データの投入だ
$ python manage.py migrate
$ python manage.py loaddata initial.json
この状態でhttp://127.0.0.1:8000/
を開くと。。。
よし、日記一覧が表示されたね
日記の記事を追加したり、編集したりする場合はhttp://127.0.0.1:8000/admin/diary/post/
に入って行う。
ログインできるユーザー名とパスワードは下記だ。
UserName: yhei
Password: password
完成版ソースだけ見る場合
ちなみに完成形を書いたブランチも用意しておいた。
答えだけ先に見たいやつは下記コマンドでブランチをチェックアウトしてくれ
# 完成版ブランチを見たい場合はこちら
$ git fetch && git checkout feature/#13
ベースアプリの解説
もう一度この日記アプリの画面表示を見てみよう。
トップページにアクセスすると、こんな画面が出てくる
仕様をまとめるとこんな感じだ。
- トップページにアクセスすると、日記一覧が表示される
- 各日記の表示項目は
- タイトル
- 投稿者
- 本文
- 投稿日
- カテゴリー
モデル定義
モデルはこんな感じだな。
from django.db import models
from django.db.models.deletion import CASCADE
from django.contrib.auth.models import AbstractUser
# Create your models here.
class User(AbstractUser):
pass
class Post(models.Model):
id = models.AutoField(primary_key=True)
user = models.ForeignKey(User, on_delete=CASCADE)
title = models.CharField(max_length=2048)
body = models.TextField()
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
categories = models.ManyToManyField(
'Category',
blank=True,
related_name="posts",
)
class Category(models.Model):
name = models.CharField(max_length=1024)
slug = models.CharField(max_length=1024)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
def __str__(self) -> str:
return f'{self.name}'
Postモデル(投稿)と、Userモデル(投稿者)が1対1で紐つく。
さらに、PostモデルとCategoryモデルが多対多で紐ついてる。
Viewとテンプレートの定義
views.pyはこんな感じ
from django.shortcuts import render
# Create your views here.
from .models import Post
# Create your views here.
def index(request):
return render(request, 'index.html', {
'posts': Post.objects.all(),
})
取得する記事をPost.objects.all()で取得して、
index.htmlのテンプレート側に渡し
<html>
<head>
<title>とあるエンジニアの日記帳</title>
</head>
<body>
{% load static %}
<h1>とあるエンジニアの日記帳</h1>
{% for post in posts %}
<div>
<h2>{{ post.title }}</h2>
<div>投稿 : {{ post.user }}</div>
<div>{{ post.body |safe }}</div>
<div>{{ post.updated_at }}</div>
<div>カテゴリー :
{% for category in post.categories.all %}
<span>{{ category }},</span>
{% endfor %}
</div>
</div>
{% empty %}
<p>No Diaries.</p>
{% endfor %}
</body>
</html>
トップページのテンプレートで表示するってだけの単純な構造だ
クラスベースビューで機能を作ってみる
一覧表示を簡単に作る
まず最初に、一覧表示を作ってみる。
diary/views.py に下記を追記する。
from django.views.generic import ListView
class PostListView(ListView):
model = Post
ListViewクラスを継承したPostListViewクラスを作る。
model変数に、Postモデルを指定するだけだ。
これだけで、Postモデルに紐ついたレコードを全て持ってきてくれる。
次にdiary/urls.py
を下記のようにし、トップページを開いたら、PostListViewを呼び出すようにする。
from django.urls import path
from . import views
from diary.views import PostListView
app_name = "diary"
urlpatterns = [
# path("", views.index, name="index"), # コメントアウト
path("", PostListView.as_view(), name="index"), # 追加
]
最後に、PostListViewで表示するテンプレートを作る。
ListViewクラスを継承すると、紐つくテンプレートは下記の規則で自動的に決まる。
class PostListView(ListView):
model = Post
# テンプレートは templates/アプリ名/クラス名(末尾のViewを除く).html が選ばれる
# この場合は templates/diary/post_list.html
ということで、templates/diary/post_list.html
を作って、下記のように記述する。
<html>
<head>
<title>とあるエンジニアの日記帳</title>
</head>
<body>
{% load static %}
<h1>とあるエンジニアの日記帳</h1>
{% for post in object_list %}
<div>
<h2>{{ post.title }}</h2>
<div>投稿 : {{ post.user }}</div>
<div>{{ post.body |safe }}</div>
<div>{{ post.updated_at }}</div>
<div>カテゴリー :
{% for category in post.categories.all %}
<span>{{ category }},</span>
{% endfor %}
</div>
</div>
{% empty %}
<p>No Diaries.</p>
{% endfor %}
</body>
</html>
デフォルトのtemplates/index.html
とほぼ同じだが、下記だけが違う。
<h1>とあるエンジニアの日記帳</h1>
{% for post in object_list %}
for文でcontextから記事一覧を取得するところだ。
ListViewで指定したモデルのレコードがobject_list
という変数に自動的に入ってくる。
このobject_listというのは、context_object_name
で指定することで任意の変数名に変更可能だ。
class PostListView(ListView):
model = Post
context_object_name = 'posts' # object_listからpostsという変数に変える
<h1>とあるエンジニアの日記帳</h1>
{% comment %} PostListViewで指定した変数で取得 {% endcomment %}
{% for post in posts %}
こちらの方が可読性は高くなる。
特に理由がなければ、context_object_name
を指定するほうが良いだろう。
この状態で、アプリを開いてみると。。。
これだけでちゃんと表示された。。。
投稿の取得方法を変更する
ただこれだけで実装終わるわけないじゃん?
普通の開発ってもっと面倒なこといっぱいあるよな??
こっからは実践的な機能を作ってみて、それも全部ListViewでできるぜ?? 便利だぜ?? っていうのを説明していくぞ。
作成日の新しいもの順で並び替える
まずは、日記を新しいもの順で並べ替えてみよう。
モデルからレコードを取得する際に、下記のようにクエリを指定すればいい。
class PostListView(ListView):
...
...
# querysetで取得するものの詳細を決める
queryset = Post.objects.order_by(
'-created_at' # 作成日の新しいものから並べる
)
投稿の取得方法をさらに改善する(N+1問題の解消)
あとは、このアプリ、潜在的な問題をはらんでいる。
えっ!!
テンプレートの下記を見てくれ。
{% for post in posts %}
<div>
...
{% comment %} ここでuserテーブルの情報を取得するSQL発行 {% endcomment %}
<div>投稿 : {{ post.user }}</div>
...
<div>カテゴリー :
{% for category in post.categories.all %}
{% comment %} ここでcategoryテーブルの情報を取得するSQL発行 {% endcomment %}
<span>{{ category }},</span>
{% endfor %}
コメント部分に注目してくれ。
それぞれ forループの中で userテーブルへのアクセス、categoryのforループの中でcategoryテーブルへのアクセスが発生する。
極端な例を挙げる。
記事が1000件あったとして、その1000件に100件ずつカテゴリーが付いているとしたら。
呼ばれるSQL数は
{% for post in posts %}
# ★記事1000件分ループ
<div>
...
# ★ここで1件SQLが呼ばれる
<div>投稿 : {{ post.user }}</div>
...
{% for category in post.categories.all %}
# ★ここで100件SQLが呼ばれる
{% comment %} ここでcategoryテーブルの情報を取得するSQL発行 {% endcomment %}
<span>{{ category }},</span>
{% endfor %}
1000×1(ユーザー) + 1000×100(カテゴリー) = 101000 SQL呼ばれることになる。
するとどうなると思う?
一覧表示がめっちゃ遅くなる。。。??
その通り。いわゆるN+1問題ってやつだ。
これを防ぐには、Postを取得するクエリであらかじめテーブルをJOINする必要がある。
こんな感じだ。
class PostListView(ListView):
model = Post
context_object_name = 'posts'
# querysetで取得するものの詳細を決める
queryset = Post.objects.order_by(
'-created_at' # 作成日の新しいものから並べる
).select_related(
'user', # あらかじめuserテーブルをjoinしておく
).prefetch_related(
'categories', # あらかじめcategoryテーブルをjoinしておく
)
ListViewを使っていても、クエリ指定は自由にできるため、問題なく対応できる。
contextを追加する
次に、下記の仕様を実現する。
・ログイン済みの場合各日記に編集リンクをつける
XX一覧とか言っても、たいていの場合モデルのレコードだけ出して終わるのは稀だ。
付加情報をつける必要が出てくる。
つーことでモデルの情報に応じた付加情報をつける方法を学ぶ。
やり方は簡単で、ListViewのget_context_data
という関数をオーバーライドして設定する。
from django.urls import reverse
class PostListView(ListView):
...
context_object_name = 'posts'
# querysetで取得するものの詳細を決める
queryset = Post.objects.order_by(
'-created_at' # 作成日の新しいものから並べる
).select_related(
'user', # あらかじめuserテーブルをjoinしておく
).prefetch_related(
'categories', # あらかじめcategoryテーブルをjoinしておく
)
...
def get_context_data(self, **kwargs):
# 継承元(ListView)のget_context_dataを呼んで、contextを取得
context = super().get_context_data(**kwargs)
# ログインしていたら、postに編集用のURLを付与する
if self.request.user.is_authenticated:
posts = context['posts'] # このpostsにはquerysetで指定したPostのデータが既に入っている
for post in posts:
# adminの編集リンクをpostのidから作成する
post.edit_url = reverse(f'admin:{post._meta.app_label}_{post._meta.model_name}_change', args=[post.id] )
context['posts'] = posts
return context # 最後に編集したcontextをreturnしてやる
querysetで指定したものを取得した後で、contextを編集できる。
これで、context[‘posts’]内のpostには、edit_urlという日記の編集リンクが付いた。
これをテンプレート側のpostの部分で表示してやる。
{% for post in posts %}
<div>
...
...
<div>カテゴリー :
{% for category in post.categories.all %}
<span>{{ category }},</span>
{% endfor %}
</div>
{% comment %} 編集リンクを追加 {% endcomment %}
{% if post.edit_url %}
<a href="{{ post.edit_url }}">編集</a>
{% endif %}
</div>
{% empty %}
トップページを表示してみると。。。
編集リンクが現れた!!
ページネーションを実装する
次は、ページネーションを実装するぞ。
ListViewにはページネーションの仕組みが最初から入っている。
少ないコード量でページング処理ができるようになる。
まずは、1ページあたり、2件の日記に絞り込んでみる。
diary/views.py
に下記を追加するだけだ。
class PostListView(ListView):
...
...
# ページネーション
paginate_by = 2
これだけで、記事が全件表示ではなく2件表示に変更される。
querysetをいじらなくても勝手にやってくれるのがポイントだ。
さらに、今何ページかの表示、次のページへ遷移するリンクを画面に表示してみるぞ。
テンプレートの末尾に下記を入れるだけだ。
...
{% empty %}
<p>No Diaries.</p>
{% endfor %}
<ul class="pagination">
{% comment %} 前へ {% endcomment %}
{% if page_obj.has_previous %}
<li class="page-item"><a class="page-link" href="?page={{ page_obj.previous_page_number }}">前へ</a></li>
{% endif %}
{% comment %} 間のページ {% endcomment %}
{% for i in page_obj.paginator.page_range %}
{% if page_obj.number == i %}
<li class="page-item active"><a class="page-link">{{ i }}</a></li>
{% else %}
<li class="page-item"><a class="page-link" href="?page={{ i }}">{{ i }}</a></li>
{% endif %}
{% endfor %}
{% comment %} 次へ {% endcomment %}
{% if page_obj.has_next %}
<li class="page-item"><a class="page-link" href="?page={{ page_obj.next_page_number }}">次へ</a></li>
{% endif %}
</ul>
</body>
</html>
page_obj
というオブジェクトに、ページネーション関連の情報が入ってくる。
{% if page_obj.has_previous %}
で、現在のページの前のページがあるか取得できる。
page_obj.previous_page_number
で、前のページの番号を取得。
{% for i in page_obj.paginator.page_range %}
で、間のページ数のリストが取得できる。ここをfor文で回せば、間のページ数のリンクが作れる。
{% if page_obj.has_next %}
で、現在のページの次のページがあるかどうか。
page_obj.next_page_number
で次のページのページ番号が取れる。
実際に遷移できるか確認してみると。。。
ページネーションできてる〜〜〜!!
http://127.0.0.1:8000/?page=1
のように、pageというクエリストリングをつけてページ番号を指定しているが、
下記のようにpage_kwarg
を指定すれば、任意のクエリストリングに変更できる。これも覚えておいてくれ。
class PostListView(ListView):
...
# ページネーション
paginate_by = 2
page_kwarg = 'page' # 未指定でも良い。デフォルトは'page'
さらに。
1ページに表示する件数も変更できるようにしてみる。
↑表示件数の絞り込み部分のベースとなるやつだ。
通常、下記のpaginate_by
の値で表示件数は固定されてしまうが、
class PostListView(ListView):
...
# ページネーション
paginate_by = 2
これを動的に変更する仕組みがある。
例えば、URLにper_page=数値というクエリストリングがついたときに、per_pageで指定された値に、paginate_byを変えてみる。
get_paginate_by
メソッドをオーバーライドすることで可能だ。
def get_paginate_by(self, queryset):
'''
per_pageをクエリによって動的に変える
Notes
-----
https://github.com/django/django/blob/stable/3.2.x/django/views/generic/list.py
'''
# 継承元のget_paginate_byを読んで今のpaginate_byを取得
# なくても良いが、継承元の関数は呼ぶのが定石。継承元で必要な処理をおこなっている場合がある
paginate_by = super().get_paginate_by(queryset)
# per_pageをリクエストのURLから取得
if self.request.GET.get('per_page'):
# paginate_byを指定された値に変更
paginate_by = int(self.request.GET.get('per_page'))
# 変更したpaginate_byを返却する
return paginate_by
get_paginate_by
中でpaginate_by
を変更してやればOKだ。
http://127.0.0.1:8000/?per_page=1
にアクセスしてみると。。。
1ページ1件になった!!
動的な検索を行う
次はカテゴリーで日記を絞り込めるようにしてみるぞ。
category=スラグ名の形式でクエリストリングがついたときに、そのスラグのカテゴリーが付与された日記だけを持ってくる実装だ。
# スラグがdiaryのカテゴリーの日記を持ってくる場合
http://127.0.0.1:8000/?category=diary
リクエストに応じてquerysetを変えるには、get_queryset
というメソッドをオーバーライドして使う。
def get_queryset(self, **kwargs):
# 継承元のget_querysetを呼び出して、querysetを取得
queryset = super().get_queryset(**kwargs)
# カテゴリーのクエリストリングがある場合
if self.request.GET.get('category'):
# querysetにカテゴリーの検索条件を入れて
queryset = queryset.filter(
categories__slug=self.request.GET.get('category')
)
# 変更後のquerysetを返す
return queryset
実際にカテゴリーで絞り込んでみると。。。
日記カテゴリーのカテゴリーだけが表示されてるね
検索フォームを追加する
後もう少しだ。検索フォームを追加していくぞ。
↑のカテゴリのselectボックスと、表示件数のselectボックスを作っていく。
まずはdiary/views.pyに↑のformクラスを作成する。
from django import forms
class PostSearchForm(forms.Form):
category = forms.fields.ChoiceField(
choices = (
# (formのoptionのvalue, 表示名)
('-', '未設定'),
('diary', '日記'),
),
required=False,
widget=forms.widgets.Select
)
per_page = forms.fields.ChoiceField(
label='表示件数',
choices = (
(None, "-"),
(1, "1"),
(2, "2"),
(10, "10"),
(50, "50"),
(100, "100"),
),
required=False,
widget=forms.widgets.Select
)
カテゴリーも表示件数も、forms.widgets.Select
を使い、choicesに条件を入れてやればいい。
あとは、作ったPostSearchForm
クラスをListViewに組み込む。
get_context_data
の末尾に入れるのがいいだろう。
def get_context_data(self, **kwargs):
...
# 検索フォーム
context['search_form'] = PostSearchForm(self.request.GET)
return context
最後にget_context_data
で入れたsearch_form
をテンプレート側で表示する。
<body>
{% load static %}
<h1>とあるエンジニアの日記帳</h1>
{% comment %} search_formを表示する {% endcomment %}
<form method="GET" class="post-form">
{% csrf_token %}
{{ search_form.as_p }}
<input type="submit" value="検索">
</form>
これで、トップページから検索ができるようになった。
selectボックスにカテゴリーモデルの値を入力する
これで検索ができるようになったと思いきや、まだまだ重大な問題がある。
下記を見てくれ。
class PostSearchForm(forms.Form):
category = forms.fields.ChoiceField(
choices = (
# (formのoptionのvalue, 表示名)
('-', '未設定'),
('diary', '日記'),
),
required=False,
widget=forms.widgets.Select
)
categoryのフォームはchoicesに固定でカテゴリーの情報が入っている。
つまり、カテゴリーを増やすたびにハードコーディングで追加する必要がある。
理想は、Categoryのレコードから自動的にひっぱってくることだが。。。
下記だと失敗する。
from .models import Post, Category
...
class PostSearchForm(forms.Form):
category = forms.fields.ChoiceField(
# ★ORMを使って持ってこようとしてもエラーになる
choices = Category.objects.all().values_list('slug', 'name'),
required=False,
widget=forms.widgets.Select
)
なぜなら、メンバ変数の定義時点ではORMが使えないからだ。
そこでPostSearchForm
クラスのコンストラクタでchoicesを後入れしてやる。
こんな感じだ。
class PostSearchForm(forms.Form):
category = forms.fields.ChoiceField(
choices = (),
required=False,
widget=forms.widgets.Select
)
...
def __init__(self, *args, **kwargs):
super(PostSearchForm, self).__init__(*args, **kwargs)
# カテゴリーの選択肢を設定モデルから取得する
self.fields['category'].choices = Category.objects.all().values_list('slug', 'name')
# 0番目に未設定の選択肢を挿入
self.fields['category'].choices.insert(0, ('', '未設定'))
self.fields
のリストの中に、categoryのChoiceFieldオブジェクトが入ってくる。
↑のようにORMを使って編集が可能になる。
よく使うので覚えておいてくれ。
検索フォームの見た目を整える
これでListViewはだいたい使えるようになった?
うむ。使えるようにはなったが。。。
検索フォームの見た目の整え方もついでにやっておくぞ
これはListViewには関係しないが、解説させてくれ
まずは検索フォームの最終形を確認する。
こんな感じに、bootstrapを使って、いい感じの検索フォームの見た目にする。
しかし、formにCSSをあてる方法があまり出回ってない。
公式は
<form method="GET" class="post-form">
{% csrf_token %}
{{ search_form.as_p }}
<input type="submit" value="検索">
</form>
form.as_p
を使えばいい感じにformが出るとか書いてあるが。
このままの見た目でリリースするやつを俺は知らない。
にも関わらず、formにどうやってCSSをあてるかの説明が全然Web上にない。
てことで、bootstrapを使いつつ、最終的なformのHTMLを下記のようにする方法を説明する。
<form method="GET" class="mt-5">
<input type="hidden" name="csrfmiddlewaretoken" value="ZsOeJWyax9kSaYHWxhYwxENncgpqH8HApn5n15fFMLO6sFdUXwzyeTyUEWr0Vbvq">
<div class="row">
<div class="form-group col-3">
Category
<select name="category" class="form-control" id="id_category">
<option value="" selected="">未設定</option>
<option value="diary">日記</option>
</select>
</div>
<div class="form-group col-3">
表示件数
<select name="per_page" class="form-control" id="id_per_page">
<option value="" selected="">-</option>
<option value="1">1</option>
<option value="2">2</option>
<option value="10">10</option>
<option value="50">50</option>
<option value="100">100</option>
</select>
</div>
</div>
<button type="submit" class="btn btn-primary">検索</button>
</form>
ではテンプレート側を改修していく。
まずはbootstrapのCSSとJSをhead部分で読み込んでおく。
<html>
<head>
...
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css" integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO" crossorigin="anonymous">
<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.3/umd/popper.min.js" integrity="sha384-ZMP7rVo3mIykV+2+9J3UJ46jBk0WLaUAdn689aCwoqbBJiSnjAK/l8WvCWPIPm49" crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/js/bootstrap.min.js" integrity="sha384-ChfqqxuZUCnJSK3+MXmPNIyE6ZbWh2IMqE241rYiqJxyMiZ6OW/JmZQ5stwEULTy" crossorigin="anonymous"></script>
</head>
bootstrapではformをいい感じにするコンポーネントがある。
これを真似して、テンプレートを作っていく。
formをfor文で回して各フィールドの構造を作る。下記のような感じだ。
...
<h1>とあるエンジニアの日記帳</h1>
<form method="GET" class="mt-5">
{% csrf_token %}
{% for field in search_form.visible_fields %}
<div class="form-group col-3">
{{ field.errors }}
{{ field.label }}
{{ field }} {% comment %} ここにselectボックスがくる {% endcomment %}
{{ field.help_text }}
</div>
{% endfor %}
<button type="submit" class="btn btn-primary">検索</button>
</form>
...
search_form.visible_fields
にはhidden input以外のフィールドが入ってくる。
それをfor文で回すことで、
<div class="form-group col-3">
各selectボックス
</div>
という構造を作りながらformのフィールドを作ることができる。
が、ここで問題がある。
<div class="form-group col-3">
{{ field.errors }}
{{ field.label }}
{{ field }} {% comment %} ここにselectボックスがくる {% endcomment %}
{{ field.help_text }}
</div>
bootstrapのForm Controlsを見ると、{{ field }}
の部分にform-control
というクラスをつけてやる必要がある。
ただ、この構造だとテンプレート側からつけるのは不可能だ。
ではどうするか。
Formクラスのコンストラクタ内で指定する。
class PostSearchForm(forms.Form):
...
def __init__(self, *args, **kwargs):
...
...
for field in self.fields.values():
field.widget.attrs["class"] = "form-control"
self.fieldsの中には各フィールドの情報が入っているので。
それをfor文で回すことで、ここのfieldのclass属性に、form-control
をつけてやる。
すると。。。
いい感じになってる!!!
まあこんな感じで
formを使ったhtmlの組み立てはクセがある
formのデザインをするときは、Djangoのルールに従いながらどう実装するかを常に考えるようにしてくれ。
Bootstrapを使って見た目を整えてみよう
ということで、最後はやってみようのコーナー。
Bootstrapを使って、下記のようなデザインを実現してみてくれ
解答は下記にあるので、どうしてもできなかったら見てくれ。
まとめ
ListViewの使い方をまとめるぞ
- 一覧表示をするにはListViewを継承する
model
変数にモデルを定義すると、勝手にレコードを取得するqueryset
でクエリを指定。get_queryset
関数で動的に検索クエリを設定可能get_context_data
でcontextを編集できるget_paginate_by
関数で動的に表示件数を変更- N+1に気をつけろ
\ 講座で学んだことを即アウトプットしよう /
次の講座
Djangoの基礎を学びたい方は
Djangoの基礎を固めたい方はこちら(セール時に買うのがおすすめ)
『Djangoパーフェクトマスター』〜インスタ映えを支えるPython超高速開発Webフレームワークを徹底解説!
動画講座で手を動かしながら、ほとんどのことが学べます。
ウサギもここから始めました。
コメント
[…] ListViewの使い方をマスターしようLIstViewを使って一覧表示を簡単に作る。並… […]