LoginSignup
5
5

More than 1 year has passed since last update.

[初心者] #2 Django Query データベース取得 1対多と多対多

Last updated at Posted at 2020-09-22

前回のDjangoの基本的なデータベースからの取得でしたが、今度は1対多(OneToMany, hasMany)と多対多(ManyToMany) をどのようにするか書きます。

実践的なアプリなら、1対多多対多でテーブル構築していくと思います。
思い通りにDjangoから取得して思い通りのアプリを作りましょう!

前回の記事

関連記事: データベースから取得してレンダリング
【Python Django】初心者プログラマーのWebアプリ#5 【データベースの値扱う】

image.png

test/accounts/models.py
from django.db import models

class Customer(models.Model):
    name = models.CharField(max_length=100)
    phone = models.CharField(max_length=20)
    email = models.EmailField(max_length=255)
    age = models.IntegerField()
    created_at = models.DateTimeField(auto_now_add=True)

    def __str__(self):
        return self.name


class Tag(models.Model):
    name = models.CharField(max_length=50)

    def __str__(self):
        return self.name


class Product(models.Model):
    CATEGORY = (
            ('daily', '日用品'),
            ('child', '子供向け'),
            ('sports', 'スポーツ'),
            )

    name = models.CharField(max_length=100)
    price = models.FloatField()
    category = models.CharField(max_length=50, choices=CATEGORY)
    description = models.CharField(max_length=2000, blank=True)
    created_at = models.DateTimeField(auto_now_add=True, null=True)
    tags = models.ManyToManyField(Tag)

    def __str__(self):
        return self.name


class Order(models.Model):
    STATUS = (
            ('processing', '処理中'),
            ('delivering', '配送中'),
            ('canceled', '配送中止'),
            ('Done', '配送済'),
            )

    customer = models.ForeignKey(Customer, on_delete=models.CASCADE)
    product = models.ForeignKey(Product, on_delete=models.CASCADE)
    date_created = models.DateTimeField(auto_now_add=True)
    status = models.CharField(max_length=50, choices=STATUS)

models.ForeignKey(Customer)のようにすれば1対多が作れます。

前回の記事参考に、これらからマイグレーションファイルを作成してmigrate。
試してみたい方は前回示したように管理画面からデータを追加してください。

[初心者]Django Query データベースから取得

テーブル

生成されるテーブルを示します。

Customer (accounts_customerテーブル)

name type notnull pk
id integer 1 1
name varchar(100) 0 0
price varchar(20) 0 0
email varchar(255) 0 0
created_at datetime 0 0

Product (accounts_productテーブル)

name type notnull pk
id integer 1 1
name varchar(100) 0 0
price integer 0 0
category varchar(50) 0 0
description varchar(2000) 0 0
created_at datetime 0 0

Order (accounts_orderテーブル)

name type notnull pk
id integer 1 1
created_at datetime 0 0
status varchar(50) 0 0
customer_id integer 0 0
product_id integer 0 0

これが、Orderモデルから生成されたテーブルになります。
ForeignKeyを二つ設定したのでcustomer_id, product_idが作られています。

Tags (accounts_tagテーブル)

name type notnull pk
id integer 1 1
name varchar(50) 0 0

作成される中間テーブル (accounts_product_tagsテーブル)

name type notnull pk
id integer 1 1
product_id integer 0 0
tag_id integer 0 0

Productクラスのところに、tags = models.ManyToManyField(Tag)のように書いていますが、これだけでproduct_id tag_idをもつ中間テーブルが作成されます。

image.png

リレーション、紐づいたデータ取得

1対多(One to many)取得

単一取得

リレーションで紐づいてるデータを1件簡単に取得します。
orderでデータを取得。紐づいている注文者のTanakaさんのデータを取得できます。

>>> order = Order.objects.first()
>>> order.customer
<Customer: Tanaka>

>>> order.customer.name
'Tanaka'

最初に登録されたOrderテーブルのデータを取得。これ、一件なので紐づくデータを限定できるのでこのように取得もできてます。

複数取得

orderテーブルのcustomer_id(modelでForeignKey(Customer)としたので生成された)とcustomerテーブルのidで紐づいているのでデータが取得できますね。

方法1

最初に一件のデータ決める。お客さん(customer)はたくさん注文の履歴(orderテーブル)があるので全部取得してみます。

>>> customer = Customer.objects.get(pk=1)
>>> orders = customer.order_set.all()
>>> orders
<QuerySet [<Order: Order object (3)>, <Order: Order object (4)>]>

>>> for order in orders:
...     print(order.product)
... 
<Product: >
<Product: 天然水2L>

方法2

以下の例はcustomerで名前にSを含んでいる人が注文したorderのデータを取得しています。
特定の一件のデータというのを限定しないで取得してるということです。

>>> o = Order.objects.filter(customer__name__contains='S')
>>> o
<QuerySet [<Order: Order object (3)>, <Order: Order object (4)>]>

>>> for query in o:
...     print(query.product)
... 

天然水2L

Customer: Satoさん を登録して
order: 机、天然水2L を

管理画面で登録したのでデータを取得できています。

今回のデータをあまり入れてないので、結果が同じでしたが💦
例えばSで始まる、Saitoさんとかの注文データもまとめて取得できるということになります。

もちろん、Satoさんなら現実にはたくさんいるので大量に取得することになりますので実際の運用するような時はDBから取得してから必要なデータを取り出す。
ではなく、条件をもう少し追加して絞って取得しましょう。

補足

customer__name__exact='Sato'のようにすれば完全一致です。
customer__name='Sato'exact抜いても同じ意味です。明示的に書きたいなら。

customer__age=20のよう数字でもかけます。

多対多 (many to many)取得

先ほどの方法2と同じように取得できます。

>>> products = Product.objects.filter(tags__name='Summer')
>>> products
<QuerySet [<Product: 花火>, <Product: ビーチボール>]>
>>> for p in products:
...     p.price
... 
2000
1200

tagsテーブルのsportsタグがついたデータを取得しています。

5
5
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
5
5