🗂
django-rest-frameworkのViewSet(APIView)で外部キー制約を設定していないカラムを結合する
はじめに
django-rest-frameworkのViewSet(get_querysetのオーバーライド)で外部キー制約を設定していないカラムを結合しようとした時に思ったより苦戦したのでその時のメモ。
サンプルコード
サンプルの方にはViewSet(APIView)を使ったサンプルも書いてあります(myapp/views.pyの下の方)
書いていること
- djangoのJoin, ForeignObjectを使った結合
- djangoのraw queryを使った結合(こちらの方法はViewSetでうまく使えなかった)
前提
- django-rest-frameworkを使用しています
- 以下の単純なテーブル構造を前提として書いています
テーブル構造
Recordテーブルのphone_numberにCustomerテーブルのcustomer_nameを結合
myapp/models.py
from django.db import models
class Record(models.Model):
phone_number = models.CharField(max_length=15)
class Customer(models.Model):
phone_number = models.CharField(max_length=15)
customer_name = models.CharField(max_length=100)
実装方法
Join, ForeignObjectを使用した方法
myapp/views.py
from django.db.models.sql.datastructures import Join
from django.db.models.fields.related import ForeignObject
from django.db.models.options import Options
from rest_framework.decorators import api_view
from rest_framework.request import Request
from rest_framework.response import Response
from myapp.models import Record, Customer
@api_view(["GET"])
def get_join(request: Request) -> Response:
# ForeignObjectのtoに結合したいテーブルを指定
fo = ForeignObject(
to=Customer,
on_delete=lambda: x,
from_fields=[None],
to_fields=[None],
rel=None,
related_name=None
)
# 結合処理
fo.opts = Options(Record._meta)
fo.opts.model = Record
fo.get_joining_columns = lambda: (("phone_number", "phone_number"), )
jo = Join(
table_name=Customer._meta.db_table,
parent_alias=Record._meta.db_table,
table_alias="T1",
join_type="LEFT JOIN",
join_field=fo,
nullable=False # 結合したカラムがNullの時にレコードを削除するか
)
# phone_numberが0から始まるレコードを抽出(objects.allだと何故か動かない)
q = Record.objects.filter(phone_number__startswith="0")
q.query.join(jo)
# extraで結合したテーブルのカラムを追加
q = q.extra(
select={
"customer_name": "myapp_customer.customer_name",
}
).values(
"id", "phone_number", "customer_name"
)
# レスポンス用のリストを作成
res = [
{
"id": r["id"],
"phone_number": r["phone_number"],
"customer_name": r["customer_name"],
}
for r in q
]
return Response(res)
raw queryを使用した方法
よく使う方法の1つですが、django-rest-frameworkのAPIViewで使う時にうまくいかなかった。
myapp/views.py
from rest_framework.decorators import api_view
from rest_framework.request import Request
from rest_framework.response import Response
from myapp.models import Record, Customer
@api_view(["GET"])
def get_raw_query(request: Request) -> Response:
rec = Record.objects.raw("""
SELECT
r.id AS id,
r.phone_number AS phone_number,
c.customer_name AS customer_name
FROM
myapp_record AS r
LEFT OUTER JOIN
myapp_customer AS C
ON r.phone_number = c.phone_number
WHERE
r.phone_number LIKE "0%"
""")
# レスポンス用のリストを作成
res = [
{
"id": r.id,
"phone_number": r.phone_number,
"customer_name": r.customer_name
}
for r in rec
]
return Response(res)
参考
Discussion