Django filter __lte __gte strings

I would like to filter models given a user input. For example the user would like to go on a cruise that is 1-5 days long. In the template I have a selector with these values (1-5, 6-9, 10-16, 17+).
In the view I create these as 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
        kwargs['cruise_duration__gte'] = '17'

Than I feed these kwargs to the filter method:

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

Previously I tried to check equality and it worked:

kwargs['cruise_duration'] = '1'

My problem is that if I write __lte or __gte it returns all the models even if they are not matching the criteria.
I read other questions regarding this and I think this should be working.

This is the model:

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)

>Solution :

cruise_duration is a CharField, that means that it will not order by value, but lexicographically. For example '10' < '9' lexicographically, since the first characters are '1' and '9', and '1' is first in the alphabet.

The most elegant solution is to make it a numerical field, like an IntegerField:

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

This will likely result in some trouble with migrations where probably the most effective way to get rid of it is to remove all the migrations, and thus start over with a fresh database.

If that is not an option, casting it to an IntegerField is probably the most effective way.

In that case the queryset looks like:

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

    cruise_duration_int=Cast('cruise_duration', output_field=IntegerField())

and then filter with cruise_duration_int__lte=… and cruise_duration_int__gte=…. But this is not an elegant solution.

It is also a bit odd that your model only has CharFields and JSONFields. Usually one uses ForeignKeys to other models to avoid data duplication, and get the database in a database normal form.

Leave a Reply