Django фильтр __lte __gte строк

Я хочу отфильтровать модели, заданные пользователем. Например, пользователь хочет отправиться в круиз продолжительностью 1-5 дней. В шаблоне у меня есть селектор с этими значениями (1-5, 6-9, 10-16, 17+). В представлении я создаю их как kwargs.

def create_kwargs(from_date, nights):
    kwargs = {}
    got_nights = nights != '-'
    got_date = False if not from_date else True

    if got_nights and (not got_date):
        nights_kwargs(kwargs, nights)
    if got_nights and got_date:
        nights_kwargs(kwargs, nights)
        kwargs['start_months__contains'] = from_date
    if (not got_nights) and got_date:
        kwargs['start_months__contains'] = from_date
    return kwargs


def nights_kwargs(kwargs, nights):
    if '-' in nights:
        c_min, c_max = nights.split('-')
        kwargs['cruise_duration__gte'] = c_min
        kwargs['cruise_duration__lte'] = c_max
    else:
        kwargs['cruise_duration__gte'] = '17'

Тогда я передаю эти kwargs в метод фильтра:

for i, area in enumerate(filter_areas):
    cruises = GeneralCruise.objects.filter(areas__contains=area, **kwargs)

Ранее я пытался проверить равенство и это сработало:

kwargs['cruise_duration'] = '1'

Моя проблема в том, что если я пишу __lte или __gte, то возвращаются все модели, даже если они не соответствуют критериям. Я читал другие вопросы по этому поводу и думаю, что это должно работать.

Это модель:

class GeneralCruise(models.Model):
    company = models.CharField(max_length=255, null=True)
    ship_name = models.CharField(max_length=255, null=True)
    ship_img = models.CharField(max_length=255, null=True)
    cruise_name = models.CharField(max_length=255, null=True)
    arrival = models.CharField(max_length=255, null=True)
    departure = models.CharField(max_length=255, null=True)
    cruise_duration = models.CharField(max_length=255, null=True)
    start_dates = models.JSONField(default=list)
    start_months = models.JSONField(default=list)
    cabins = models.JSONField(default=list)
    days_desc = models.JSONField(default=list)
    port_codes = models.JSONField(default=list)
    areas = models.JSONField(default=list)

Проблема заключается в том, что ваше поле cruise_duration является CharField, поэтому gte и lte ведут себя не так, как вы ожидаете (скажем, если бы это было IntegerField). Вы сравниваете строки, а не числовые значения.

Чтобы проиллюстрировать разницу:

>>> "9" > "10"
True
>>> 9 > 10
False

Вам нужно изменить тип столбца cruise_duration на тип поля, которое можно сравнивать на основе кардинального значения (например, IntegerField).

Соответственно, входные данные из ваших kwargs также должны быть санированы и преобразованы в целые числа, когда они будут предоставлены в качестве аргументов __lte/__gte для фильтрации модели по этому полю.

cruise_duration является CharField, то есть будет упорядочиваться не по значению, а лексикографически. Например, '10' < '9' лексикографически, поскольку первыми символами являются '1' и '9', а '1' является первым в алфавите.

Самое элегантное решение - сделать его числовым полем, как IntegerField:

class GeneralCruise(models.Model):
    # …,
    cruise_duration = models.IntegerField(null=True)
    # …

Это, вероятно, приведет к некоторым проблемам с миграциями, где, вероятно, наиболее эффективным способом избавиться от этого является удаление всех миграций, и, таким образом, начать заново со свежей базой данных.

Если это невозможно, то отбрасывание в IntegerField, вероятно, самый эффективный способ.

В этом случае набор запросов выглядит следующим образом:

from django.db.models.functions import Cast, IntegerField

GeneralCruise.objects.annotate(
    cruise_duration_int=Cast('cruise_duration', output_field=IntegerField())
)

и затем фильтровать с помощью cruise_duration_int__lte=… и cruise_duration_int__gte=…. Но это не является элегантным решением.

Также немного странно, что в вашей модели есть только CharFields и JSONFields. Обычно используют ForeignKeys к другим моделям, чтобы избежать дублирования данных, и получить базу данных в нормальной форме базы данных.

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