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=…
. Но это не является элегантным решением.
Также немного странно, что в вашей модели есть только CharField
s и JSONField
s. Обычно используют ForeignKey
s к другим моделям, чтобы избежать дублирования данных, и получить базу данных в нормальной форме базы данных.