Django Queryset for no-operation - python

Is there any way to specify a Django Queryset, which will do nothing but still be valid Queryset? Empty queryset should ideally not call DB and results would be empty.

Model.objects.none() always gives you an empty queryset

Use MyModel.objects.none() which will return an EmptyQuerySet, which is a QuerySet subclass that always evaluates to an empty list.
For eg., if you have a model class Entry (or just use models.Model), you can simply create an EmptyQuerySet by:
>>> Entry.objects.none()
[]

Related

Can we add add objects to django queryset like we do to python list. I have an empty django queryset, now I want to add objects to it

This is how I have initialized an Empty Queryset now I want to add objects to it.
How can I do that.
from django.db.models.query import EmptyQuerySet
EmptyQuerySet is a class to checking if a queryset is empty by .none()
A queryset represents a query you make to your database and it have to be translatable to an SQL query.
So you can't add objects to a QuerySet like to python list but you can combine queryset using set operations.
See QuerySet API Reference for more information.

What if I delete rows from Django queryset and then filter again?

Consider the following code:
questions = Question.objects.only('id', 'pqa_id', 'retain')
del_questions = questions.filter(retain=False)
# Some computations on del_questions
del_questions.delete()
add_questions = questions.filter(pqa_id=None)
Will add_questions not contain questions with retain=False? I.e. is questions object re-evaluated when we run delete() on its subset del_questions?
Short answer: you here use different QuerySets, so you will here, by creating a copy, make another query. If you would use the same QuerySet, Django will remove the cache, and so it will re-evaluate the QuerySet. It is however possible to let objects temporarily survive a .delete() call, due to caching in another QuerySet that was evaluated before.
is questions object re-evaluated when we run delete() on its subset del_questions
questionss is never evaluated in the first place. A QuerySet is iterable and in case you iterate over it (or fetch the length, or something else), will result in a query. But if you write Model.objects.all().filter(foo=3) then Django will not first "evaluate" the .all() by fetching all Model objects into memory.
A QuerySet is in essence a tool to build a query, by chaining operations and each time constructing a new queryset. Eventually you can evaluate one of the querysets.
Here by apply a .filter(..) for the two calls. We thus constructed two different QuerySets, and so if you evaluate the former, then this will not result in any caching in the latter.
A second important note is that a .delete() does not evaluate the queryset, and thus does not cache the results. If we inspect the .delete() method [GitHub], we see:
def delete(self):
"""Delete the records in the current QuerySet."""
assert self.query.can_filter(), \
"Cannot use 'limit' or 'offset' with delete."
if self._fields is not None:
raise TypeError("Cannot call delete() after .values() or .values_list()")
del_query = self._chain()
# The delete is actually 2 queries - one to find related objects,
# and one to delete. Make sure that the discovery of related
# objects is performed on the same database as the deletion.
del_query._for_write = True
# Disable non-supported fields.
del_query.query.select_for_update = False
del_query.query.select_related = False
del_query.query.clear_ordering(force_empty=True)
collector = Collector(using=del_query.db)
collector.collect(del_query)
deleted, _rows_count = collector.delete()
# Clear the result cache, in case this QuerySet gets reused.
self._result_cache = None
return deleted, _rows_count
With self._chain(), one creates a copy of the querset. So even if this would change the state of a QuerySet, then it would not change the state of this QuerySet.
Another interesting part is self._result_cache = None, here Django resets the cache. So if the queryset was already evaluated before you called .delete() (for example you materialized the queryset before calling .delete()), then it will remove that cache. So if you would reevaluate the QuerySet, this would result in another query to fetch the items.
There is however a scenario where data can still get outdated. For example the following:
questions = Question.objects.all() # create a queryset
list(questions) # materialize the result
questions2 = questions.all() # create a copy of this queryset
questions2.delete() # remove the entries
If we now would call list(questions), we thus obtain the elements in the cache of questions, and this QuerySet is not invalidated, so the elements "survive" a .delete() from another queryset (a copy from this one, although that is not necessary, a simply Questions.objects.all().delete() would also do the trick).

How to filter in django

I want to filter Consultants(this is my model from where I want to filter)
on the basis of username.
But when I try to filter it returns a empty list.
my code is
username=request.user.username
print "value of requested logged in user is",username
user=Consultants.objects.filter(username='username')
print user.consul_id
When I print consul_id it says QuerySet object has no attribute consul_id.
How can I filter? Should I use get to filter.I have used get but it didn't solve my problem.
You should change this:
user=Consultants.objects.filter(username='username')
print user.consul_id
to
user=Consultants.objects.filter(username='username').first()
print user.consul_id if user else 'None'
Because calling filter() returns a Queryset. You need to get the first element from that queryset(hence use first()). Also you can use get() method

Move an element inside a Queryset

I have a queryset in my django view.
I would like to change the position of one of the item in order it to be the first element of the queryset.
Any idea on how to do that?
EDIT:
I have a queryset:
qs = UserProfile.objects.filter(myfilter=whatever)
I know that inside this queryset I have:
specific_user.userprofile
What I want to do is to put the user.userprofile in first position into my queryset because I use this queryset in a loop in my template:
{% for i in qs %}
<div> i.name </div>
And I want to be sure that the first name of the list is the name of the specific_user.
QuerySet objects in Django are abstract representations of the result of database queries. Generally speaking, it represents SQL that might later be executed when specific objects from the queryset are requested, but until that happens, no queries are executed. So you can't really think of a queryset like a list, which might be arbitrarily reordered. You can request the objects in your queryset to be sorted using its order_by method, so if you want a particular object to come first, it must have some sortable attribute on the DB side of things by which it can be ordered. This could be a column or a value calculated from its columns using F expressions.
But, for your needs, it seems like you could just make a list of your queryset in your view:
profiles = [specific_user.userprofile] + [profile for profile in UserProfile.objects.filter(myfilter=whatever).exclude(id=specific_user.userprofile.id)]
Add that to your context and iterate over it instead of the original queryset in your template.
Since Django 1.8 you have Conditional Expressions which you can use to define a specific order.
You would set a special case when the UserProfile is specific_user.userprofile:
qs = UserProfile.objects.filter(myfilter=whatever)\
.order_by(Case(When(pk=specific_user.userprofile.pk, then=0), default=1)))
We set the value of 0 in the special case, and 1 as default.
This way the specified user would always show first.

How to exclude results with get_object_or_404?

In Django you can use the exclude to create SQL similar to not equal. An example could be.
Model.objects.exclude(status='deleted')
Now this works great and exclude is very flexible. Since I'm a bit lazy, I would like to get that functionality when using get_object_or_404, but I haven't found a way to do this, since you cannot use exclude on get_object_or_404.
What I want is to do something like this:
model = get_object_or_404(pk=id, status__exclude='deleted')
But unfortunately this doesn't work as there isn't an exclude query filter or similar. The best I've come up with so far is doing something like this:
object = get_object_or_404(pk=id)
if object.status == 'deleted':
return HttpResponseNotfound('text')
Doing something like that, really defeats the point of using get_object_or_404, since it no longer is a handy one-liner.
Alternatively I could do:
object = get_object_or_404(pk=id, status__in=['list', 'of', 'items'])
But that wouldn't be very maintainable, as I would need to keep the list up to date.
I'm wondering if I'm missing some trick or feature in django to use get_object_or_404 to get the desired result?
Use django.db.models.Q:
from django.db.models import Q
model = get_object_or_404(MyModel, ~Q(status='deleted'), pk=id)
The Q objects lets you do NOT (with ~ operator) and OR (with | operator) in addition to AND.
Note that the Q object must come before pk=id, because keyword arguments must come last in Python.
The most common use case is to pass a Model. However, you can also pass a QuerySet instance:
queryset = Model.objects.exclude(status='deleted')
get_object_or_404(queryset, pk=1)
Django docs example:
https://docs.djangoproject.com/en/1.10/topics/http/shortcuts/#id2
There's another way instead of using Q objects. Instead of passing the model to get_object_or_404 just pass the QuerySet to the function instead:
model = get_object_or_404(MyModel.objects.filter(pk=id).exclude(status='deleted'))
One side effect of this, however, is that it will raise a MultipleObjectsReturned exception if the QuerySet returns multiple results.
get_object_or_404 utilizes the get_queryset method of the object manager. If you override the get_queryset method to only return items that aren't "deleted" then get_object_or_404 will automatically behave as you want. However, overriding get_queryset like this will likely have issues elsewhere (perhaps in the admin pages), but you could add an alternate manager for when you need to access the soft deleted items.
from django.db import models
class ModelManger(models.Manger):
def get_queryset(self):
return super(ModelManger, self).get_queryset().exclude(status='deleted')
class Model(models.Model):
# ... model properties here ...
objects = ModelManager()
all_objects = models.Manager()
So if you need only non-deleted items you can do get_object_or_404(Models, id=id) but if you need all items you can do get_object_or_404(Models.all_objects, id=id).

Categories