[Fixed]-Django query based on dynamic property()

10👍

filter() operates on the database level (it actually writes SQL), so it won’t be possible to use it for any queries based on your python code (dynamic property in your question).

This is an answer put together from many other answers in this department : )

13👍

The accepted answer is not entirely true.

For many cases, you can override get() in the model manager to pop dynamic properties from the keyword arguments, then add the actual attributes you want to query against into the kwargs keyword arguments dictionary. Be sure to return a super so any regular get() calls return the expected result.

I’m only pasting my own solution, but for the __startswith and other conditional queries you could add some logic to split the double-underscore and handle appropriately.

Here was my work-around to allow querying by a dynamic property:

class BorrowerManager(models.Manager):
    def get(self, *args, **kwargs):
        full_name = kwargs.pop('full_name', None)
        # Override #1) Query by dynamic property 'full_name'
        if full_name:
            names = full_name_to_dict(full_name)
            kwargs = dict(kwargs.items() + names.items())
        return super(BorrowerManager, self).get(*args, **kwargs)

In models.py:

class Borrower(models.Model):
    objects = BorrowerManager()

    first_name = models.CharField(null=False, max_length=30)
    middle_name = models.CharField(null=True, max_length=30)
    last_name = models.CharField(null=False, max_length=30)
    created = models.DateField(auto_now_add=True)

In utils.py (for the sake of context):

def full_name_to_dict(full_name):
    ret = dict()
    values = full_name.split(' ')
    if len(values) == 1:
        raise NotImplementedError("Not enough names to unpack from full_name")
    elif len(values) == 2:
        ret['first_name'] = values[0]
        ret['middle_name'] = None
        ret['last_name'] = values[1]
        return ret
    elif len(values) >= 3:
        ret['first_name'] = values[0]
        ret['middle_name'] = values[1:len(values)-1]
        ret['last_name'] = values[len(values)-1]
        return ret
    raise NotImplementedError("Error unpacking full_name to first, middle, last names")

4👍

I had a similar problem and was looking for solution. Taking for granted that a search engine would be the best option (e.g. django-haystack with Elasticsearch), that’s how I would implement some code for your needs using only the Django ORM (you can replace icontains with istartswith):

from django.db.models import Value
from django.db.models.functions import Concat

queryset = User.objects.annotate(full_name=Concat('first_name', Value(' '), 'last_name')
return queryset.filter(full_name__icontains=value)

In my case I didn’t know whether the user would insert ‘first_name last_name‘ or viceversa, so I used the following code.

from django.db.models import Q, Value
from django.db.models.functions import Concat

queryset = User.objects.annotate(first_last=Concat('first_name', Value(' '), 'last_name'), last_first=Concat('last_name', Value(' '), 'first_name'))
return queryset.filter(Q(first_last__icontains=value) | Q(last_first__icontains=value))

With Django <1.8, you would probably need to resort to extra with the SQL CONCAT function, something like the following:

queryset.extra(where=['UPPER(CONCAT("auth_user"."last_name", \' \', "auth_user"."first_name")) LIKE UPPER(%s) OR UPPER(CONCAT("auth_user"."first_name", \' \', "auth_user"."last_name")) LIKE UPPER(%s)'], params=['%'+value+'%', '%'+value+'%'])

0👍

Think it’s not possible in django to filter on properties that does not present as a database filed, but what you can do to make cool autocomplete search is something like this:

if ' ' in query:
    query = query.split()
    search_results = list(chain(User.objects.filter(first_name__icontains=query[0],last_name__icontains=query[1]),
                                    User.objects.filter(first_name__icontains=query[1],last_name__icontains=query[0])))
else:
    search_results =  User.objects.filter(Q(first_name__icontains=query)| Q(last_name__icontains=query))

This code gives the user of your system a flexibility to start typing either first name or last name and the user will be thankful to you for allowing this.

Leave a comment