optimize SerializerMethodField in a serializer - django

I have a serializer like that :
class UserSerializerDetail(UserSerializer):
active_projects = serializers.SerializerMethodField()
def get_active_projects(self, obj):
return obj.projects.filter(project__active=True).count()
the problem I am having here is that the SerializerMethodField is calling an extra filter and I wish to use select related to create a join and overcome another hit in the database.
The second issue is how can I select_related a count?

I assume the you meant user have many projects. 1 to many relationship.
The best is the combination of prefetch_related with aggregation (select_related mostly used for 1 to 1 relationship).
example:
from django.db.models import Count, Q
projects = User.objects.prefetch_related('projects').annotate(active_projects=Count('projects', filter=Q(projects__active=True)))
Then your users now have active_projects field.

Related

How I can get attribute from queryset using prefetch_related in Django?

I have following model and extracted queryset using prefetch_related as below.
queryset = Light.objects.filter(
certificate__name="A").prefetch_related('zone__namingzone'
)
From this queryset, I want to get following data set.
{"naming1":lpd1,"naming2":lpd2...}
However, when I try to extract attribute from queryset as below, I get create_reverse_many_to_one_manager
for i in queryset:
print (i.zone.namingzone)
What I want to get is naming attribute in naming table. Could anyone tell me how I can extract this?
models.py
class Certificate(models.Model):
name=models.CharField(max_length=20)
class Zone(models.Model):
zone=models.CharField(max_length=20)
class Light(models.Model):
certificate=models.ForeignKey(Certificate, on_delete=models.CASCADE,related_name='certificate')
zone=models.ForeignKey(Zone, on_delete=models.CASCADE,related_name='lightzone')
lpd=models.IntegerField()
class Meta:
unique_together = (('certificate', 'zone'),)
class Naming(models.Model):
zone=models.ForeignKey(Zone, on_delete=models.CASCADE,related_name='namingzone')
naming=models.CharField(max_length=20)
When you traverse a FK in reverse, you end up with a manager, and multiple items on the other side. So i.zone.namingzone in your for loop is a manager, not a NamingZone. If you change your print loop to:
for i in queryset:
print (i.zone.namingzone.all())
You should see all the naming zones for your item. You can extract the naming field from each NamingZone from the queryset as follows:
queryset.values('zone__namingzone__naming')
You probably want to extract a few other fields from your Light model, like the lpd for instance:
queryset.values('lpd', 'zone__namingzone__naming')
You might have a same ldp several times, as many times as it has naming zones.

prefetch_related and Prefetch object issue, filtering thru reverse Foreign Key

I have 2 models, company and Product.
class Product(Meta):
company = models.ForeignKey(Company, related_name='products', on_delete=models.CASCADE)
form the database I'm trying to get the Company data and the corresponding products.
From the products I want to get only the name and to be ordered descending by updated_at, created_at.
I'm working with Prefetch object and prefetch_related and definitively I have multiple misunderstandings how they work.
def get_queryset(self):
qs = Company.objects.prefetch_related(
Prefetch('products', queryset=Product.objects.only('name').order_by('-updated_at', '-created_at'))).get()
return qs
The error that I receive is:
get() returned more than one Company
Because I closed the prefetch_related method/function with ))) :
I thought get() will act over the Company object and get it using the pk/slug from the url(as get do by default in DetailView). Seems that is not the case.
I'm already using 'products' the related name in the Prefetch object, why in queryset is necessary to tell again the model queryset=Product.objects.... ?
I was looking at the following example in django documentation:
Question.objects.prefetch_related(Prefetch('choice_set')).get().choice_set.all()
If there is 'choice_set' in Prefetch object why is called at the end choice_set.all() ?
Isn't Django attached to the quesryset in prefetch_related the products to the queryset (question.choice_set) ?
I think my problem is that I don't understand the order of execution, and I'm confused how methods are chained, even if are closed by ')'
queryset.get() will only work if the queryset has a single object. If it contains zero or more than one objects, you'll get an error.
You should return a queryset from the get_queryset object. In the class based view, the code that filters on the pk/slug is in get_object.
The prefetch_related method is useful if you want to fetch the products for multiple countries. The way that the Django docs use get() is confusing in my opinion - if the queryset has a single item then prefetch_related is over-complicated.
If you have a single company, then there is no advantage, and the code will be simpler if you fetch the countries separately, e.g. in get_context_data.
def get_context_data(self, **kwargs):
context = super(MyView, self).get_context_data(**kwargs)
context['products'] = Product.objects.filter(company=self.object).Product.objects.only('name').order_by('-updated_at', '-created_at')))
return context
I've removed the only('name') call. It's an optimisation that you probably don't need.
If you really want to use prefetch_related, then remove the get().
qs = Company.objects.prefetch_related(
Prefetch('products', queryset=Product.objects.order_by('-updated_at', '-created_at')))
By specifying the queryset above, you are able to change the order (you could filter it if you like). If you don't want to customize the queryset you can simply do:
Company.objects.prefetch_related('products')
When you use Question.objects.prefetch_related(...), the queryset is still a list of questions. You need to call choice_set.all() on the individual instances to access their choices. This won't cause any additional queries, because Django has already prefetched the choices.
queryset = Question.objects.prefetch_related(Prefetch('choice_set'))
for question in queryset:
print(question) # the question
print(question.choice_set.all()) # the related choices

Django - Loading Many-To-Many relationship admin page is so slow

(Django 1.8) I have a table which has 4 many-to-many relationship to other tables.
Two of these tables have so many entries and that is causing the admin page to load very slowly because it is trying to load all the entries in the lists.
Is there a way to avoid the internal admin page query for loading all the entries of the big tables to speed up the admin page load?
I think the best way is to only list the selected values but I'm not sure how.
I'm not sure how to use limit_choices_to in here:
class Data(models.Model):
pass # stuff here
class Report(models.Model):
data= models.ManyToManyField(Data)
I also tried adding this to my admin.py but it did not help at all. It's not limiting for some reason:
def queryset(self, request):
qs = super(MyModelAdmin, self).queryset(request)
if len(qs) > 10:
qs = qs[:10]
return qs
If you still want to use limit_choices_to, then please refer to the docs. You basically just supply the filters in a dictionary object.
To speed up the admin, my suggestions include:
1. Using raw_id_fields in your ModelAdmin. This gives you a little searchbox instead of a selectbox and avoids the overhead of listing all related objects.
2. If you are handling forward ForeignKey relationships, you can use list_select_related in your ModelAdmin as well. In your case, you are handling many-to-many relationships, so you can try overriding the get_queryset method of the ModelAdmin, and use prefetch_related like in the code below.
from django.contrib import admin
class TestModelAdmin(admin.ModelAdmin):
def get_queryset(self, request):
test_model_qs = super(TestModelAdmin, self).get_queryset(request)
test_model_qs = test_model_qs.prefetch_related('many-to-many-field')
return test_model_qs
If you really like to get your hands dirty, I highly recommend you use django-debug-toolbar. It really gives you visibility on how many and what SQL statements are being run. If you can read SQL, you can deduce what you need to input to select_related and prefetch_related.

prefetch_related for multiple Levels

If my Models look like:
class Publisher(models.Model):
pass
class Book(models.Model):
publisher = models.ForeignKey(Publisher)
class Page(models.Model):
book = models.ForeignKey(Book)
and I would like to get the queryset for Publisher I do Publisher.object.all().
If then want to make sure to prefetch I can do:
Publisher.objects.all().prefetch_related('book_set')`
My questions are:
Is there a way to do this prefetching using select_related or
must I use prefetch_related?
Is there a way to prefetch the
page_set? This does not work:
Publisher.objects.all().prefetch_related('book_set', 'book_set_page_set')
Since Django 1.7, instances of django.db.models.Prefetch class can be used as an argument of .prefetch_related. Prefetch object constructor has a queryset argument that allows to specify nested multiple levels prefetches like that:
Project.objects.filter(
is_main_section=True
).select_related(
'project_group'
).prefetch_related(
Prefetch(
'project_group__project_set',
queryset=Project.objects.prefetch_related(
Prefetch(
'projectmember_set',
to_attr='projectmember_list'
)
),
to_attr='project_list'
)
)
It is stored into attributes with _list suffix because I use ListQuerySet to process prefetch results (filter / order).
No, you cannot use select_related for a reverse relation. select_related does a SQL join, so a single record in the main queryset needs to reference exactly one in the related table (ForeignKey or OneToOne fields). prefetch_related actually does a totally separate second query, caches the results, then "joins" it into the queryset in python. So it is needed for ManyToMany or reverse ForeignKey fields.
Have you tried two underscores to do the multi level prefetches? Like this: Publisher.objects.all().prefetch_related('book_set', 'book_set__page_set')

django prefetch_related id only

I'm trying to optimise my queries but prefetch_related insists on joining the tables and selecting all the fields even though I only need the list of ids from the relations table.
You can ignore the 4th query. It's not related to the question.
Related Code:
class Contact(models.Model):
...
Groups = models.ManyToManyField(ContactGroup, related_name='contacts')
...
queryset = Contact.objects.all().prefetch_related('Groups')
Django 1.7 added Prefetch objects which let you customise the queryset used when prefetching.
In particular, see only().
In this case, you'd want something like:
queryset = Contact.objects.all().prefetch_related(
Prefetch('Groups', queryset=Group.objects.all().only('id')))