3
10

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Djangoアプリにタグ機能を追加したメモ

Last updated at Posted at 2020-11-28

はじめに

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 紐づいたモデルへのページネーション適応について

3
10
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
10

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?