Как фильтровать по диапазону ИЛИ по значению "null"? (Т.е. объединить NumberFilter диапазона и BooleanFilter для null=True IntegerField)

У меня есть Item модель с числовым number полем. Это поле number по умолчанию имеет значение null.

# models.py
class Item(models.Model):
  number = models.IntegerField(default=None, blank=True, null=True)

Я хочу настроить фильтры, которые могут возвращать набор запросов Items, где number находится в диапазоне - что достаточно просто:

# filters.py
class ItemFilter(django_filters.FilterSet):
  min_num = django_filters.NumberFilter(method="min_num_filter")
  max_num = django_filters.NumberFilter(method="max_num_filter")

  class Meta:
    model = Item
    fields = ("min_num", "max_num", "incl_null")

  def min_num_filter(self, queryset, name, value):
    return queryset.filter(number__gte=value)

  def max_num_filter(self, queryset, name, value):
    return queryset.filter(number__lte=value)

Но что если я хочу иметь дополнительный булевский фильтр, который может включать Items, который имеет null для number вместе с тем, что Items соответствует диапазону min_num и max_num?

Например, запрос URL в форме ?min_num=1&max_num=10&incl_null=True должен вернуть все Items, где number находится между 1 и 10 ИЛИ number равен None.

Следующий код не работает:

class ItemFilter(django_filters.FilterSet):
  ...
  incl_null = django_filters.BooleanFilter(method="incl_null_filter")

  class Meta:
    model = Item
    fields = ("min_num", "max_num", "incl_null")

  // doesn't work
  class incl_null_filter(self, queryset, name, value):
    if value is True:
      return queryset | Item.objects.filter(number=None)
    if value is False:
      return queryset

Edit: Я пробовал методы в документации "Filtering by empty values", но я думаю, что это исключительно для значений null - где я ищу совпадение диапазона OR a null value.

Ну, единственное решение, которое я могу придумать, это передать min range, max range и is_null boolean в одно поле char, а затем преобразовать его в 3 отдельных фильтра для выполнения действий.

Таким образом, URL запроса будет выглядеть как ?master_num=1-10-1 для диапазона 1 - 10 вкл. None и ?master_num=1-10-0 для диапазона 1 - 10 вкл. None.

class ItemFilter(django_filters.FilterSet):
  master_num = django_filters.CharFilter(method="master_num_filter")

  class Meta:
    model = Item
    fields = ("master_num")

  def master_num_filter(self, queryset, name, value):
    # array = [min, max, 1 or 0 for True and False]
    array = value.split("-")
    min = Q(year_published__gte=int(array[0]))
    max = Q(year_published__lte=int(array[1]))
    if array[2] == "1":
        incl_null = Q(year_published=None)
        return queryset.filter((min & max) | incl_null)
    else:
        return queryset.filter(min & max)

Хотелось бы узнать, есть ли лучший способ сделать это.

Попробуйте этот запрос:


from django.db.models import Q

min_ = 0
max_ = 10

Item.objects.filter(Q(number__gte=min_, number__lte=max_) | Q(number__isnull=True))

Вернуться на верх