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

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

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

前回の記事

https://qiita.com/Bashi50/items/9e1d62c4159f065b662b

関連記事: データベースから取得してレンダリング
[【Python Django】初心者プログラマーのWebアプリ#5 【データベースの値扱う】](https://qiita.com/Bashi50/items/280bc341eeec5d7f8797)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/260345/e01c93ce-0bc6-1984-da1d-354054ad3484.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 データベースから取得](https://qiita.com/Bashi50/items/9e1d62c4159f065b662b)


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

### 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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/260345/1d95ab85-28a7-5e74-336b-6f90cd3c027f.png)


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

# 1対多(One to many)取得
## 単一取得
リレーションで紐づいてるデータを１件簡単に取得します。
`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テーブル)があるので全部取得してみます。

```py
>>> 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`のデータを取得しています。
特定の一件のデータというのを限定しないで取得してるということです。

```py
>>> 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と同じように取得できます。

## 例

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

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