Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Django, having Q object filter by function in model

Inside my Profile model I have the following function:

It serves to return the fullname of the user (or a alternative if some data is missing).

    def full_name(self):
      first_name = self.user.first_name.strip()
      if first_name and self.user.last_name:
          if not self.middle_name:
              full_name = u'%s %s' % (first_name, self.user.last_name)
          else:
              full_name = u'%s %s %s' % (first_name, self.middle_name,
                                       self.user.last_name)
          return full_name.strip()
      elif first_name:
          return first_name
      return self.user.username

Now for my question: I have a view where I filter a queryset based on the variable 'q' (that is returned from a searchbox in the template) Like so:

qs = qs.filter(tenant_demo_purchase=True).order_by('-id')
    #Search users within results
    q = self.request.GET.get('q', None)
    if q:
        qs = qs.filter(Q(user__username__icontains=q) |
                       Q(user__first_name__icontains=q) |
                       Q(user__last_name__icontains=q) |
                       Q(arrangement_period__arrangement__title__icontains=q)  | 
                       ).filter(tenant_demo_purchase=True).order_by('-id')
    else:
        pass
    return qs

This filter works fine if I give it a firstname, username, or a last name. But if I give it a firstname + lastname (example: "John Doe") it returns no results.

Most of my users tend to submit the fullname, so as a solution I tried to have it acces the Profile.full_name function. By adding the following line

Q(user__profile__fullname__icontains=q) 

However, this crashes with the following error message:

raise FieldError('Related Field got invalid lookup: {}'.format(lookups[0]))
FieldError: Related Field got invalid lookup: fullname

Interistengly, when I look at the django error page it seems to ignore the usernames and fail on the 'arrangement__title' query, ignoring the rest:

 Q(arrangement_period__arrangement__title__icontains=q)

I tried doing the obvious:

Q(user__profile.fullname__icontains=q) 

But that just throws the following error

SyntaxError: keyword can't be an expression

I just want a way for users to input a full name (firstname + space + lastname) and still have the program return the correct results.

Does anyone know of a way to do this?

Solution

Thanks to Ivan his answer, the following codes provides the desired result:

q = self.request.GET.get('q', None)
    if q:
        qs = qs.annotate(
            full_name=Concat(
                'user__first_name',
                Value(' '),
                'user__last_name',
                output_field=CharField()
            )
        ).filter(Q(full_name__icontains=q) |
                 Q(user__username__icontains=q) |
                 Q(user__first_name__icontains=q) |
                 Q(user__last_name__icontains=q) |
                 Q(arrangement_period__arrangement__title__icontains=q)
                 ).order_by('-id')
like image 875
Jasper Avatar asked Oct 11 '17 12:10

Jasper


People also ask

What does Q mean in Django?

Q object encapsulates a SQL expression in a Python object that can be used in database-related operations. Using Q objects we can make complex queries with less and simple code. For example, this Q object filters whether the question starts wiht 'what': from django.

What is the purpose of filter () method in Django?

The filter() method is used to filter you search, and allows you to return only the rows that matches the search term.

What is F in Django ORM?

In the Django QuerySet API, F() expressions are used to refer to model field values directly in the database.


1 Answers

Try using database function concat:

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


qs = qs.annotate(
    full_name=Concat(
        'user__first_name',
        Value(' '),
        'user__last_name',
        output_field=CharField()
    )
).filter(full_name__icontains=q)
like image 59
Ivan Avatar answered Sep 30 '22 17:09

Ivan