はじめに
Djangoの多対多(ManyToMany)関係のモデルを利用して、簡易的なタグ機能を作成してみました。
環境
- Ubuntu 16.04 LTS
- Django 2.2
- Python 3.6
作成したもの
ブログシステムなどでよくある、投稿記事に対してジャンルごとにタグづけが行える機能のようなイメージで、記事に対してタグを付加・削除したり、特定タグのつけられた記事の検索を行うことができるモデルを作ってみました。
モデル
今回、作成したモデルはシンプルにタグを定義するTag
と、記事を定義するArticle
の2つ。
記事とタグの紐づけはManyToManyFiled
を使用することで実現し、これによりDjango側でTag
-Article
間の多対多の関係を紐づける中間テーブルが自動的に生成される。
from django.db import models
# タグ
class Tag(models.Model):
name = models.CharField(max_length=32)
def __str__(self):
return self.name
# 記事
class Article(models.Model):
title = models.CharField(max_length=128)
tags = models.ManyToManyField(Tag)
def __str__(self):
return self.title
使用方法
各インスタンス生成~タグ付けまで
# タグの生成
t1 = Tag.objects.create(name="Django")
t2 = Tag.objects.create(name="Python")
# 記事の生成
a = Article.objects.create(title="Djangoでタグ機能を作る")
# 記事にタグを付加
a.tags.add(t1)
a.tags.add(t2)
# 更新
a.save()
記事(Article)を起点にした操作
ManyToManyField
を設定したArticle
を起点にした操作は、変数自体の属性が持つall
、add
、remove
などの各種メソッドを利用して操作を行うことができる。
記事に付加されたタグの取得
Article
インスタンスに付加されたタグを確認するには、all
メソッドで取得できる。
a = Article.objects.get(name="Djangoでタグ機能を作る")
tag_list = a.tags.all()
記事へタグを追加
タグの追加はadd
メソッドで行うことが可能。
a = Article.objects.get(name="Djangoでタグ機能を作る")
t = Tag.objects.get(name="Django")
a.tags.add(t)
記事へタグを設定(置き換え)
タグの設定はset
メソッドで一括して行うことも可能。
※ 不足分の追加ではなく、置き換えとなるので注意。
t1 = Tag.objects.get(name="Django")
t2 = Tag.objects.get(name="Python")
a = Article.objects.get(name="Djangoでタグ機能を作る")
a.tags.set([t1, t2])
記事から特定タグを削除
関連付けられた1つのタグを削除したい場合は、削除したいタグのインスタンスをremove
メソッドで指定することで削除できる。
※ これは記事Article
とタグTag
の関係が削除されるのみで、タグTag
そのものは削除されない。
a = Article.objects.get(name="Djangoでタグ機能を作る")
t = Tag.objects.get(name="Django")
a.tags.remove(t)
記事からタグを一括削除
clear
メソッドを使用すると、関連付けられたタグを一括で削除することも可能。
※ これも記事Article
とタグTag
の関係が削除されるのみで、タグTag
そのものは削除されない。
a = Article.objects.get(name="Djangoでタグ機能を作る")
a.tags.clear()
特定のタグが付けられた記事を検索・取得
特定のタグがつけられた記事を検索する場合は、通常の絞り込みと同様にfilter
メソッドでTag
のインスタンスを条件に指定することで検索できる。
t = Tag.objects.get(name="Django")
article_list = Article.objects.filter(tags=t)
タグのインスタンスを用いて検索することに加えて、次のようなタグTag
の持つ属性を利用した検索も可能。
# TagのnameがDjangoというタグがつけられた全ての記事(Article)を取得
article_list = Article.objects.filter(tags__name="Django")
タグ(Tag)を起点にした操作
ManyToManyField
を設定しなかったタグ側にも、逆参照という形で操作を行うこともできる。
対向側にはモデル名_set
という属性が自動的に付加されているので、これを使用して操作を行う。
特定のタグのついた記事の取得
タグを起点に、モデル名_set
のall
メソッドで該当のタグが付加されている全ての記事を取得することが可能。
t = Tag.objects.get(name="Django")
article_list = t.article_set.all()
特定のタグを特定の記事につける
記事にタグをつける場合と同様add
メソッドでタグの付加が可能。
a = Article.objects.get(name="Djangoでタグ機能を作る")
t = Tag.objects.get(name="Django")
t.article_set.add(a)
特定のタグを特定の記事から削除
記事にタグをつける場合と同様remove
メソッドでタグの削除が可能。
a = Article.objects.get(name="Djangoでタグ機能を作る")
t = Tag.objects.get(name="Django")
t.article_set.remove(a)
特定のタグを全ての記事から削除
clear
メソッドでその時、そのタグを付加されている全ての記事から削除することが可能。
この操作に関しては、記事を起点に操作するよりも、タグ起点で行った方がシンプルになると思われる。
t = Tag.objects.get(name="Django")
t.article_set.clear()
補足: ManyToManyFieldを使用する場合の注意
多対多の関係を実現するManyToManyField
を使用するには、紐づけられる双方のインスタンスが主キーを持っている必要がある。
Djangoのモデルでは主キーを明示的に設定しなくても、デフォルトでid
という主キーとなる変数が追加されますが、これはcreate
メソッドでインスタンスを生成するか、save
メソッドを呼び出してDBへ初回保存された際に値が確定・生成されるため、注意が必要。
主キーが確定しない状態でadd
メソッドを用いて要素の追加を行うとエラーとなってしまう。
>>> a = Article(title="Djangoでタグ機能を作る")
>>> a.tags.add(t1)
ValueError: "<Article: Djangoでタグ機能を作る>" needs to have a value for field "id" before this many-to-many relationship can be used.
create
で生成するか、ManyToManyField
へ値を設定する前にsave
を一度呼び出してDBへ保存しておけばエラーとならないのですが、一時的に中途半端な状態でDBへ保存されてしまうことになるため、完全に出来上がるまでDBへ保存したくない場合は、トランザクション制御の機能などを利用する必要がある。
from django.db import transaction
with transaction.atomic():
a = Article(title="Djangoでタグ機能を作る")
a.save() # ここではDBは変更されない
t = Tag.objects.get(name="Django")
a.tags.add(t)
a.save() # ここではDBは変更されない
# withブロックを抜けた時点で変更内容がDBへCommitされる
参考
多対多 (many-to-many) 関係 | Django documentation | Django
https://docs.djangoproject.com/ja/2.1/topics/db/examples/many_to_many/
データベースのトランザクション | Django documentation | Django
https://docs.djangoproject.com/ja/2.1/topics/db/transactions/