知識の枝

"All is well"

Django ManyToManyで逆参照&filterする

約73日前 2021年8月16日20:25
デジタル
Django

改訂履歴


2021/8/16 投稿

1. 背景


Djangoを使った開発中に覚えたことを備忘録として残します。

今回はManyToManyの子から親を逆参照する方法を解説します。


2. ゴール


ManyToManyの逆参照を用い、オブジェクトの絞り込みを行う。


3. はじめに


ManyToManyの逆参照方法自体は]ForeignKeyの逆参照とほぼ同じです。

ForeignKeyの場合の方法が知りたい方は下記の記事へどうぞ。
Django ForeignKeyを逆参照する方法


さて今回は逆参照をうまく使いつつ絞り込みを行うという話ですが、まずイメージを掴んでもらいたいと思います。


動画投稿サイト(例えばYoutube)から、APIを使って

「あるキーワード」で検索した結果を定期的に取得する

と仮定します。


取得した動画の情報は「Video」モデルとしてデータベース(DB)に登録。

Videoに登録される情報は「動画ID」、「タイトル」、「投稿日」の3つとします。


ついでに検索を行った履歴を「Searched」モデルとして登録しておきます。

中身は「検索キーワード」、「検索日時」、「検索結果」とし、

検索結果には上記で作成したVideoオブジェクトをManyToManyで紐付けます。


Searchedオブジェクトは検索の実行ごとに作成され、

「■月■日に"●●●"というキーワードで検索したら、これだけの動画が見つかりました」

という情報を保存します。






もちろん検索結果の一部がダブることもありますし、別のキーワードで同じ動画が見つかることもあります。

Videoのダブリ登録は避け、別キーワードで同じVideoが見つかった際はそのSearchedオブジェクトにManyToManyで紐付けます。
(色々なキーワードで同じVideoが見つかる可能性があるため、ForeignKeyではなくManyToManyを採用しています)

こんな登録関係になる。




このように「検索」⇔「登録」作業が複数回実行されたとき、”ある検索キーワード”に紐づく動画の数がどんどん増えることが予想できます。


前置きが長くなりましたが、ここからが本題です。

さてこのとき、キーワード「●●●」に紐付いているVideoオブジェクトはどのように抽出したら良いでしょうか?


Videoオブジェクトには「自分がどんなキーワードで検索された動画」かを示す情報がありません。

どんなフィルターを掛ければ良いのか?

次項でその方法を解説します!


4. フィルター方法


4.1 - 逆参照


上記で述べたように、Videoオブジェクトには「自分がどんなキーワードで検索された動画」かを示す情報がありません。

「Video」と「検索キーワード」を結ぶ唯一の接点は検索履歴モデル「Searched」です。

このSearchedをVideo側から逆参照できれば、キーワードでフィルタリングできそうですね。


下記のように逆参照しながら絞り込み可能です。
videos = Video.objects.filter(searched__keyword=●●●)
Videoモデルには「searched」というカラムは存在しませんが、SearchedモデルによってManyToManyで選択されている為、モデル名を小文字にした文字列(今回の場合はsearched)で絞り込むことができます。


「searched__keyword=●●●」の部分は、「Searchedオブジェクトのkeywordカラムが”●●●”のモデル」という条件になります。

すなわち「”●●●”というキーワードで検索したときの検索結果」に紐付いているVideoオブジェクトを全て洗い出すことができます。


4.2 - 重複の削除


但し、このままでは絞り込んだ結果の中でVideoオブジェクトが重複する可能性がある為、重複を除去します。

重複の除去は「distinct()」によって実現できます。
videos = Video.objects.filter(searched__keyword=●●●).distinct().order_by("posted_at")
こんな感じですね。


上記を実行することで、変数「videos」には「キーワード”●●●”で検索したらヒットするVideo」が重複無しで格納されます。


5. さいごに


ManyToManyの逆参照は上手くいきましたか?

今回紹介したような絞り込みが必要な場合は、ぜひ上記の方法を試してみて下さい!

お疲れ様でした。