I have a form that has a group of 13 checkboxes that together make up my search criteria... except that I also added a pair of radio buttons for ALL or ANY.
I was hoping to get away with something elegant like:
priority_ids = request.GET.getlist("priority") # checkboxes
collection = request.GET.get("collection") # radio buttons
priorities = []
for priority_id in priority_ids:
priorities.append(Q(focus__priority=priority_id))
if (collection == "any"): qset = any(priorities)
elif (collection == "all"): qset = all(priorities)
However, any() and all() return a boolean, not a queryset that I can use in a filter. I want an "any" or "all" that does the equivalent of "Q(...) | Q(...) | Q(...)" or "Q(...) & Q(...) & Q(...)" for anywhere from 1 to 13 criteria.
There's nothing that Django needs to do about that. You just need to combine your Q-s with & and respectively |, in a simple loop or in a more compact way with reduce.
And regarding terminology it seems to me that you are calling Q a queryset, but it's not. It's a filter on a queryset.
Something like the below should work:
priority_ids = request.GET.getlist("priority")
collection = request.GET.get("collection")
priority_filters = []
for priority_id in priority_ids:
priority_filters.append(Q(focus__priority=priority_id))
base_qs = SomeModel.objects.all()
if collection == "any":
filtered_qset = base_qs.filter(reduce(operator.or_, priority_filters))
elif collection == "all":
filtered_qset = base_qs.filter(reduce(operator.and_, priority_filters))
You can query dynamically with Q() as below...
from django.db.models import Q
from priority_app.models import Priority # get your model identified
#(whatever API place you had this):
priority_id = request.GET.get("priority") #getlist is isn't a proper list
field_id = request.query_params.get('collection')
if priority_id:
priority_id_list = list(map(str.strip, priority_id.split(",")))
dynamic_query_filters = Priority.objects.none()
for a_item in priority_id_list:
if collection == "any":
dynamic_query_filters |= (Q(id=a_item)) # OR query
else:
dynamic_query_filters &= (Q(id=a_item)) # AND query
queryset = Priority.objects.filter(dynamic_query_filters)
# you will notice, it executes in order of "query", maybe take them in different variables and chain them accordingly.
Related
I have these classes:
class Keyword(models.Model):
keyword = models.CharField(max_length=100, unique=True)
class Snippet(models.Model):
keywords = models.ManyToManyField(Keyword)
and a list of searched keywords:
searchlist = ['Photo','Cat']
I would like to dynamically query the database for snippets that contain both (AND operation) of the keywords in searchlist.
From that list of snippets, return the list of unique keywords from each of the snippets, excluding the original search terms.
So far I have this:
# Turn list of values into list of Q objects
queries = [Q(keywords__keyword__exact=tag) for tag in searchlist]
# Take one Q object from the list
query = queries.pop()
# Or the Q object with the ones remaining in the list
for item in queries:
query &= item
# Query the model
snippet_list = Snippet.objects.filter(query)
which returns the desired list of snippets but I'm unsure of how to retrieve the combined list of keywords, or if there is a more efficient way to achieve the end result.
EDIT:
I would like to achieve the following pseudo code on snippet_list:
for snippet in snippet_list:
combined_keywords += snippet.keywords
ordered_keyword_list = getOrderedKeywords() # <- I have this function already
final_list = intersection(ordered_keyword_list, combined_keywords)
where combined_keywords is all the keywords in the snippet_list. And final_list is a ordered unique version of combined_keywords.
The final solution looks like this thanks to #Soravux and #Dave-Webb. I'm not sure if this is the most efficient method, but it works.
# Turn list of values into list of Q objects
queries = [Q(keywords__keyword__exact=tag) for tag in valueslist]
query = Q()
for item in queries:
query &= item
# Query the model
snippet_list = Snippet.objects.filter(query)
combined_keywords = []
for snippet in snippet_list:
combined_keywords += snippet.keywords.all()
ordered_keyword_list = Keyword.objects.all().annotate(count=Count('snippet')).order_by('-count')
final_list = [x for x in ordered_keyword_list if x in combined_keywords]
I would like to know which is the best way to filter objects, that have multiple parameter/value pairs of one foreign model.
Example:
class Car(models.Model):
name = models.SlugField("Name")
class Parameter(models.Model):
name = models.SlugField()
value = models.TextField(blank=True)
car = models.ForeignKey('Car', related_name = 'parameters')
Let's say i have some cars that have these 3 specific parameters: (name=color, value=blue); (name=tire, value=Goodyear); (name=seat, value=leather)
What would be the best way to filter all cars that have these certain parameter/value combinations?
What i have done by now is to filter one parameter/value pair after the other in a loop. But this takes to much time and i'm sure there is a better way. Somehow by aggregations perhaps...?
By now the only way i get it to work, is:
Job.objects.filter( Q(parameters__name="tire", parameters__value="Goodyear") ).filter( Q(parameters__name="color", parameters__value="blue") ).filter( Q(parameters__name="seat", parameters__value="leather") )
If I understand you correctly you are looking to OR some query filters, by default Django ANDs all filters together. To introduce ORing you should use Q objects. In your example you would need a combination of ANDing (&) and ORing (|):
from django.db.models import Q
queryset = Car.objects.filter(
(Q(parameters__name='color') & Q(parameters__value='blue')) |
(Q(parameters__name='tire') & Q(parameters__value='Goodyear')) |
(Q(parameters__name='seat') & Q(parameters__value='leather'))
)
Note the reverse lookup from Car to Parameter.
This should equate to get me all Cars that have parameters: name=color AND value=blue OR parameters: name=tire AND value=Goodyear OR parameters: name=seat AND value=leather.
Use the Q option.
from django.db.models import Q
query = Q()
query &= (Q(name='color') & Q(value='blue'))
query &= (Q(name='tire') & Q(value="Goodyear"))
query &= (Q(name="seat") & Q(value="leather"))
# List : Force the db call to one call
result = list(Parameters.objects.filter(query))
for c in result:
c.car
Should works.
If I have the following query
return Table.objects.filter(Q(cond1) | Q(cond2))
is there a way to know which condition has given a specific row?
You can split your query in two queries:
qs1 = Table.objects.filter(cond1).extra(select={'condition': 'cond1'})
qs2 = Table.objects.filter(cond2).extra(select={'condition': 'cond2'})
Then create a union of the querysets:
qs12 = qs1 | qs2
EDITED: Unions are not supported between querysets with extra()
Then create a chain of your querysets:
from itertools import chain
qs12 = list(chain(qs1, qs2))
And use it like this:
for obj in qs12:
if obj.condition == 'cond1':
...
elif obj.condition == 'cond2':
...
Not really, no. Your query is roughly equivalent to the following SQL:
SELECT *
FROM Table
WHERE condition OR other_condition
Just like your django query, there is no natural indicator that will let you know which condition happened to be true for that particular relation. You either have to execute two queries, add extra information (the condition) to the relation, or use the condition itself.
c1 = Q('name__exact'='Bob') # condition 1
c2 = Q('name__exact'='Mary') # condition 2
# use separate queries
set1 = Table.objects.filter(c1) # meets condition 1
set2 = Table.objects.filter(c2) # meets condition 2
# or use the natural condition
both = Table.objects.filter(c1|c2)
for item in both:
if item.name == 'Bob':
# condition 1
elif item.name == 'Mary':
# condition 2
I got a serach box in my project, the thing is that a user can enter any keyword and my ModelForm filters the fields I explicitly tell to filter, I use the following piece of code in my Form:
def get_matching_siniestros(self):
if self.cleaned_data['keywords'] is None:
return None
matching = []
for kw in self.cleaned_data['keywords']:
numero_ajuste = Siniestro.objects.filter(
numero_ajuste__icontains=kw
)
nombre_contratante = Siniestro.objects.filter(
poliza__contratante__nombre__icontains=kw
)
matching = chain(
numero_ajuste,
nombre_contratante,
matching
)
# verify not repeated Siniestro
non_rep_siniestros = []
for siniestro in matching:
if siniestro not in non_rep_siniestros:
non_rep_siniestros.append(siniestro)
return non_rep_siniestros
What I want to do is to programatically filter on any CharField in the Model and also if possible on any CharField of nested relations, in this example Siniestro has a FK to poliza and poliza has an FK to contratante.
You can iterate over every field and do whatever you like, e.g.:
[process(field) for field in model._meta.fields if field.__class__ == CharField]
where process can be a function, or whatever you require.
That said, I should really point out that the complexity you're trying to involve is bound to get messy. IMO, have a look at django-haystack. Indexing should be the way to go.
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]))