django queryset - searching for firstname and lastname - django

i have a django app that retrieve all subjects from a single table of users. i've also implemented an input search form,
this is the query performed:
all_soggs = Entity.objects.filter(lastname__istartswith=request.GET['query_term']).order_by('lastname')
if(all_soggs.count()==0):
all_soggs = Entity.objects.filter(firstname__istartswith=request.GET['query_term']).order_by('firstname')
as you can see the query first search for matching items by lastname, and then by firstname. this works until i insert the complete name 'firstaname lastname' or 'lastname firstname', in this case there's no results. how can i modify the query to make a better search?
thanks - luke

Copy/paste from: https://stackoverflow.com/a/17361729/1297812
from django.db.models import Q
def find_user_by_name(query_name):
qs = User.objects.all()
for term in query_name.split():
qs = qs.filter( Q(first_name__icontains = term) | Q(last_name__icontains = term))
return qs

You need Q objects and you also need to split your query into separate terms (since no first name will match the full string "Firstname Lastname").
Here's an idea to match any first or last name starting with either "Firstname" or "Lastname" in the search "Firstname Lastname".
This is a generic search - adjust the query to suit your specific needs!
Edit: oops, I really don't like using reduce since it looks confusing, but these need to be ORed together and we can't do a more verbose version because the number of terms is unknown.
import operator
from django.db.models import Q
search_args = []
for term in request.GET['query_term'].split():
for query in ('first_name__istartswith', 'last_name__istartswith'):
search_args.append(Q(**{query: term}))
all_soggs = Entity.objects.filter(reduce(operator.or_, search_args))
To clarify how to use Q objects, given the search "Firstname Lastname" the previous query is equal to:
Entity.objects.filter(
Q(first_name__istartswith="Firstname") | Q(last_name__istartswith="Firstname") |
Q(first_name__istartswith="Lastname") | Q(last_name__istartswith="Lastname")
)

This is a fairly old question but I just ran into the same problem and I thought I would share a more elegant solution.
from django.db.models import Value as V
from django.db.models.functions import Concat
from ..models import User
def find_user_by_name(search_str) -> QuerySey[User]:
q = User.objects.annotate(full_name=Concat('first_name', V(' '), 'last_name'))
q = q.filter(full_name__icontains=search_str)
return q
This is the only solution that I have found which lets gives the behaviour I wanted, IE seaching with a full name string with a space ("John Doe") and with a partial string ("John Do").

Similar question:Querying full name in Django
query = request.GET.get('query')
entities = []
try:
firstname = query.split(' ')[0]
lastname = query.split(' ')[1]
entities += Entity.objects.filter(firstname__icontains=firstname,lastname__icontains=lastname)
entities += Entity.objects.filter(firstname__icontains=lastname,lastname__icontains=firstname)
entities = set(entities)

Related

Django postgres HStoreField order by

Can I order the results of QuerySet by values inside of HStoreField, for example I've got model:
class Product(model.Models):
name = CharField(max_length=100)
properties = HStoreField()
And I want to store some properties of my product in HStoreField like:
{ 'discount': '10', 'color': 'white'}
In view I want to order the resulting QuerySet by discount.
The above answer does not work. Order transforms were never implemented for HStoreField, see https://code.djangoproject.com/ticket/24747.
But the suggestion in https://code.djangoproject.com/ticket/24592 works. Here is some more detail.
from django.contrib.gis.db.models import TextField, HStoreField, Model
from django.db.models import F, Func, Value
class MyThing(Model):
name: TextField()
keys: HStoreField()
things = [MyThing(name='foo'),
MyThing(name='bar'),
MyThing(name='baz')]
things[0].keys['movie'] = 'Jaws'
things[1].keys['movie'] = 'Psycho'
things[2].keys['movie'] = 'The Birds'
things[0].keys['rating'] = 5
things[1].keys['rating'] = 4
things[2].keys['year'] = '1963'
# Informal search
MyThing.objects\
.filter(keys__has_key='rating')\
.order_by(Func(F('keys'), Value('movie'),
function='',
arg_joiner=' -> ',
output_field=TextField()))
The formal search is exactly as described in the second link above. Use the imports in the above snippet with that code.
This might work (I cannot test right now):
.order_by("properties -> 'discount'")
But be aware that HSTORE values are strings, so you do not get numeric order but instead items are ordered as strings.
Do get proper numeric order, you should extract the properties->discount key as separate column and cast it to integer.

keyword search over several fields in django

I am using the standard User model of django.
I would like to search users by first name and last name. So I have a free text search and using icontains I am searching for the that text.
However, I would like that a user can search for "John A" and retrieve all users which their First name + Last name contains "John A" however, because I am using icontains over first name and last name separately , it doesn't work. "John" alone will work. but as long as I have involved both fields, it doesn't (which make sense)
Any simple solution for this ?
Use Q objects to create your queries, and use | for OR and & for AND.
>>> from django.db.models import Q
>>> qs = Q(first_name__icontains='John A') | Q(last_name__icontains='John A')
>>> User.objects.filter(qs)
read more about it here
p.s. You can also use | and & to join regular querysets
User.objects.filter(...) | User.objects.filter(...)
but I think using Q objects is just neater when going over multiple fields
edit
as par your comment, all you want to do is this:
>>> query = 'John A'
>>> fn, ln = query.split()
>>> User.objects.filter(first_name=fn, last_name=ln)
For complex situations you probably want check the first name and last name. The second one usually is a middle name or part of the others, so icontain will catch it. So something like this:
>>> query = 'Jon Van Mendrik'
>>> fn, ln = [i[0], i[-1] for i in query.split()] #take the first and last
>>> print fn, ln
"Jon Mendrik"
>>> qs = Q(first_name__icontains=fn, last_name__icontains=ln)
>>> qs = qs | Q(first_name__icontains=ln, last_name__icontains=fn)
>>> User.objects.filter(qs).distinct()
It's not very pretty, but will probably work for most cases, including where the last name was entered first and so on. Just make sure you understand what I did here and not just copy it blindly to your project -
I took the first and last parts of the string and assigned them to fn and ln respectively
I create a queryset for the firtcase
I then joined it to another queryset for the reverse option using |
lastly, I filtered on that queryset and used distinct to get rid of possible duplicates
You can probably add a lot more logic. For example, 'John A' - the last name is obviously an acronym and not the entire name while the first is a full name. You can implement some logic that takes care of that and build the queryset accordingly (contains is more costly then startswith or then simple comparison). Using Q objects will make that easy. Play around with it, you'll get the hang of it
I suggest you use natural_key. In your User model, add something like:
def natural_key(self):
return (self.first_name, self.last_name)
result:
users = User.objects.filter()
for user in users:
print(user.natural_key)
You can then filter on natural keys.
Hope it helps.

django dynamically filtering with q objects

I'm trying to query a database based on user input tags. The number of tags can be from 0-5, so I need to create the query dynamically.
So I have a tag list, tag_list, and I want to query the database:
design_list = Design.objects.filter(Q(tags__tag__contains = "tag1") and Q(tags__tag__contains = "tag2") and etc. etc. )
How can I create this feature?
You'll want to loop through the tag_list and apply a filter for each one.
tag_list = ['tag1', 'tag2', 'tag3']
base_qs = Design.objects.all()
for t in tag_list:
base_qs = base_qs.filter(tags__tag__contains=t)
This will give you results matching all tags, as your example indicated with and. If in fact you needed or instead, you will probably need Q objects.
Edit: I think I have what you're looking for now.
tags = ['tag1', 'tag2', 'tag3']
q_objects = Q() # Create an empty Q object to start with
for t in tags:
q_objects |= Q(tags__tag__contains=t) # 'or' the Q objects together
designs = Design.objects.filter(q_objects)
I tested this and it seems to work really well.
Edit 2: Credit to kezabelle in #django on Freenode for the initial idea.
You can use this way:
my_dict = {'field_1': 1, 'field_2': 2, 'field_3': 3, ...} # Your dict with fields
or_condition = Q()
for key, value in my_dict.items():
or_condition.add(Q(**{key: value}), Q.OR)
query_set = MyModel.objects.filter(or_condition)
By this way you can use dynamically generated field names.
Also you can use Q.AND for AND condition.
Just prepare a tag list first then, query like this:
tags = ['tag1', 'tag2',...]
design_list = Design.objects.filter(tags__tag__contains__in = tags)
You may need to add AND and OR conditions
query = (Q(fild1='ENABLE'))
# Filter by list
query.add(Q(fild2__in=[p.code for p in Objects.field.all()]), Q.AND)
# filter OR
q_objects = Q(field3='9999999')
for x in myList:
q_objects.add(Q(field3=x.code), Q.OR)
query.add(q_objects, Q.AND)
Use reduce :
from functools import reduce
design_list = Design.objects.filter(reduce(lambda q1,q2: q1 & q2,
[Q(tags__tag__contains=t)
for t in tag_list]))

How to express "or" in a dictionary queryset in django

I'm filtering a big number of users based on attributes. For example, they can be filtered by minimum gpa.
So far I've been doing the following to construct my queryset-
('gpa_gt' just means that the gpa property has to be greater than whatever is the query param)
if len(request.GET) != 0:
kwargs = {}
if request.GET['mingpa']:
kwargs['gpa__gt'] = request.GET['mingpa']
profiles = BlahUser.objects.filter(**kwargs)
Now, the users have a first school major attribute and a second school major attribute. If I filter by a major, I want the user to show up in the results if his first major OR his second major match any of my selections (I use a multiple select on the filtering). What I have now only works for one major-
if request.GET.has_key('major'):
kwargs['major__in'] = request.GET.getlist('major')
I know how to explicitly write out the query, but in my case I'm building it dynamically of sorts, and am stumped at how to do so. Any help appreciated, thanks!
You inclusively or in Django querysets with the Q object:
Poll.objects.get(
Q(pub_date=date(2005, 5, 2)) | Q(pub_date=date(2005, 5, 6)),
question__startswith='Who')
You can operate on them like variables so you would like something like this:
query = Q( gpa__gt=request.GET['mingpa'] )
if request.GET.has_key('major'):
query = query | Q( major__in=request.GET.getlist('major') )
profiles = BlahUser.objects.get( query )
Borgar is definitely on the right track, but I think this is a little closer to what you are
looking for:
# filter for minimum GPA
gpa_filter = Q( gpa__gt=request.GET['mingpa'] )
major_filter = Q()
if request.GET.has_key('major'):
majors = request.GET.getlist('major')
# filter for students with first OR second major in specified majors
major_filter = Q( first_major__in=majors| Q(second_major__in=majors)
# run filter (these are AND'ed together), so we get students that:
# 1. Have a first or second major in the specified list of majors AND
# 2. Have a GPA above the specified minimum
profiles = BlahUser.objects.get( gpa_filter, major_filter )
Note that you can continue to use profiles as a regular queryset (as in this contrived example):
seniors_matching_request_params = profiles.filter(status='senior')
So, just use Q objects where you need to, then continue to operate on the queryset as normal .
I'm thinking of this so far- (course being major)
if request.GET.has_key('course'):
extra = ['Q(first_course IN ' + request.GET.getlist('course') + ') | Q(second_course IN ' + request.GET.getlist('course') + ')']
profiles = sumaConnectUser.objects.extra(where=[extra])
'course' is what we call a major. I haven't checked whether the above works but it's a start..
Help? lol

Django query case-insensitive list match

I have a list of names that I want to match case insensitive, is there a way to do it without using a loop like below?
a = ['name1', 'name2', 'name3']
result = any([Name.objects.filter(name__iexact=name) for name in a])
Unfortunatley, there are no __iin field lookup. But there is a iregex that might be useful, like so:
result = Name.objects.filter(name__iregex=r'(name1|name2|name3)')
or even:
a = ['name1', 'name2', 'name3']
result = Name.objects.filter(name__iregex=r'(' + '|'.join(a) + ')')
Note that if a can contain characters that are special in a regex, you need to escape them properly.
NEWS: In Django 1.7+ it is possible to create your own lookups, so you can actually use filter(name__iin=['name1', 'name2', 'name3']) after proper initialization. See documentation reference for details.
In Postgresql you could try creating a case insensitive index as described here:
https://stackoverflow.com/a/4124225/110274
Then run a query:
from django.db.models import Q
name_filter = Q()
for name in names:
name_filter |= Q(name__iexact=name)
result = Name.objects.filter(name_filter)
Index search will run faster than the regex matching query.
Another way to this using django query functions and annotation
from django.db.models.functions import Lower
Record.objects.annotate(name_lower=Lower('name')).filter(name_lower__in=['two', 'one']
Adding onto what Rasmuj said, escape any user-input like so
import re
result = Name.objects.filter(name__iregex=r'(' + '|'.join([re.escape(n) for n in a]) + ')')
Keep in mind that at least in MySQL you have to set utf8_bin collation in your tables to actually make them case sensitive. Otherwise they are case preserving but case insensitive. E.g.
>>> models.Person.objects.filter(first__in=['John', 'Ringo'])
[<Person: John Lennon>, <Person: Ringo Starr>]
>>> models.Person.objects.filter(first__in=['joHn', 'RiNgO'])
[<Person: John Lennon>, <Person: Ringo Starr>]
So, if portability is not crucial and you use MySQL you may choose to ignore the issue altogether.
I am expanding Exgeny idea into an two liner.
import functools
Name.objects.filter(functools.reduce(lambda acc,x: acc | Q(name_iexact=x)), names, Q()))
After trying many methods, including annotate, which resulted in duplicate objects, I discovered transformers (https://docs.djangoproject.com/en/4.1/howto/custom-lookups/#a-transformer-example) which allow for a simple solution.
Add the following to models.py before model declarations:
class LowerCase(models.Transform):
lookup_name = "lower"
function = "LOWER"
models.CharField.register_lookup(LowerCase)
models.TextField.register_lookup(LowerCase)
You can now use the __lower transformer alongside any lookup, in this case: field__lower__in. You can also add bilateral = True to the transformer class for it to apply to both the field and the list items, which should be functionally equivalent to __iin.
Here is an example of custom User model classmethod to filter users by email case-insensitive
from django.db.models import Q
#classmethod
def get_users_by_email_query(cls, emails):
q = Q()
for email in [email.strip() for email in emails]:
q = q | Q(email__iexact=email)
return cls.objects.filter(q)
If this is a common use case for anyone, you can implement this by adapting the code from Django's In and IExact transformers.
Make sure the following code is imported before all model declarations:
from django.db.models import Field
from django.db.models.lookups import In
#Field.register_lookup
class IIn(In):
lookup_name = 'iin'
def process_lhs(self, *args, **kwargs):
sql, params = super().process_lhs(*args, **kwargs)
# Convert LHS to lowercase
sql = f'LOWER({sql})'
return sql, params
def process_rhs(self, qn, connection):
rhs, params = super().process_rhs(qn, connection)
# Convert RHS to lowercase
params = tuple(p.lower() for p in params)
return rhs, params
Example usage:
result = Name.objects.filter(name__iin=['name1', 'name2', 'name3'])