Djangoでデータを取得してみる(ManyToMany)

データの準備

モデルを作成。

$ vi books/models.py
・・・
class Store(models.Model):
    name = models.CharField(max_length=100)

class Book(models.Model):
    title = models.CharField(max_length=100)
    price = models.IntegerField()
    author = models.ForeignKey(Author, on_delete=models.CASCADE)
    stores = models.ManyToManyField("Store")

マイグレーションを実施。

$ python3 manage.py makemigrations
$ python3 manage.py migrate

ManyToManyを指定すると、自動的に中間テーブルが作られる。

$ sqlite3 db.sqlite3
> .schema books_book_stores
CREATE TABLE IF NOT EXISTS "books_book_stores" (
  "id" integer NOT NULL PRIMARY KEY AUTOINCREMENT,
  "book_id" bigint NOT NULL REFERENCES "books_book" ("id") DEFERRABLE INITIALLY DEFERRED,
  "store_id" bigint NOT NULL REFERENCES "books_store" ("id") DEFERRABLE INITIALLY DEFERRED
);
CREATE UNIQUE INDEX "books_book_stores_book_id_store_id_e76b3c7a_uniq" ON "books_book_stores" ("book_id", "store_id");
CREATE INDEX "books_book_stores_book_id_43c79a35" ON "books_book_stores" ("book_id");
CREATE INDEX "books_book_stores_store_id_d8b84690" ON "books_book_stores" ("store_id");

データを作成し、投入。

$ vi books/fixtures/initial_data.json
[
  ・・・

  {"model": "books.store", "pk": 1, "fields": {"name": "ショップA"}},
  {"model": "books.store", "pk": 2, "fields": {"name": "ショップB"}},
  {"model": "books.store", "pk": 3, "fields": {"name": "ショップC"}},

  {"model": "books.book", "pk": 1, "fields": {"title": "book1", "price": 1000, "author_id":1, "stores":[1,2,3]}},
  {"model": "books.book", "pk": 2, "fields": {"title": "book2", "price": 2000, "author_id":1, "stores":[]}},
  {"model": "books.book", "pk": 3, "fields": {"title": "book3", "price": 3000, "author_id":1, "stores":[1]}},
  {"model": "books.book", "pk": 4, "fields": {"title": "book4", "price": 4000, "author_id":2, "stores":[2,3]}},
  {"model": "books.book", "pk": 5, "fields": {"title": "book5", "price": 5000, "author_id":2, "stores":[]}}
]

$ python3 manage.py loaddata initial_data.json

ManyToManyのフィールドには、「"stores":[1,2,3]」のような感じで、配列で指定すると、 中間テーブルにデータが設定される。

> select * from books_book_stores;
id book_id store_id
1 1 1
2 1 2
3 1 3
4 3 1
5 4 2
6 4 3

データの取得

storeテーブルのデータは、「book.stores.all」で参照できる。

・・・
class BookList(ListView):
    model = Book
・・・
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
</head>
<body>
  {% for book in object_list %}
    {{ book.title }}<br>
    {% for store in book.stores.all %}
      ・{{ store.name}}<br>
    {% endfor %}
  {% endfor %}
</body>
</html>

f:id:yk5656:20210504175743j:plain

上記のままだと、sotresを参照するたびにSQLが実行されるが、 「prefetch_related('stores')」を付けると、まとめてデータを取得するので、 SQLの実行回数が減る。

・・・
class BookList(ListView):
    model = Book
    queryset = Book.objects.all().prefetch_related('stores')
・・・

絞り込み

「stores__name」のような感じで条件を指定することも可。

・・・
class BookList(ListView):
    model = Book
    queryset = Book.objects.filter(stores__name='ショップA')
・・・

f:id:yk5656:20210504175816j:plain

逆参照

Book側からは「sotres」で参照できたが、Stores側からは「book_set」で参照できる。