django admin additional field on changelist page - django

Well, I believe this is a general question.
Let's say I have a model, called Book, and another model called Author. And Author is the foreign key of Book.
Easy, ha?
On BookAdmin page, I want to show Author of Book. So In the list_display field I have:
list_display = ('book_name', 'author'.....)
Then I use django-debug-toolbar to check the number of queries executed when loading the page.
There are about 100+ queries have been executed. (I have 100 book entries on same page)
If I don't include 'author', there are only 5 queries (something like that).
I know that the extra queries come from foreign key.
My question is that is there anyway to reduce the number of queries being executed when we are dealing with foreign key?
I don't want to perform any complicate operation. I just want the call the unicode() method in that foreign key model.
Thanks in advance

Well, I found it out myself.
The way to do it is to override the queryset() in BookAdmin class.
Like:
def queryset(self, request):
return Book.objects.all().select_related('author')
That is it.
Django will load the Author in the same time it loads Book. So there will be only on database hit rather than over 100 hits.

Related

Django adding the values of reverse foreign key as field while returning

I have two models. One is Task model and other is reward model.
class Task(models.Model):
assigned_by = models.CharField(max_length=100)
class Reward(models.Model):
task = model.ForeignKey(Task)
Now I want to return a queryset of Task along with the reward field in it. I tried this query.
search_res = Task.objects.annotate(reward='reward').
I got this error: The annotation 'reward' conflicts with a field on the model.
Please tell how to solve this. I want an field reward in each task object.
To reach your goal with the actual models I would simply use the relations along with the task.
Let's say you have a task (or a queryset of tasks):
t = Task.objects.get(pk=1)
or
for t in Task.objects.all():
you can get the reward like this:
t.reward_set.first()
Take care of exception in case there's no reward actually linked to the task.
That incurs in quite an amount of queries for large datasets, so you could optimize the requests toward the DB with select_related or prefetch_related depending on your needs. Look at the Django docs for that.

Filtering a Django queryset once a slice has been taken

In a django forum I maintain, I hell-ban users who're abusive, for 4 days. In the "Home page" of the forum, I display everyones comments, but exclude comments by people who're hell-banned. It goes like this:
def get_queryset(self):
if self.request.user_banned: #if user is hell-banned
return Link.objects.order_by('-id')[:120]
else: #if user is not hell-banned
global condemned
queryset = Link.objects.order_by('-id').exclude(submitter_id__in=condemned)[:120]
return queryset
The above is a get_queryset method of a ListView. Notice how hell-banned users can't tell their comments are being excluded from the site (the point of the hell ban). condemned is a list containing primary keys of hell-banned users.
Now I want to optimize the above by slicing first and excluding banned people later. I'm trying to do that via:
def get_queryset(self):
if self.request.user_banned: #if user is hell-banned
return Link.objects.order_by('-id')[:120]
else: #if user is not hell-banned
global condemned
queryset = Link.objects.order_by('-id')[:120]
queryset = queryset.exclude(submitter_id__in=condemned)
return queryset
This unfotunately gives me the error:
Cannot filter a query once a slice has been taken.
What alternatives do I have? Need the most efficient solution I can find, since performance is key. I'm on Django < 1.8. Thanks in advance.
Firstly, Django doesn't let you filter after a slice, because in the underlying SQL, you can't easily limit the results and then filter with where.
Doing the filtering then the slice probably not a problem anyway. Note that querysets are lazy, so Django will only ever fetch 120 objects from the db.
You'll need to do some benchmarking to find out whether the exclude is really slowing you down. You could test whether the query with the exclude and slice is noticeably slower than the query just with the slice.
If you find that the exclude is slow, you could filter in Python
comments = [c for c in comments if c.submitter_id not in condemned].
Note that you may end up with fewer than 120 comments this way.
Another option is to add a condemned flag to the Submitter model, then change the query to .exclude(submitter__condemned=True). This might be faster than the current .exclude(submitter_id__in=condemned).
You should also check that your database has indexes for the submitter_id field. Since it's a foreign key, it probably does.

Django-rest-framework serializer makes a lot of queries

Let's say I have this model:
class Place(models.Model):
....
owner = ForeignKey(CustomUserModel)
....
And I have this DRF serializer that returns a list of Places (the view calling it uses DRF's generics.ListAPIView class):
class PlaceSerializer(serializers.ModelSerializer):
owner = UserModelSerializer() # Gets only specific fields for a place owner
class Meta:
model = Place
The problem is, when the serializer gets a query that returns, let's say... 50 places, I can see (in connection.queries) that a query is being made for each owner foreign key relation, which sums up to a lot of queries. This of course has a big impact on performance.
Also important to mention is that for the view calling the serializer I had get_queryset() return only Places that are in a certain distance from a center point using a custom query. I used Django's extra() method for that.
I have tried using select_related and prefetch_related with the query mentioned above, but it doesn't seem to make any difference in terms of queries being made later on by the serializer.
What am I missing?
select_related will work as expected with serializers.
Make sure you're setting that in the 'queryset' attribute on the view if you're using the generic views.
Using select_related inside 'get_queryset' will work too.
Otherwise the only thing I can suggest is trying to narrow the issue down with some more debugging. If you still believe there's an issue and have a minimal example that'll replicate it then raise the issue as a ticket, or take the discussion to the mailing list.

In what situations are Django formsets used?

I am a beginner with Django. In what situations are Django formsets used? (In real applications.)
Please give some examples.
According to the documentation:
A formset is a layer of abstraction to working with multiple forms on the same page. It can be best compared to a data grid.
So any time you want to have multiple instances of a particular form displayed on a page for creation or updating. An example of this might be a Phonebook. Each Form is a single entry in the phonebook, but a Formset of Phonbook forms will allow you to create or edit multiple entries in one go.
One case is when ModelA have a foreign key to ModelB. you can say that one ModelB has many ModelA, so lets say that ModelB is a Invoice and ModelA is the ProductItem, one invoice has one or many product items.
In the practice now you can make a basic formset like that: http://help.mailchimp.com/img/addafieldoption.jpg where you can add more product items to a invoice.

Making a fairly complex Django model method sortable in admin?

I have a reasonably complex custom Django model method. It's visible in the admin interface, and I would now like to make it sortable in the admin interface too.
I've added admin_order_field as recommended in this previous question, but I don't fully understand what else I need to do.
class Book(models.Model):
id = models.IntegerField(primary_key=True)
title = models.CharField(max_length=200)
library_id = models.CharField(max_length=200, unique=True)
def current_owner(self):
latest_transaction = Transaction.objects.filter(book=self)[:1]
if latest_transaction:
if latest_transaction[0].transaction_type==0:
return latest_transaction[0].user.windows_id
return None
current_owner.admin_order_field = 'current_owner'
Currently, when I click on the current_owner field in the admin interface, Django gives me
FieldError at /admin/books/book/
Cannot resolve keyword 'current_owner' into field
Do I need to make a BookManager too? If so, what code should I use? This isn't a simple Count like the example in the previous question, so help would be appreciated :)
Thanks!
The Django admin won't order models by the result of a method or any other property that isn't a model field (i.e. a database column). The ordering must be done in the database query, to keep things simple and efficient.
The purpose of admin_order_field is to equate the ordering of a non-field property to the ordering of something that is a field.
For example, a valid values current_owner.admin_order_field could be id, title or library_id. Obviously none of these makes sense for your purpose.
One solution would be to denormalise and always store current_owner as a model field on Book; this could be done automatically using a signal.
You can't do this. admin_order_field has to be a field, not a method - it's meant for when you have a method that returns a custom representation of an underlying field, not when you do dynamic calculations to provide the value. Django's admin uses the ORM for sorting, and that can't sort on custom methods.