前回に記事はこちら。
モデル
最初にモデルを編集します。
models.pyを下記のようにします。
Tagクラスを作成して、Postクラスに新たにTagと紐づけるカラムを追加します。
posts/models.py
class Tag(models.Model):
title = models.CharField(max_length=255, verbose_name='タイトル')
created_at = models.DateTimeField(auto_now_add=True, verbose_name='登録日')
updated_at = models.DateTimeField(auto_now=True, verbose_name='更新日')
class Meta:
verbose_name = 'タグ'
verbose_name_plural = 'タグ'
def __str__(self):
return self.title
class Post(models.Model):
title = models.CharField(max_length=255, verbose_name='タイトル')
body = models.TextField(verbose_name='内容')
is_public = models.BooleanField(default=True, verbose_name='公開')
published_at = models.DateTimeField(verbose_name='公開日')
category = models.ForeignKey(Category, on_delete=models.SET_NULL, null=True, verbose_name ='カテゴリ')
tags = models.ManyToManyField(Tag, related_name='tags', blank=True, verbose_name ='タグ')
class Meta:
verbose_name = '投稿'
verbose_name_plural = '投稿'
def __str__(self):
return self.title
下記コマンドを実行しマイグレーションファイルを作成します。
$ python manage.py makemigrations posts
posts/migrations/0003_tag_alter_category_options_post_tags.pyというファイルが生成されます。
migrateコマンドを実行しましょう。
$ python manage.py migrate
これでテーブルが作成されます。
データベースの確認
テーブルが作られているか確認してみましょう。
$ python manage.py dbshell
sqlite> .table
タグを保存するposts_tagと、関連付けするposts_post_tagsテーブルが作成されています。
sqliteを終了します。
sqlite> .quit
管理画面にタグ項目を追加
タグを登録できるように管理画面に追加します。
投稿編集画面でもタグが選択できるように修正しましょう。
posts/admin.py
from django.contrib import admin
from .models import Post, Category, Tag
# ...
class TagAdmin(admin.ModelAdmin):
list_display = ['title', 'created_at', 'updated_at']
admin.site.register(Tag, TagAdmin)
管理画面にタグ項目が追加されました。
一覧画面にも何のタグが紐付けされているかわかるように表示してみましょう。
posts/admin.py
from django.contrib import admin
from .models import Post, Category, Tag
class PostAdmin(admin.ModelAdmin):
list_display = ['title', 'category', 'get_tags', 'published_at']
search_fields = ['title', 'body']
def get_queryset(self, request):
query = super().get_queryset(request)
return query.prefetch_related('category', 'tags')
def get_tags(self, obj):
return ','.join([i.title for i in obj.tags.all()])
get_tags.short_description = 'タグ'
タグはカテゴリーと違く複数存在するのでget_tagsというメソッドを新しくを作成して,区切りで表示するようにします。
get_tags.short_descriptionは一覧のカラム名などに使用されます。
投稿の編集画面からもタグが選択できるようになっているので、タグの登録と投稿を編集してタグを紐づけてみましょう。
これで管理画面側の設定は完了です。
詳細画面にタグ名表示
次は管理画面で登録した情報をフロント画面に表示してみましょう。
タグは複数割り当てられるのでpost.tags.allを展開する形になります。
posts/templates/posts/post_detail.html
<ul>
{% for tag in post.tags.all %}
<li>{{ tag }}</li>
{% endfor %}
</ul>
一覧画面にタグ名表示
管理画面と同じようにモデルに一覧表示用のメソッドを作成します。
posts/models.py
class Post(models.Model):
# ...
def tag_list(self):
return ','.join([i.title for i in self.tags.all()])
prefetch_relatedにも忘れず追加しておきましょう。
posts/views.py
class IndexView(generic.ListView):
paginate_by = 10
def get_queryset(self):
# ...
posts = posts.order_by('-published_at').prefetch_related('category', 'tags')
return posts
テンプレートではモデルで作成したtag_listを呼び出します。
posts/templates/posts/post_list.html
<ul>
{% for post in post_list %}
<li>
<a href="{% url 'posts:detail' post.id %}">{{ post.title }}</a>
{{ post.category }}
{{ post.tag_list }}
</li>
{% endfor %}
</ul>
タグで絞り込む
タグ毎に表示できるようにしてみます。
/posts/tag/1/にアクセスするとタグIDが1の投稿一覧を表示するようにします。
ルーティング
タグ用のルーティングを設定します。
posts/views.py
urlpatterns = [
path('', views.IndexView.as_view(), name='index'),
path('category/<int:category>/', views.IndexView.as_view(), name='index_category'),
path('tag/<int:tag>/', views.IndexView.as_view(), name='index_tag'),
path('<int:pk>/', views.DetailView.as_view(), name='detail'),
]
ビュー
ビューはカテゴリーと同じように増やすだけですね。
posts/views.py
from .models import Post, Category, Tag
class IndexView(generic.ListView):
paginate_by = 10
def get_queryset(self):
posts = Post.objects.filter(is_public=True)
# カテゴリーが指定されていたら条件追加
if ('category' in self.kwargs):
posts = posts.filter(category=self.kwargs['category'])
# タグが指定されていたら条件追加
if ('tag' in self.kwargs):
posts = posts.filter(tags=self.kwargs['tag'])
posts = posts.order_by('-published_at').prefetch_related('category', 'tags')
return posts
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
# ナビゲーション用カテゴリー
context['categories'] = Category.objects.values('id', 'title')
# ナビゲーション用タグ
context['tags'] = Tag.objects.values('id', 'title')
# タイトルの制御
context['title'] = 'お知らせ'
if ('category' in self.kwargs):
result = next(
item for item in context['categories']
if item['id'] == self.kwargs['category']
)
context['title'] = result['title']
if ('tag' in self.kwargs):
result = next(
item for item in context['tags']
if item['id'] == self.kwargs['tag']
)
context['title'] = result['title']
return context
テンプレート
ナビゲーション用にtagsを渡しているのでカテゴリーと同じように展開して完成。
posts/templates/posts/post_list.html
<ul>
{% for tag in tags %}
<li><a href="{% url 'posts:index_tag' tag.id %}">{{ tag.title }}</a></li>
{% endfor %}
</ul>
関連するデータが複数あるのでその部分さえ気をつければ表示部分は一対多とほとんど同じでできますね。
GitHubはこちら。

