はじめに
Qiitaや多くのサイトでよく見かけるタグ機能を実装する。
ここではDjango-girlsチュートリアル終了後のblogアプリに多対多の関係をもつTagモデルをつくる。
postモデルは複数のtagモデルを持ち、tagモデルも複数のpostモデルを持っている状態を目指す。多対多の関係。
前提
Django-girlsチュートリアルでpostモデルがすでに作成済み。
タグの登録は管理画面上のみでおこなうので、タグの登録画面やフォームは説明しない。
環境
macOS
Django version 2.2.16
手順
まず、タグモデルを作成する。TagモデルをPostモデルより上に定義。
'blog/models.py'
class Tag(models.Model): #tagモデルを新しく定義する
name = models.CharField(max_length=200)
def __str__(self):
return self.name
class Post(models.Model):
author = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
title = models.CharField(max_length=200)
text = MarkdownxField()
created_date = models.DateTimeField(default=timezone.now)
published_date = models.DateTimeField(blank=True, null=True)
tags = models.ManyToManyField(Tag) #この行を追加する
マイグレーションを作成、適応させる
'ターミナル'
MacBook djangoblog % python manage.py makemigrations blog
Migrations for 'blog':
blog/migrations/0005_auto_20201128_1608.py
- Create model Tag
- Add field tags to post
MacBook djangoblog % python manage.py migrate blog
Operations to perform:
Apply all migrations: blog
Running migrations:
Applying blog.0005_auto_20201128_1608... OK
管理画面にコメントモデルを登録する
'admin.py'
from django.contrib import admin
from .models import Post, Comment, Tag #追加部分
from markdownx.admin import MarkdownxModelAdmin
# 管理画面にpostモデルを登録
admin.site.register(Post, MarkdownxModelAdmin)
# 管理画面にコメントモデルを登録
admin.site.register(Comment)
# 管理画面にtagモデルを登録 #追加部分
admin.site.register(Tag)
これで管理画面でtagの登録、削除などができるようになりました。
Postオブジェクトを選択すると、tagが選べるようにもなっているはずです。
テンプレートの表示部分は省略します。
タグに紐づいている記事一覧を表示するやり方
大まかな流れとしては、テンプレート上のタグの名前をtag/idの形式でリンク化。
urlを経由して、view.py
でtagの詳細ページに紐づくpost一覧を表示するテンプレートを追加するという流れ。
まず最初に、urls.py
で以下のように設定します
'urls.py'
from django.urls import path
from . import views
urlpatterns = [
path('', views.PostList.as_view(), name='post_list'),
path('post/<int:pk>/', views.PostDetail.as_view(), name='post_detail'),
path('post/<int:pk>/comment/', views.add_comment_to_post, name='add_comment_to_post'),
# 下の行の部分を追加。パスの名付け方やview.pyでの関数名、nameの部分は任意に名付けられる。
path('tag/<int:pk>/', views.TagDetail.as_view(), name='tag_detail')
]
urlで追記後、view.py
でtagに紐づくpostをテンプレートに返す部分を追記していく
importとclass部分は自分がわかりやすいところに書けば大丈夫です。
'view.py'
~
from .models import Post, Tag
from django.views.generic import DetailView
~
class TagDetail(DetailView): # 追記部分。Detailviewという汎用クラスビューを利用する。
model = Tag
~
次にテキストエディターで新規にからのテンプレートを作成し、blog/templates/blog
の他のファイルがあるところに保存します。ファイル名はtag_detail.html
とします。(Detailviewを使用した時のデフォルトテンプレート名です。model_detail.htmlとすることでview内でテンプレートの指定をする必要がなくなります)
これでURL欄で/tag/1
と打ち込むと、空のテンプレートにアクセスできるようになりました。あとはテンプレートをデザインしていくだけです。 すでにあるpost_detailを使いまわすのが簡単だとおもいます。テンプレート拡張などしている場合は各自調整してください。
`/tag_detail.html`
{{ tag.name }} # tagの名前を表示
{{ tag.post_set.all }} # tagに紐づいたpost_setをとりだす。
{% for post in tag.post_set.all %} # post_setをfor文でひとつづつとりだしていく
{{ post.title }}
{% endfor %}
これでtag/idとアクセスしたときにタグに紐づいているpostが一覧表示されるようになりました。
やり方2, モデルの詳細画面に紐づくモデルのobject_listをページネーションさせたい場合
上記のDetailViewでは、ひもづいたpost_setにはページネーションを適応させられない。
SingleObjectMixin, ListViewを使用する。view.pyを大幅に修正していく。
`view.py`
from django.views.generic.detail import SingleObjectMixin
from django.views.generic import ListView
~
class TagDetail(SingleObjectMixin, ListView):
paginate_by = 7
template_name = "blog/tag_detail.html"
def get(self, request, *args, **kwargs):
self.object = self.get_object(queryset=Tag.objects.all())
return super().get(request, *args, **kwargs)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['tag'] = self.object
return context
def get_queryset(self):
return self.object.post_set.all().order_by('-created_date')
~
'sample.html'
{% for post in page_obj %}
<div class="content">
<div class="title">
<a href="{% url 'post_detail' post.pk %}">{{ post.title }}</a>
</div>
<div class="datatime">{{ post.created_date|date:"Y/n/j" }}</div>
</div>
{% endfor %}
これで紐づくモデルのページネーションが反映されるようになった。
このやり方は日本語の記事はほぼないので公式サイトをみながらでないと、まず実装に手間取る。以下の公式を参照。
https://docs.djangoproject.com/en/3.0/topics/class-based-views/mixins/#using-django-s-class-based-view-mixins
参考文献
https://docs.djangoproject.com/ja/3.1/topics/db/examples/many_to_many/
https://tutorial-extensions.djangogirls.org/ja/homework_create_more_models
https://tutorial.djangogirls.org/ja/template_extending/
https://docs.djangoproject.com/en/3.0/topics/class-based-views/mixins/#using-django-s-class-based-view-mixins
更新
2020/12/4 文章を編集
2021/01/03 tagにひもづいたpostを取り出す方法を追記
2021/01/17 紐づいたモデルへのページネーション適応について