9

Nearly every kind of lookup in Django has a case-insensitive version, EXCEPT in, it appears.

This is a problem because sometimes I need to do a lookup where I am certain the case will be incorrect.

Products.objects.filter(code__in=[user_entered_data_as_list])

Is there anything I can do to deal with this? Have people come up with a hack to work around this issue?

Jordan Reiter
  • 20,467
  • 11
  • 95
  • 161

8 Answers8

9

I worked around this by making the MySQL database itself case-insensitive. I doubt that the people at Django are interested in adding this as a feature or in providing docs on how to provide your own field lookup (assuming that is even possible without providing code for each db backend)

Here is one way to do it, admittedly it is clunky.

products = Product.objects.filter(**normal_filters_here)
results = Product.objects.none()
for d in user_entered_data_as_list:
    results |= products.filter(code__iexact=d)
Jordan Reiter
  • 20,467
  • 11
  • 95
  • 161
2

If your database is MySQL, Django treats IN queries case insensitively. Though I am not sure about others

Edit 1:

model_name.objects.filter(location__city__name__in': ['Tokio','Paris',])

will give following result in which city name is

Tokio or TOKIO or tokio or Paris or PARIS or paris

Point Networks
  • 1,071
  • 1
  • 13
  • 35
1

Here is a solution that do not require case-prepared DB values. Also it makes a filtering on DB-engine side, meaning much more performance than iterating over objects.all().

def case_insensitive_in_filter(fieldname, iterable):
    """returns Q(fieldname__in=iterable) but case insensitive"""
    q_list = map(lambda n: Q(**{fieldname+'__iexact': n}), iterable)
    return reduce(lambda a, b: a | b, q_list)

The other efficient solution is to use extra with quite portable raw-SQL lower() function:

MyModel.objects.extra(
    select={'lower_' + fieldname: 'lower(' + fieldname + ')'}
).filter('lover_' + fieldname + '__in'=[x.lower() for x in iterable])
Ivan Klass
  • 6,407
  • 3
  • 30
  • 28
1

If it won't create conflicts, a possible workaround may be transforming the strings to upper or lowercase both when the object is saved and in the filter.

nilved
  • 63
  • 5
1

Another solution - albeit crude - is to include the different cases of the original strings in the list argument to the 'in' filter. For example: instead of ['a', 'b', 'c'], use ['a', 'b', 'c', 'A', 'B', 'C'] instead.

Here's a function that builds such a list from a list of strings:

def build_list_for_case_insensitive_query(the_strings):
    results = list()
    for the_string in the_strings:
        results.append(the_string)
        if the_string.upper() not in results:
            results.append(the_string.upper())
        if the_string.lower() not in results:
            results.append(the_string.lower())
    return results
1

A lookup using Q object can be built to hit the database only once:

from django.db.models import Q

user_inputed_codes = ['eN', 'De', 'FR']

lookup = Q()
for code in user_inputed_codes:
    lookup |= Q(code__iexact=code)
filtered_products = Products.objects.filter(lookup)
niekas
  • 8,187
  • 7
  • 40
  • 58
0

A litle more elegant way would be this:

[x for x in Products.objects.all() if x.code.upper() in [y.upper() for y in user_entered_data_as_list]]
  • 1
    I don't see how this is more elegant. Products.objects.all() might return *thousands* of rows, and then you're going to filter it outside of the database and then return it as a list, not even as a query, which means you can't sort it (easily) or perform additional filters on it. Just realized you're also looping through `user_entered_data_as_list` *each time* so YEAH everyone, do **not** use this technique unless your table is very very small. – Jordan Reiter Sep 24 '12 at 15:14
  • Just did a test, your method takes around 200 times longer for a table with 30k+ records: http://pastebin.com/R78P0Pdq – Jordan Reiter Sep 24 '12 at 17:43
  • It's not effective, though you can try use generator instead of simple list. – sepulchered Oct 12 '12 at 10:45
0

You can do it annotating the lowered code and also lowering the entered data

from django.db.models.functions import Lower

Products.objects.annotate(lower_code=Lower('code')).filter(lower_code__in=[user_entered_data_as_list_lowered])
Hispar
  • 1
  • 1