Getting model instance when using QuerySet.values() - django

Let's say I have two models, one referencing the other:
class Shelf(models.Model):
pass
class Book(models.Model):
shelf = models.ForeignKey(Shelf)
I'd like to use values() on a QuerySet of Book instances:
In [1]: Book.objects.create(shelf=Shelf.objects.create())
Out[1]: <Book: Book object>
In [2]: Book.objects.values()
Out[2]: [{'id': 1, 'shelf_id': 1}]
The problem is that the returned dictionaries contain just the primary keys of the related Shelf instances instead of the instances themselves. Is there a way to get the actual instances in a single query? E.g.:
In [2]: Book.objects.values()
Out[2]: [{'id': 1, 'shelf': <Shelf: Shelf object>}]
The reason I'm using values() is so that I can merge two QuerySets for different models which I want to sort and render into a single table in a view.

You can get a very similar output by a generator with .get() like this:
(Shelf.objects.get(id) for id in Book.objects.values_list("shelf"))

According to the docs:
If you have a field called foo that is a ForeignKey, the default
values() call will return a dictionary key called foo_id, since this
is the name of the hidden model attribute that stores the actual value
(the foo attribute refers to the related model).
I don't think it is possible with the default values() implementation. You can create a custom manager and override the values() method to create a multi-level dictionary for each ForeignKey of the model.

Related

is django queryset.distinct() necessary when you are not using queryset.values()?

I'm trying to wrap my head around the distinct method of the django queryset class, the thing I'm having trouble understanding is when to actually use it. note that I'm not talking about the "distinct on" feature of postgres.
I understand that each model instance has to have an id property and ids are unique so when you are querying model instances it's not really possible to get duplicate models/rows. so is the following use of distinct redundant?
User.objects.distict()
I know that one correct use of the distinct method is when you use the values method and you don't select the id, you might have values that are duplicate and you could use distinct in these scenarios.
is there any other scenario where one might need to use distinct (e.g. when using select_related or prefetch_related)?
A common use of distinct() is to eliminate duplicates when you filter across multiple tables
Consider the following models
class Parent(models.Model):
pass
class Child(models.Model):
parent = models.ForeignKey(Parent, on_delete=models.CASCADE)
value = models.IntegerField()
Populated with the following data
p = Parent.objects.create()
Child.objects.create(parent=p, value=1)
Child.objects.create(parent=p, value=2)
Child.objects.create(parent=p, value=3)
If you filter a Parent queryset by the related value column then you will get a duplicate for every Child that matches the filter
Parent.objects.filter(child__value__gt=0)
# <QuerySet [<Parent: Parent object (1)>, <Parent: Parent object (1)>, <Parent: Parent object (1)>]>
Parent.objects.filter(child__value__gt=1)
# <QuerySet [<Parent: Parent object (1)>, <Parent: Parent object (1)>]>
But if you use distinct() then the duplicates will be eliminated
Parent.objects.filter(child__value__gt=0).distinct()
# <QuerySet [<Parent: Parent object (1)>]>

Adding List of Related IDs to returned values in a Django Queryset

According to the example in Django's Docs, I can obtain a list of reverse related objects like this:
Blog.objects.values('entry__id')
Which would presumably give me the ids of all related Entry objects. If I wanted this information in addition to all the other information returned in a normal .values() call, is there a short way to add it on? Or do I need to explicitly list all of the models' fields in .values() to have the reverse keys included in the output?
You can use annotate to add extra fields from foreign keys
from django.db.models import F
blogs = Blog.objects.all().annotate(entry_id=F('entry__id'))
for blog in blogs:
print(blog.entry_id)

Getting the primary key of a ForeignKey field without querying the database again in Django

I have the following models:
class Model1(models.Model):
...
class Model2(models.Model):
...
model1 = models.ForeignKey(Model1)
Now, lets say I have an object of Model2 with pk=241 which is related to another object of Model1 with pk=102. I am querying them as follows:
model2 = Model2.objects.get(pk=241)
Now, if I want the pk of the referenced Model1 object. I do the following:
model2.model1.pk
This should not query the database again according to what I understand about tables, but if I run the following:
from django.db import connection
connection.queries
I get a list of 2 queries. Why do I need to query my database again to only get the primary key of my related object? Is there a way to avoid doing this?
I am aware of select_related(), however, what if I want to call the Model1 objects pk in the save() method of the Model2 class?
Moreover, is select_related() required even if I want to just retrieve the pk of the related object and nothing more?
You can access the underlying field without a db hit.
model2.model1_id
You don't need select_related here, since you are not actually accessing the related object.

How can I use Django Querysets and Q() to compare against objects of the same model type?

I have a Django model named MyModel. m is an instance of MyModel.
I would like to use Django QuerySets to find all the instances of MyModel that are not m.
How to do it? This doesn't work:
MyModel.objects.filter(~Q(m))
It seems you can query against attributes of MyModel using Q(). However I don's see how I can use Q to include/exclude instances of MyModel itself. Is this doable? If so, how? If not, what is the most efficient and elegant way to get at what I'm trying to do?
Use the model's pk (primary key) field:
MyModel.objects.exclude(pk=m.pk)
To exclude another model n also (additional question asked in comment below), you could do:
MyModel.objects.exclude(pk=m.pk).exclude(pk=n.pk)
More generally, to exclude a list of instances list_of_instances, use the __in syntax:
MyModel.objects.exclude(pk__in=[instance.pk for instance in list_of_instances])

Django: How to get objects instead of just foreign keys in annotate?

Annotate on a foreign key returns foreign keys. How can I get the objects themselves from the query?
In the following example 'the_user' is a foreign key in the "Vote" model:
Vote.objects.values('the_user').annotate(vote_count=Count('the_user')).order_by('-vote_count')
It would return
[{'the_user': 4, 'vote_count': 12} , {'the_user': 6, 'vote_count': 2}]
But I need the user objects themselves.. Not the ids
values() does exactly that - returns values, use usual queryset
Vote.objects.annotate(vote_count=Count('the_user')).order_by('-vote_count')
Then each object in that queryset will have vote_count attribute.
You should always query on the model which objects you want to get back in the resulted query set, so in your case:
qs = User.objects.annotate(vote_count=Count('vote')).order_by('-vote_count')
The reason you are getting the foreign key and not the user objects is because of your use of values(), values returns a dict and not the model object. Use a select_related instead of values and that will resolve your problem.