How to make Example code more DRY? - django

I have the following code:
for e in Contact.objects.filter(contact_owner=batch.user, group=batch.group):
msg = Message.objects.create(
recipient_number=e.mobile,
)
However, I want to do something like the below where first I check if the object has a group if not filter on contact instead else filter on group.
As you can see this is not very DRY. How can this be made better?
if self.group == None:
for e in Contact.objects.filter(contact_owner=batch.user, contact=batch.group):
msg = Message.objects.create(
recipient_number=e.mobile,
)
else:
for e in Contact.objects.filter(contact_owner=batch.user, group=batch.group):
msg = Message.objects.create(
recipient_number=e.mobile,
)

The idea you should consider in your case is taken from
https://docs.djangoproject.com/en/dev/topics/db/queries/#chaining-filters
QuerySets are lazy – the act of creating a QuerySet doesn’t involve any database activity. You can stack filters together all day long, and Django won’t actually run the query until the QuerySet is evaluated
This allows you to build queries brick by brick.
q = Contact.objects.filter(contact_owner=batch.user)
if self.group == None:
q = q.filter(contact=batch.group)
else
q = q.filter(group=batch.group)
for e in q:
msg = Message.objects.create(
recipient_number=e.mobile,
)

You can also use the if operator, if you prefer this kind of shorter writing
q=Contact.objects.filter(contact_owner=batch.user)
q = q.filter(contact=batch.group) if self.group is None else q.filter(group=batch.group)
for e in q:
msg = Message.objects.create(
recipient_number=e.mobile,
)

Related

Django filtering the Q problem when is empty

I have a problem filtering the data and showing the result in a table...
In my template I have some inputs that I send input values with ajax and then i get the value using request.POST.get() in my views. The problem appears when some fields are empty I receive an error "Cannot use None as a query value"
I want to do something like this but i cannot find a solution
ex. when the request.POST.get is empty that Q to be ignored
Can anyone help me?
Thanks and sorry for my english
This is my code in my views.py
def api(request):
a = request.POST.get('a')
b = request.POST.get('b')
c = request.POST.get('c')
d = request.POST.get('d')
e = request.POST.get('e')
f = request.POST.get('f')
g = request.POST.get('g')
h = request.POST.get('h')
raport = Test.objects.filter(
Q(data_test__year__gte=a) &
Q(data_test__year__lte=b) &
Q(data_in__isnull=c) &
Q(data_out__isnull=d) &
Q(adress__fieldone=e) &
Q(adress__fieldtwo=f) &
Q(categoru=g) &
Q(stare=h) &
Q(medic=i)
)
serializer = TestSerializer(raport, many=True)
return JsonResponse(serializer.data, safe=False)
We can make a function that creates a Q-object [Django-doc] for only non-None values, like:
from django.db.models import Q
def q_without_none(**kwargs):
return Q(**{k: v for k, v in kwargs.items() if v is not None})
or if you want to exclude the empty string as well:
from django.db.models import Q
def q_without_empty(**kwargs):
return Q(**{k: v for k, v in kwargs.items() if v not in (None, '')})
Then we can construct the query like:
raport_pacient = Pacient.objects.filter(
q_without_none(
data_creare_pacient__year__gte=de_la,
data_creare_pacient__year__lte=pana_la,
data_iesire__isnull=iesit,
data_deces__isnull=iesit,
adresa_pacient__judet__nume_judet=judet,
adresa_pacient__localitate__nume_localitate=localitate,
categorie=categorie,
stare_civila=stare_civila,
medic=request.user.medic
)
)
Note: although not really your question, using a JsonResponse with safe=False is, like the name suggests, not very safe. There have been exploits with lists for JSON, so it is usually better to define a dictionary at the top level, for example like:
return JsonResponse({'data': serializer.data})

Django Q Objects - Return both True and False matches

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))

Getting next and previous objects in Django

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

Django: Generating a queryset from a GET request

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.

How to include "None" in lte/gte comparisons?

I've got this complex filtering mechanism...
d = copy(request.GET)
d.setdefault('sort_by', 'created')
d.setdefault('sort_dir', 'desc')
form = FilterShipmentForm(d)
filter = {
'status': ShipmentStatuses.ACTIVE
}
exclude = {}
if not request.user.is_staff:
filter['user__is_staff'] = False
if request.user.is_authenticated():
exclude['user__blocked_by__blocked'] = request.user
if form.is_valid():
d = form.cleaned_data
if d.get('pickup_city'): filter['pickup_address__city__icontains'] = d['pickup_city']
if d.get('dropoff_city'): filter['dropoff_address__city__icontains'] = d['dropoff_city']
if d.get('pickup_province'): filter['pickup_address__province__exact'] = d['pickup_province']
if d.get('dropoff_province'): filter['dropoff_address__province__exact'] = d['dropoff_province']
if d.get('pickup_country'): filter['pickup_address__country__exact'] = d['pickup_country']
if d.get('dropoff_country'): filter['dropoff_address__country__exact'] = d['dropoff_country']
if d.get('min_price'): filter['target_price__gte'] = d['min_price']
if d.get('max_price'): filter['target_price__lte'] = d['max_price']
if d.get('min_distance'): filter['distance__gte'] = d['min_distance'] * 1000
if d.get('max_distance'): filter['distance__lte'] = d['max_distance'] * 1000
if d.get('available_on'): # <--- RELEVANT BIT HERE ---
filter['pickup_earliest__lte'] = d['available_on'] # basically I want "lte OR none"
filter['pickup_latest__gte'] = d['available_on']
if d.get('shipper'): filter['user__username__iexact'] = d['shipper']
order = ife(d['sort_dir'] == 'desc', '-') + d['sort_by']
shipments = Shipment.objects.filter(**filter).exclude(**exclude).order_by(order) \
.annotate(num_bids=Count('bids'), min_bid=Min('bids__amount'), max_bid=Max('bids__amount'))
And now my client tells me he wants pickup/drop-off dates to be 'flexible' as an option. So I've updated the DB to allow dates to be NULL for this purpose, but now the "available for pickup on" filter won't work as expected. It should include NULL/None dates. Is there an easy fix for this?
Flip the logic and use exclude(). What you really want to do is exclude any data that specifies a date that doesn't fit. If pickup_latest and pickup_earliest are NULL it shouldn't match the exclude query and wont be removed. Eg
exclude['pickup_latest__lt'] = d['available_on']
exclude['pickup_earliest__gt'] = d['available_on']
Most database engines don't like relational comparisons with NULL values. Use <field>__isnull to explicitly check if a value is NULL in the database, but you'll need to use Q objects to OR the conditions together.
Don't think that's actually a django-specific question. Variable 'd' is a python dictionary, no? If so, you can use this:
filter['pickup_latest__gte'] = d.get('available_on', None)