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から計算しようとした場合、次のようなルートが考えられます。

  1. Skillをall()で全件取得、これをForループしUseSkillからOperation(案件)を抽出。さらに案件を回して自分の係わった案件の期間を計算し合計
  2. Operationから自分の係わった案件を抽出。これをForループしUseSkillを抽出。[スキル名,期間]のリストを作成しGroup_byで結合とSum
  3. UseSkillからリレーション先のOperationのuserに抽出条件をかけGroup_byし使用スキル名Listを作成。これをForループしOperationを抽出(enddate-startdate)の計算結果をSUMで集計
  4. UseSkillからリレーション先のOperationのuserに抽出条件をかけGroup_byし使用スキル名Listを作成。これをwhere in でOperationを抽出。pandasに入れてゴニョゴニョ
  5. 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に格納しようと思いました。

\ 最新情報をチェック /

コメントを残す

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください