前回のDjangoの基本的なデータベースからの取得でしたが、今度は1対多
(OneToMany, hasMany)と多対多
(ManyToMany) をどのようにするか書きます。
実践的なアプリなら、1対多
と多対多
でテーブル構築していくと思います。
思い通りにDjangoから取得して思い通りのアプリを作りましょう!
前回の記事
関連記事: データベースから取得してレンダリング
【Python Django】初心者プログラマーのWebアプリ#5 【データベースの値扱う】
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。
試してみたい方は前回示したように管理画面からデータを追加してください。
テーブル
生成されるテーブルを示します。
Customer (accounts_customerテーブル)
name | type | notnull | pk |
---|---|---|---|
id | integer | 1 | 1 |
name | varchar(100) | 0 | 0 |
price | varchar(20) | 0 | 0 |
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
をもつ中間テーブルが作成されます。
リレーション、紐づいたデータ取得
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タグがついたデータを取得しています。