Django:QuerySetを使ってリレーション先のカラムにfilterし抽出する方法
Django:QuerySetを使ってリレーション先のカラムにfilterし抽出する方法
Laravel のEloquentではコレがなかなか面倒だったりするけど、DjangoのQuerySetこの処理がとても簡単です。
__でテーブル名をつなぐだけでカラムまでたどり着ける
具体的にはこんなコードで処理できます。
ベースのモデル.objects.filter(リレーション先のモデル1__さらにその先のモデル2__モデル2のカラム名__exact=抽出する値)
リレーション先の値で抽出する具体例
具体的に今作っているスクリプトを例にします。
# Models.py
from django.db import models
from accounts.models import CustomUser
class Skill(models.Model):
CATEGORY_CHOICES = (
(1, 'スクリプト'),
(2, 'データベース'),
(3, 'フレームワーク/ライブラリ'),
(4, '環境/OS'),
(5, 'その他'),
)
category = models.IntegerField(verbose_name='分類区分', choices=CATEGORY_CHOICES)
skillname = models.CharField(verbose_name="スキル名", max_length=100, blank=False)
created_at = models.DateTimeField(verbose_name='作成日時', auto_now_add=True)
updated_at = models.DateTimeField(verbose_name='更新日時', auto_now=True)
class Meta:
verbose_name_plural = 'Skill'
def __str__(self):
name = self.skillname
#return self.category + ' ' + self.skillname
return name
class Operation(models.Model):
EMPLOY_CHOICES = (
(1, '正社員'),
(2, 'フリーランス'),
(3, '派遣'),
(4, 'その他'),
)
WORKING_CHOICES = (
(1, '常駐'),
(2, '一部リモート'),
(3, 'フルリモート'),
)
user = models.ForeignKey(CustomUser, on_delete=models.CASCADE, verbose_name='ユーザーID')
title = models.CharField(verbose_name='案件名', max_length=100, blank=False)
employmentstatus = models.IntegerField(verbose_name='雇用形態', choices=EMPLOY_CHOICES)
wayofworking = models.IntegerField(verbose_name='活動形態', choices=WORKING_CHOICES)
ordering = models.CharField(verbose_name='依頼主', max_length=100, blank=True, null=True)
start_date = models.DateField(verbose_name='開始日', blank=True, null=True)
end_date = models.DateField(verbose_name='修了日', blank=True, null=True)
casecontent = models.TextField(verbose_name='依頼内容', blank=True, null=True, default=None)
handled = models.TextField(verbose_name='担当パート', blank=True, null=True)
numberofteams = models.IntegerField(verbose_name='チーム人数', default=None)
image = models.ImageField('画像1', upload_to='images/', blank=True, null=True, default=None)
creations = models.URLField(verbose_name='参考URL', blank=True, null=True, default=None)
created_at = models.DateTimeField(verbose_name='作成日時', auto_now_add=True)
updated_at = models.DateTimeField(verbose_name='更新日時', auto_now=True)
class Meta:
verbose_name_plural = 'Operation'
def __str__(self):
return self.title
class UseSkill(models.Model):
operation = models.ForeignKey(Operation, on_delete=models.CASCADE, verbose_name='案件ID')
skill = models.ForeignKey(Skill, on_delete=models.CASCADE, verbose_name='スキルID')
created_at = models.DateTimeField(verbose_name='作成日時', auto_now_add=True)
updated_at = models.DateTimeField(verbose_name='更新日時', auto_now=True)
class Meta:
verbose_name_plural = 'Useskill'
def __str__(self):
return self.Skill.skillname + ' '+ self.Operation.title
これは今私が作成しているポートフォリオPDFの生成システムのテーブルです。
案件=operationに対して、どんなスキルを使ったかをUseSkillというテーブルに記載していきます。
スキルの活用期間を算出したい場合どうすればいいか
UseSkillをスキルごとに抽出しスキルを利用した期間をoperationから計算しようとした場合、次のようなルートが考えられます。
- Skillをall()で全件取得、これをForループしUseSkillからOperation(案件)を抽出。さらに案件を回して自分の係わった案件の期間を計算し合計
- Operationから自分の係わった案件を抽出。これをForループしUseSkillを抽出。[スキル名,期間]のリストを作成しGroup_byで結合とSum
- UseSkillからリレーション先のOperationのuserに抽出条件をかけGroup_byし使用スキル名Listを作成。これをForループしOperationを抽出(enddate-startdate)の計算結果をSUMで集計
- UseSkillからリレーション先のOperationのuserに抽出条件をかけGroup_byし使用スキル名Listを作成。これをwhere in でOperationを抽出。pandasに入れてゴニョゴニョ
- UseSkillからリレーション先のOperationのuserに抽出条件をかけ抽出。結果をpandasに入れてゴニョゴニョ
泥臭く何回も『抽出=>Forループ』をかければ欲しい情報までは持っていけます。
でも勉強用なので3~5のどれかで行きたいです。
でこの時共通の課題となるのが【 リレーション先のテーブルのカラムに抽出条件をかける 】という操作です。
リレーション先の値で抽出するDjangoのスクリプト
us = Useskill.objects.filter(operation__customer__id__exact=ユーザーID)
1行で完了、しかもわかりやすい!Djangoスゲー。
これをLaravelのEloquentで書くとこんな感じ
us = UseSkill::whereHas('operation',function($seach)use('ユーザーID'){
$search->where('user',ユーザーID)
}->get();
functionが出てきたりなどなど慣れれば問題ないんですけど、敷居は高く見えますよね。
まとめ
QuerySetの一発で一気に日付計算までできないかなと試行錯誤しましたが、私にはまだ難易度高いようで挫折しました。
us = Useskill.objects.filter(operation__customer__id__exact=u_id)\
.extra(
select={'val':"select startdate - enddate from managedcontents_operation where id=%s"},
select_params=(['operation'])
)
こんなで行けるかと思ったのですが、パラメーターに'operation'という文字列が入ってしまい失敗。
素直にPandasに格納しようと思いました。


