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

