Django: When to use QuerySet none() - python

Just came across this in the django docs
Calling none() will create a queryset that never returns any objects
and no query will be executed when accessing the results. A qs.none()
queryset is an instance of EmptyQuerySet.
I build a lot of CRUD apps (surprise) and I can't think of a situation where I would need to use none().
Why would one want to return an EmptyQuerySet?

Usually in instances where you need to provide a QuerySet, but there isn't one to provide - such as calling a method or to give to a template.
The advantage is if you know there is going to be no result (or don't want a result) and you still need one, none() will not hit the database.
For a non-realistic example, say you have an API where you can query your permissions. If the account hasn't been confirmed, since you already have the Account object and you can see that account.is_activated is False, you could skip checking the database for permissions by just using Permission.objects.none()

In cases where you want to append to querysets but want an empty one to begin with
Similar to conditions where we instantiate an empty list to begin with but gradually keep appending meaningful values to it
example..
def get_me_queryset(conditionA, conditionB, conditionC):
queryset = Model.objects.none()
if conditionA:
queryset |= some_complex_computation(conditionA)
elif conditionB:
queryset |= some_complex_computation(conditionB)
if conditionC:
queryset |= some_simple_computation(conditionC)
return queryset
get_me_queryset should almost always return instance of django.db.models.query.QuerySet (because good programming) and not None or [], or else it will introduce headaches later..
This way even if none of the conditions come True, your code will still remain intact. No more type checking
For those who do not undestand | operator's usage here:
queryset |= queryset2
It translates to:
queryset = queryset + queryset

another use of queryset.none is when you don't know if there will be objects but do not want to raise an error.
example:
class DummyMixin(object):
def get_context_data(self,**kwargs):
""" Return all the pks of objects into the context """
context = super(DummyMixin, self).get_context_data(**kwargs)
objects_pks = context.get(
"object_list",
Mymodel.objects.none()
).values_list("pk", flat=True)
context["objects_pks"] = objects_pks

Another good use case for this is if some calling method wants to call .values_list() or similar on results. If the method returned None, you'd get an error like
AttributeError: 'list' object has no attribute 'values_list'
But if your clause returns MyModel.objects.none() instead of None, the calling code will be happy, since the returned data is an empty queryset rather than a None object.
Another way of putting it is that it allows you to not mix up return types (like "this function returns a QuerySet or None," which is messy).

It's useful to see where qs.none() is used in other examples in the Django docs. For example, when initializing a model formset using a queryset if you want the resulting formset to be empty the example given is:
formset = AuthorFormSet(queryset=Author.objects.none())

none() is used in get_queryset() to return an empty queryset depending on the state of has_view_or_change_permission() as shown below:
class BaseModelAdmin(metaclass=forms.MediaDefiningClass):
# ...
def has_view_or_change_permission(self, request, obj=None):
return self.has_view_permission(request, obj) or self.has_change_permission(
request, obj
)
# ...
class InlineModelAdmin(BaseModelAdmin):
# ...
def get_queryset(self, request):
queryset = super().get_queryset(request)
if not self.has_view_or_change_permission(request):
queryset = queryset.none() # Here
return queryset

Related

Sort queryset by added field

I'm trying to add an extra field to the instances of my queryset and then sort the set by the new field.
But I get a field error( Cannot resolve keyword 'foo' into field. Choices are: ... )
My view(abstract):
def view(request):
instances = Model.objects.all()
for counter, instance in enumerate(instances):
instance.foo = 'bar' + str(counter)
instances.order_by('foo') #this line causes trouble
return render(request, 'template.html', {'instances': instance})
My template:
{% for instance in instances %}
{{instance.foo}}
{% endfor %}
If I leave out the order_by line the templates renders as expected, so the field seems to be there.
So why do I get a field error?
It would be awesome, if somebody could help me to understand what I'm doing wrong.
Thanks in advance.
I found a possible solution to change the template to
{% for instance in instances|dictsort:'foo' %}
and that works fine, but from what I understand there should be as little logic as possible in the view, so I figure sorting should be done in the view.
Or is this actually the right way?
The Django ORM aims to construct database queries. As a result, you can only query on what a database "knows". Methods, properties, or attributes you added yourself are unknown to the database. The .order_by thus has no effect, since you "patched" the objects in the instances queryset.
If you however call an instances.order_by you construct a new queryset. This queryset takes the context of the parent, and thus represents a (slightly) modified query, but again, a query. Whether the old queryset is already evaluated or patched, is of no importance.
Furthermore even if there was a column foo, it would not help, since the instance.order_by does not order the instance queryset, it constructs a new one, one that looks approximately like the old one, except that the rows are ordered.
You thus will have to sort in Python now. You can for example construct a list of ordered elements with sorted(..), like:
from operator import attrgetter
def view(request):
instances = Model.objects.all()
for counter, instance in enumerate(instances):
instance.foo = 'bar' + str(counter)
mydata = sorted(instances, key=attrgetter('foo'))
return render(request, 'template.html', {'instances': mydata})
So now mydata is no longer a QuerySet, but a vanilla list. Furthermore note that ordering in a database might be slightly different than ordering in Python. In Python exceptions can occur if not all elements have the same type, and furthermore it is not guaranteed that the semantics behind the order relation is exactly the same.
The new attribute in the Python Objects does not exist in the database and only in those instances. order_by changes the queryset and not your current list of objects stored in memory.
One approach would be to use the builtin python sorting functions in the view like: sorted or even list.sort().

Django ORM: Filter with OR not returning object

The following code is not returning an object, but it 'should'. I'm passing the correct match for mobile, email and incentive id, So what I'm I doing wrong?
return Recipient.objects.filter(
models.Q(mobile=mobile,
email=email,
incentive__id__exact=incentive_id) |
models.Q(friends_mobile=mobile,
friends_email=email,
incentive__id__exact=incentive_id)
).order_by(
'-date_created')[:1].get()
using .get() is problematic. It will return an object if using a QuerySet with only one object, but if there's more it will throw a MultipleObjectsReturned error and if the QuerySet is empty it will throw a DoesNotExist Error.
So the problem is very probably a QuerySet that does not apply to the specific condition you require. The solution? Return a default value.
Finally, you can use [0] instead of limiting to one object and then fetching it, and also, as #pythonishvili mentioned, you could pre-filter on the incentive. summing it all up, it should look like so (I split the query across multiple lines for better readability):
qs = Recipient.objects.filter(incentive__id__exact=incentive_id) #common filter
qs = qs.filter(models.Q(mobil‌​e=mobile, email=email) |
models.Q(friends_mobile=mobile, friends_email=email))
qs = qs.order_by('-date_created')
return qs[0] if qs else None #or any other default you'd like to use

Changing the display order of forms in a formset

I am displaying a modelformset and I would like the forms to be ordered by the contents of one of its fields. So I want to use the equivalent of SomeModel.objects.filter(whatever).order_by('somefield') for a (model)formset in a template.
How can I do this?
Note that can_order does not do what I want (it must be automatic, not user specified). I have also tried other things, like the dictsort filter, but that produces unpredictable output (i.e. not ordered by the specified field).
I even tried {% regroup formset by somefield as sorted_formset %}, but the resulting sorted_formset cannot be used (iterated) as a normal formset.
To complete the answers. There are two ways how you can control the order of forms in the formset: formsets respect order of given queryset (this is shown in other replies). Another way (if you want to have the order of the forms fully under control) is to define custom formset class and override __iter__ and __getitem__ methods:
from django.forms import BaseModelFormSet
class MyModelBaseFormset(BaseModelFormSet):
def __iter__(self):
"""Yields the forms in the order they should be rendered"""
return ...
def __getitem__(self, index):
"""Returns the form at the given index, based on the rendering order"""
return ...
MyModelFormset = modelformset_factory(model=MyModel, formset=MyModelBaseFormset, queryset=...)
This approach is described in the Django documentation:
Iterating over the formset will render the forms in the order they
were created. You can change this order by providing an alternate
implementation for the __iter__() method.
Formsets can also be indexed into, which returns the corresponding
form. If you override __iter__, you will need to also override
__getitem__ to have matching behavior.
https://docs.djangoproject.com/en/dev/topics/forms/formsets/#django.forms.formsets.BaseFormSet
The example of implementing these methods is for example in this SO thread: modelformset __iter__ overloading problem.
if you didn't defined Formset, this is the "inline code" version:
FS=inlineformset_factory(ParentClass,ChildClass)
formset=FS(instance=parentobject,
queryset=parentobject.childobject_set.order_by("-time_begin")
)
Thanks to #john-peters answer above, for pointing me in the right direction. But here is a better way:
MyFormset(inlineformset_factory(...)):
def get_queryset(self):
return super(MyFormset, self).get_queryset().order_by('myfieldname')
This way you do not have to copy or mess with django's code and potentially cause breakage down the road.. just take the queryset that django gives you and override the ordering. I have used this in my own code, it works.
EDIT. After working on this a bit I realize that although it appears to work fine, it somehow messes up the logic in BaseInlineFormset.get_queryset(), resulting in duplicate database queries. However, in the hopes that someone will comment on this and correct it, I will leave it here. Meanwhile, I have another solution which DOES WORK and does not result in redundant queries.. as follows:
MyFormset(inlineformset_factory(...)):
def __init__(self, *args, **kwargs):
super(MyFormset, self).__init__(*args, **kwargs)
self.queryset = self.queryset.order_by('myfieldname')
This modifies the queryset at a safe time, before anything is done with it. In my code I am also doing .select_related() here, which greatly speeds up my large modelformsets!
Thanks to rantanplan's comment I found a solution. I could not use the method as described in the link above because I don't know what the queryset is going to be (this is a complex form with nested formsets).
Anyway, I found a solution by overriding the get_queryset method from Django's BaseInlineFormSet Class.
I copied it below, including my mod, in case googlers find it helpful.
def get_queryset(self):
'''
Copied this method from Django code and modified the ordering statement
'''
if not hasattr(self, '_queryset'):
if self.queryset is not None:
qs = self.queryset
else:
qs = self.model._default_manager.get_query_set()
# If the queryset isn't already ordered we need to add an
# artificial ordering here to make sure that all formsets
# constructed from this queryset have the same form order.
if not qs.ordered:
# MY MOD IS HERE:
# qs = qs.order_by(self.model._meta.pk.name)
qs = qs.order_by('order_index')
#/MOD
# Removed queryset limiting here. As per discussion re: #13023
# on django-dev, max_num should not prevent existing
# related objects/inlines from being displayed.
self._queryset = qs
return self._queryset
A more simpler way but it might not apply in every situation but if you can afford it just define ordering in models Meta class:
class ExampleModel(models.Model):
...
class Meta:
ordering = ("name", )
Formset will respect the queryset's ordering which will respect the Meta class variable's value.

How to update() a single model instance retrieved by get() on Django ORM?

I have a function which currently calls Models.object.get(), which returns either 0 or 1 model objects:
if it returns 0, I create a new model instance in the except DoesNotExist clause of the function.
Otherwise, I would like to update the fields in the pre-existing
instance, without creating a new one.
I was originally attempting to
call .update() on the instance which was found, but .update()
seems to be only callable on a QuerySets. How do I get around
changing a dozen fields, without calling .filter() and comparing
the lengths to know if I have to create or update a pre-existing
instance?
With the advent of Django 1.7, there is now a new update_or_create QuerySet method, which should do exactly what you want. Just be careful of potential race conditions if uniqueness is not enforced at the database level.
Example from the documentation:
obj, created = Person.objects.update_or_create(
first_name='John', last_name='Lennon',
defaults={'first_name': 'Bob'},
)
The update_or_create method tries to fetch an object from database
based on the given kwargs. If a match is found, it updates the
fields passed in the defaults dictionary.
Pre-Django 1.7:
Change the model field values as appropriate, then call .save() to persist the changes:
try:
obj = Model.objects.get(field=value)
obj.field = new_value
obj.save()
except Model.DoesNotExist:
obj = Model.objects.create(field=new_value)
# do something else with obj if need be
if you want only to update model if exist (without create it):
Model.objects.filter(id = 223).update(field1 = 2)
mysql query:
UPDATE `model` SET `field1` = 2 WHERE `model`.`id` = 223
As of Django 1.5, there is an update_fields property on model save. eg:
obj.save(update_fields=['field1', 'field2', ...])
https://docs.djangoproject.com/en/dev/ref/models/instances/
I prefer this approach because it doesn't create an atomicity problem if you have multiple web app instances changing different parts of a model instance.
I don't know how good or bad this is, but you can try something like this:
try:
obj = Model.objects.get(id=some_id)
except Model.DoesNotExist:
obj = Model.objects.create()
obj.__dict__.update(your_fields_dict)
obj.save()
Here's a mixin that you can mix into any model class which gives each instance an update method:
class UpdateMixin(object):
def update(self, **kwargs):
if self._state.adding:
raise self.DoesNotExist
for field, value in kwargs.items():
setattr(self, field, value)
self.save(update_fields=kwargs.keys())
The self._state.adding check checks to see if the model is saved to the database, and if not, raises an error.
(Note: This update method is for when you want to update a model and you know the instance is already saved to the database, directly answering the original question. The built-in update_or_create method featured in Platinum Azure's answer already covers the other use-case.)
You would use it like this (after mixing this into your user model):
user = request.user
user.update(favorite_food="ramen")
Besides having a nicer API, another advantage to this approach is that it calls the pre_save and post_save hooks, while still avoiding atomicity issues if another process is updating the same model.
As #Nils mentionned, you can use the update_fields keyword argument of the save() method to manually specify the fields to update.
obj_instance = Model.objects.get(field=value)
obj_instance.field = new_value
obj_instance.field2 = new_value2
obj_instance.save(update_fields=['field', 'field2'])
The update_fields value should be a list of the fields to update as strings.
See https://docs.djangoproject.com/en/2.1/ref/models/instances/#specifying-which-fields-to-save
I am using the following code in such cases:
obj, created = Model.objects.get_or_create(id=some_id)
if not created:
resp= "It was created"
else:
resp= "OK"
obj.save()
update:
1 - individual instance :
get instance and update manually get() retrieve individual object
post = Post.objects.get(id=1)
post.title = "update title"
post.save()
2 - Set of instances :
use update() method that works only with queryset that what would be returned by filter() method
Post.objects.filter(author='ahmed').update(title='updated title for ahmed')

django: Optimal way to select manytomany object with a particular attribute

I have a model app which has many to many association with users. Here is the code.
class app(models.Model):
users = models.ManyToManyField(UserProfile)
Now I want to check if a Django user with particular 'id' exits in this app's user list. After some hits and trials, I got to a solution.
def user_exists(self,user_obj):
userset = self.app.users.all()
for userprof in userset:
if userprof.user == user_obj:
return True
return False
How can I improve upon this?
That is very inefficient: it gets all related users and iterates through.
A ManyToManyField returns a queryset. So you can use the normal queryset filtering methods, to do all that in a single command:
return self.app.users.filter(user=user_obj).exists()
Note this uses the exists() method to return a bool directly from the database, rather than evaluating the actual objects.

Categories

Resources