from django.db.models import Q
MODULES_USERS_PERMS = {
MODULE_METHOD: [],
MODULE_NEWS: [],
MODULE_PROJECT: ['created_by', 'leader'],
MODULE_TASK: [],
MODULE_TICKET: [],
MODULE_TODO: []
}
filter_fields = MODULES_USERS_PERMS[MODULE_PROJECT]
perm_q = map(lambda x: Q(x=user), filter_fields)
if perm_q: #sum(perm_q)
if len(perm_q) == 1:
return perm_q[0]
elif len(perm_q) == 2:
return perm_q[0] | perm_q[1]
elif len(perm_q) == 3:
return perm_q[0] | perm_q[1] | perm_q[2]
I do not know how to describe in words what is required by code, I hope he speaks for itself.
I need to make a filter from the list of objects.
Needless code is not working.
UPDATE:
code, that looks better, but not working too:
filters = ['created_by', 'leader']
filter_params = Q()
for filter_obj in filters:
filter_params = filter_params | Q(filter_obj=user)
FieldError at /projects/
Cannot resolve keyword 'filter_obj' into field. Choices are:
begin_time, comment, created_at, created_by, created_by_id, end_time,
id, leader, leader_id, name, project_task, status, ticket_project
If you're looking to combine an unknown number of Q objects:
import operator
perm_q = reduce(operator.or_, perm_q)
Or:
summed_q = perm_q[0]
for new_term in perm_q[1:]:
summed_q = summed_q | new_term
Which does the same thing, just more explicitly.
Based on your edit - you need to turn the string contained in your filter_obj variable into a keyword argument. You can do this by creating a dictionary to use as the keyword arguments for the Q constructor:
filters = ['created_by', 'leader']
filter_params = Q()
for filter_obj in filters:
kwargs = {filter_obj: user}
filter_params = filter_params | Q(**kwargs)
Related
I am querying a set of objects with a condition like this:
filters_for_dates_report = ['birthday', 'job_anniversary', 'wedding_anniversary']
values_list_for_dates_report = ['id', 'name', 'date']
for filter_for_dates_report in filters_for_dates_report:
filter_dict.update({filter_for_dates_report: {
filter_for_dates_report + "__range" : [start_date, end_date]
}})
list_of_Q = [Q(**{key: val}) for key, val in filter_dict.items()]
if list_of_Q:
model_details = Model.objects.filter(reduce(operator.or_, list_of_Q))
.values(*values_list_for_dates_report)
Now I want to exclude the objects which have null values for filter_for_dates_report attributes.
A direct query would be
Model.objects.filter(
Q(birthday__range=[start_date, end_date] & birthday__isnull=False))
.values(*values_list_for_dates_report)
But how can I do this for multiple values wherein I want only the values within that range and also which are not null for multiple filter_for_dates_report attributes.
Something like:
Model.objects.filter(
(Q(birthday__range=[start_date, end_date]) & Q(birthday__isnull=False)) |
(Q(marriage_anniversary__range=[start_date, end_date]) & Q(marriage_anniversary__isnull=False)) |
(Q(job_anniversary__range=[start_date, end_date]) & Q(job_anniversary__isnull=False)))
.values(*values_list_for_dates_report)
loop over and reduce it with OR operator :
import operator
filter_dict = []
queryset = Model.objects.all()
for filter_for_dates_report in filters_for_dates_report:
filter_dict.append(Q(**{
filter_for_dates_report + "__range": [start_date, end_date]
}))
queryset = Model.objects.filter(
reduce(operator.or_, filter_dict)
).values(*values_list_for_dates_report)
This will create a queryset with filters OR with what you put in the loop.
You dont need to add a __isnull if you add a __range
I am building a search feature with multiple T/F filter options, like so:
Search: ________________
Filters:
___ Open to Public
___ Parking Available
_x_ Free Entrance
In this case the user is specifically looking for events that are free to get into. I would like my Q object to return keyword matched objects where free_entrance is set to True.
My thought is to define the variables in my search function:
search_public = None
search_parking = None
search_free_entrance = True
and set the Q object up like so:
q_objects.append(
Q(
name__icontains=search_term,
public__icontains=search_public,
parking__icontains=search_parking,
free_entrance=search_free_entrance
)
)
However, I want all objects (True or False) to be returned for the unfilitered variables (instead of only objects set to None). Is there a keyword I can insert, or is there a Q object filter type that I am missing?
UPDATE:
In addition to the posted answer, *args can also be used for complex (OR) Q objects:
From:
http://www.nomadjourney.com/2009/04/dynamic-django-queries-with-kwargs/
args = ( Q( name__icontains = 'search_term' ) | Q( company__icontains = 'search_term' ) )
Use kwargs:
search = {'name__icontains': search_term}
if search_public is not None:
search.update({'public__icontains': search_public})
if search_parking is not None:
search.update({'parking__icontains': search_parking})
if search_free_entrance is not None:
search.update({'pree_entrance__icontains': search_free_entrance})
q_objects.append(Q(**search))
or more complicated example (your question from comment):
search_kwargs = {'name__icontains': search_term}
search_args = tuple()
if search_public is not None:
search_args += (Q(Q(company__icontains=search_public) | Q(other__icontains=search_public)),)
q_objects.append(Q(*search_args, **search_kwargs))
I'm trying to get the next and previous objects of a comic book issue. Simply changing the id number or filtering through date added is not going to work because I don't add the issues sequentially.
This is how my views are setup and it WORKS for prev_issue and does return the previous object, but it returns the last object for next_issue and I do not know why.
def issue(request, issue_id):
issue = get_object_or_404(Issue, pk=issue_id)
title = Title.objects.filter(issue=issue)
prev_issue = Issue.objects.filter(title=title).filter(number__lt=issue.number)[0:1]
next_issue = Issue.objects.filter(title=title).filter(number__gt=issue.number)[0:1]
Add an order_by clause to ensure it orders by number.
next_issue = Issue.objects.filter(title=title, number__gt=issue.number).order_by('number').first()
I know this is a bit late, but for anyone else, django does have a nicer way to do this, see https://docs.djangoproject.com/en/1.7/ref/models/instances/#django.db.models.Model.get_previous_by_FOO
So the answer here would be something something like
next_issue = Issue.get_next_by_number(issue, title=title)
Django managers to do that with a bit of meta class cleaverness.
If it's required to find next and previous objects ordered by field values that can be equal and those fields are not of Date* type, the query gets slightly complex, because:
ordering on objects with same values limiting by [:1] will always produce same result for several objects;
object can itself be included in resulting set.
Here's are querysets that also take into account the primary keys to produce a correct result (assuming that number parameter from OP is not unique and omitting the title parameter as it's irrelevant for the example):
Previous:
prev_issue = (Issue.objects
.filter(number__lte=issue.number, id__lt=instance.id)
.exclude(id=issue.id)
.order_by('-number', '-id')
.first())
Next:
next_issue = (Issue.objects
.filter(number__gte=issue.number, id__gt=instance.id)
.exclude(id=issue.id)
.order_by('number', 'id')
.first())
from functools import partial, reduce
from django.db import models
def next_or_prev_instance(instance, qs=None, prev=False, loop=False):
if not qs:
qs = instance.__class__.objects.all()
if prev:
qs = qs.reverse()
lookup = 'lt'
else:
lookup = 'gt'
q_list = []
prev_fields = []
if qs.query.extra_order_by:
ordering = qs.query.extra_order_by
elif qs.query.order_by:
ordering = qs.query.order_by
elif qs.query.get_meta().ordering:
ordering = qs.query.get_meta().ordering
else:
ordering = []
ordering = list(ordering)
if 'pk' not in ordering and '-pk' not in ordering:
ordering.append('pk')
qs = qs.order_by(*ordering)
for field in ordering:
if field[0] == '-':
this_lookup = (lookup == 'gt' and 'lt' or 'gt')
field = field[1:]
else:
this_lookup = lookup
q_kwargs = dict([(f, get_model_attr(instance, f))
for f in prev_fields])
key = "%s__%s" % (field, this_lookup)
q_kwargs[key] = get_model_attr(instance, field)
q_list.append(models.Q(**q_kwargs))
prev_fields.append(field)
try:
return qs.filter(reduce(models.Q.__or__, q_list))[0]
except IndexError:
length = qs.count()
if loop and length > 1:
return qs[0]
return None
next_instance = partial(next_or_prev_instance, prev=False)
prev_instance = partial(next_or_prev_instance, prev=True)
note that do not use object.get(pk=object.pk + 1) these sorts of things, IntegrityError occurs if object at that pk is deleted, hence always use a query set
for visitors:
''' Useage '''
"""
# Declare our item
store = Store.objects.get(pk=pk)
# Define our models
stores = Store.objects.all()
# Ask for the next item
new_store = get_next_or_prev(stores, store, 'next')
# If there is a next item
if new_store:
# Replace our item with the next one
store = new_store
"""
''' Function '''
def get_next_or_prev(models, item, direction):
'''
Returns the next or previous item of
a query-set for 'item'.
'models' is a query-set containing all
items of which 'item' is a part of.
direction is 'next' or 'prev'
'''
getit = False
if direction == 'prev':
models = models.reverse()
for m in models:
if getit:
return m
if item == m:
getit = True
if getit:
# This would happen when the last
# item made getit True
return models[0]
return False
original author
Usage
# you MUST call order by to pass in an order, otherwise QuerySet.reverse will not work
qs = Model.objects.all().order_by('pk')
q = qs[0]
prev = get_next_or_prev(qs, q, 'prev')
next = get_next_or_prev(qs, q, 'next')
next_obj_id = int(current_obj_id) + 1
next_obj = Model.objects.filter(id=next_obj_id).first()
prev_obj_id= int(current_obj_id) - 1
prev_obj = Model.objects.filter(id=prev_obj_id).first()
#You have nothing to loose here... This works for me
I have a Django form setup using GET method. Each value corresponds to attributes of a Django model. What would be the most elegant way to generate the query? Currently this is what I do in the view:
def search_items(request):
if 'search_name' in request.GET:
query_attributes = {}
query_attributes['color'] = request.GET.get('color', '')
if not query_attributes['color']: del query_attributes['color']
query_attributes['shape'] = request.GET.get('shape', '')
if not query_attributes['shape']: del query_attributes['shape']
items = Items.objects.filter(**query_attributes)
But I'm pretty sure there's a better way to go about it.
You could do it with a list comp and and "interested params" set:
def search_items(request):
if 'search_name' in request.GET:
interested_params = ('color', 'shape')
query_attrs = dict([(param, val) for param, val in request.GET.iteritems()
if param in interested_params and val])
items = Items.objects.filter(**query_attrs)
Just for fun (aka don't actually do this) you could do it in one line:
def search_items(request):
items = Items.objects.filter(
**dict([(param, val) for param, val in request.GET.iteritems()
if param in ('color', 'shape') and val])
) if 'search_name' in request.GET else None
well, the basic way you are approaching the problem seems sound, but the way you wrote it out looks a little funny. I'd probably do it this way:
def search_items(request):
if 'search_name' in request.GET:
query_attributes = {}
color = request.GET.get('color', '')
if color:
query_attributes['color'] = color
shape = request.GET.get('shape', '')
if shape:
query_attributes['shape'] = shape
items = Items.objects.filter(**query_attributes)
If you want it to be fully dynamic, you can use a little bit of model introspection to find out what fields you can actually query, and filter only using those.
Though, this solution won't allow you to use __lookups in GET parameters, don't know if you need it.
def search_items(request):
if 'search_name' in request.GET:
all_fields = Items._meta.get_all_field_names()
filters = [(k, v) for k, v in request.GET.items() if k in all_fields]
items = Items.objects.filter(*filters)
def search_items(request):
try:
items = Items.objects.filter(**dict([
(F, request.GET[F]) for F in ('color', 'shape')
]))
except KeyError:
raise Http404
Suppose 'color' and 'shape' are required GET params. Predefined tuple of filtering params is prefered because of security reasons.
I want to be able to list the items that either a user has added (they are listed as the creator) or the item has been approved.
So I basically need to select:
item.creator = owner or item.moderated = False
How would I do this in Django? (preferably with a filter or queryset).
There is Q objects that allow to complex lookups. Example:
from django.db.models import Q
Item.objects.filter(Q(creator=owner) | Q(moderated=False))
You can use the | operator to combine querysets directly without needing Q objects:
result = Item.objects.filter(item.creator = owner) | Item.objects.filter(item.moderated = False)
(edit - I was initially unsure if this caused an extra query but #spookylukey pointed out that lazy queryset evaluation takes care of that)
It is worth to note that it's possible to add Q expressions.
For example:
from django.db.models import Q
query = Q(first_name='mark')
query.add(Q(email='mark#test.com'), Q.OR)
query.add(Q(last_name='doe'), Q.AND)
queryset = User.objects.filter(query)
This ends up with a query like :
(first_name = 'mark' or email = 'mark#test.com') and last_name = 'doe'
This way there is no need to deal with or operators, reduce's etc.
You want to make filter dynamic then you have to use Lambda like
from django.db.models import Q
brands = ['ABC','DEF' , 'GHI']
queryset = Product.objects.filter(reduce(lambda x, y: x | y, [Q(brand=item) for item in brands]))
reduce(lambda x, y: x | y, [Q(brand=item) for item in brands]) is equivalent to
Q(brand=brands[0]) | Q(brand=brands[1]) | Q(brand=brands[2]) | .....
Similar to older answers, but a bit simpler, without the lambda...
To filter these two conditions using OR:
Item.objects.filter(Q(field_a=123) | Q(field_b__in=(3, 4, 5, ))
To get the same result programmatically:
filter_kwargs = {
'field_a': 123,
'field_b__in': (3, 4, 5, ),
}
list_of_Q = [Q(**{key: val}) for key, val in filter_kwargs.items()]
Item.objects.filter(reduce(operator.or_, list_of_Q))
operator is in standard library: import operator
From docstring:
or_(a, b) -- Same as a | b.
For Python3, reduce is not a builtin any more but is still in the standard library: from functools import reduce
P.S.
Don't forget to make sure list_of_Q is not empty - reduce() will choke on empty list, it needs at least one element.
Multiple ways to do so.
1. Direct using pipe | operator.
from django.db.models import Q
Items.objects.filter(Q(field1=value) | Q(field2=value))
2. using __or__ method.
Items.objects.filter(Q(field1=value).__or__(field2=value))
3. By changing default operation. (Be careful to reset default behavior)
Q.default = Q.OR # Not recommended (Q.AND is default behaviour)
Items.objects.filter(Q(field1=value, field2=value))
Q.default = Q.AND # Reset after use.
4. By using Q class argument _connector.
logic = Q(field1=value, field2=value, field3=value, _connector=Q.OR)
Item.objects.filter(logic)
Snapshot of Q implementation
class Q(tree.Node):
"""
Encapsulate filters as objects that can then be combined logically (using
`&` and `|`).
"""
# Connection types
AND = 'AND'
OR = 'OR'
default = AND
conditional = True
def __init__(self, *args, _connector=None, _negated=False, **kwargs):
super().__init__(children=[*args, *sorted(kwargs.items())], connector=_connector, negated=_negated)
def _combine(self, other, conn):
if not(isinstance(other, Q) or getattr(other, 'conditional', False) is True):
raise TypeError(other)
if not self:
return other.copy() if hasattr(other, 'copy') else copy.copy(other)
elif isinstance(other, Q) and not other:
_, args, kwargs = self.deconstruct()
return type(self)(*args, **kwargs)
obj = type(self)()
obj.connector = conn
obj.add(self, conn)
obj.add(other, conn)
return obj
def __or__(self, other):
return self._combine(other, self.OR)
def __and__(self, other):
return self._combine(other, self.AND)
.............
Ref. Q implementation
This might be useful https://docs.djangoproject.com/en/dev/topics/db/queries/#spanning-multi-valued-relationships
Basically it sounds like they act as OR
Item.objects.filter(field_name__startswith='yourkeyword')