Djangoでクエリのfilterを動的に連結する方法

Djangoでクエリのfilterを動的に連結する方法をご紹介します。

条件

  • Django 2.1.7
  • Python 3.7.0

前提

以下のようなModelが存在するとします。

models.py

from django.db import models
from django.urls import reverse
from django.contrib.auth.models import AbstractUser


class CustomUser(AbstractUser):

    def __str__(self):
        return self.username + ":" + self.email


class Post(models.Model):
    """投稿モデル"""
    class Meta:
        db_table = 'post'

    title = models.CharField(verbose_name='タイトル', max_length=255)
    text = models.CharField(verbose_name='内容', max_length=255, default='', blank=True)
    author = models.ForeignKey(
        'sample.CustomUser',
        on_delete=models.CASCADE,
    )
    created_at = models.DateTimeField(verbose_name='登録日時', auto_now_add=True)
    updated_at = models.DateTimeField(verbose_name='更新日時', auto_now=True)

    def __str__(self):
        return self.title + ',' + self.text

    @staticmethod
    def get_absolute_url(self):
        return reverse('sample:index')

filterの連結方法

以下のようにします。

from django.db.models import Q

condition_1 = Q()
condition_2 = Q()

Post.objects.select_related().filter(condition_1 & condition_2)

実装例

DBレコード

以下のような3つのレコードが存在すると仮定します。

views.py 例1

import logging
from django.contrib.auth.mixins import LoginRequiredMixin
from django.views import generic
from .models import Post
from django.db.models import Q

logger = logging.getLogger('development')


class IndexView(LoginRequiredMixin, generic.ListView):

    template_name = 'sample/index.html'
    model = Post

    def get_queryset(self):

        # 検索条件
        condition_user = Q()
        condition_title = Q()
        title = ""

        current_user = self.request.user # 現在のユーザを取得
        if current_user.is_superuser:  # スーパーユーザの場合
            condition_user = Q(author=current_user.id)
        if title: # タイトルが指定されている場合
            condition_title = Q(title__icontains=title)

        return Post.objects.select_related().filter(condition_user & condition_title)

以下のようなSQLが発行されます。

SELECT “post”.”id”, “post”.”title”, “post”.”text”, “post”.”author_id”, “post”.”created_at”, “post”.”updated_at”, “sample_customuser”.”id”, “sample_customuser”.”password”, “sample_customuser”.”last_login”, “sample_customuser”.”is_superuser”, “sample_customuser”.”username”, “sample_customuser”.”first_name”, “sample_customuser”.”last_name”, “sample_customuser”.”email”, “sample_customuser”.”is_staff”, “sample_customuser”.”is_active”, “sample_customuser”.”date_joined” FROM “post” INNER JOIN “sample_customuser” ON (“post”.”author_id” = “sample_customuser”.”id”) WHERE “post”.”author_id” = 1; args=(1,)

リスト画面を表示させると以下のようになります。

views.py 例2

例1との違いは、title = “test2″で絞り込んでいることです。

import logging
from django.contrib.auth.mixins import LoginRequiredMixin
from django.views import generic
from .models import Post
from django.db.models import Q

logger = logging.getLogger('development')


class IndexView(LoginRequiredMixin, generic.ListView):

    template_name = 'sample/index.html'
    model = Post

    def get_queryset(self):

        # 検索条件
        condition_user = Q()
        condition_title = Q()
        title = "test2"

        current_user = self.request.user # 現在のユーザを取得
        if current_user.is_superuser:  # スーパーユーザの場合
            condition_user = Q(author=current_user.id)
        if title: # タイトルが指定されている場合
            condition_title = Q(title__icontains=title)

        return Post.objects.select_related().filter(condition_user & condition_title)

以下のようなSQLが発行されます。

SELECT “post”.”id”, “post”.”title”, “post”.”text”, “post”.”author_id”, “post”.”created_at”, “post”.”updated_at”, “sample_customuser”.”id”, “sample_customuser”.”password”, “sample_customuser”.”last_login”, “sample_customuser”.”is_superuser”, “sample_customuser”.”username”, “sample_customuser”.”first_name”, “sample_customuser”.”last_name”, “sample_customuser”.”email”, “sample_customuser”.”is_staff”, “sample_customuser”.”is_active”, “sample_customuser”.”date_joined” FROM “post” INNER JOIN “sample_customuser” ON (“post”.”author_id” = “sample_customuser”.”id”) WHERE (“post”.”author_id” = 1 AND “post”.”title” LIKE ‘%test2%’ ESCAPE ‘\’); args=(1, ‘%test2%’)

WHERE句に「AND “post”.”title” LIKE ‘%test2%’ ESCAPE ‘\’」が追加されていることがわかります。

リスト画面を表示させると以下のようになります。

参考

stackoverflow

https://stackoverflow.com/questions/769843/how-do-i-use-and-in-a-django-filter

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です