Different queryset optimisation for list view and change view in Django Admin - django-admin

The get_queryset method for an admin model can be overridden; I use it to select/prefetch objects that are OneToOneFields or ManyToManyFields. However, the list view for my model shows only concise information while the change view contains many more objects. It would not make sense to prefetch ManyToManyField relations in the list view if these will not be displayed anyway.
Sample model:
class Location(TimeStampedModel):
owner = models.ForeignKey('Profile', on_delete=models.CASCADE)
postcode = models.CharField("postcode", max_length=11, blank=True)
tenants = models.ManyToManyField('Profile', blank=True)
Sample admin model:
#admin.register(Location)
class LocationAdmin(admin.ModelAdmin):
list_display = ('owner', 'postcode')
fields = ('owner', 'postcode', 'tenants')
filter_horizontal = ('tenants',)
def get_queryset(self, request):
qs = super(LocationAdmin, self).get_queryset(request).select_related('owner__user')
qs = qs.prefetch_related('tenants')
return qs
Is it possible to define different optimisations for the queryset returned for the list view of the model and the queryset returned for the change/add view of the same model?
That is, in the sample admin model above, the qs.prefetch_related('tenants') line will be relevant for the change/add view only?

The easiest way to achieve this is to use the request.resolver_match attribute which can be used to work out which view you are executing. The following is slightly hacky/fragile (it is essentially using some internals), but works:
class LocationAdmin(admin.ModelAdmin):
list_display = ['owner', 'postcode']
fields = ['owner', 'postcode', 'tenants']
filter_horizontal = ['tenants']
def get_queryset(self, request):
qs = super(LocationAdmin, self).get_queryset(request)
qs = qs.select_related('owner__user')
if request.resolver_match.func.__name__ == 'change_view':
qs = qs.prefetch_related('tenants')
return qs
You should also consider whether you need this and whether it will actually work. The change view shows only one main object, which means that the N+1 problem for a list of objects often does not apply. In addition, the queries for inlines and the widgets for foreign key and many-to-many fields may not use the querysets you provide from get_queryset. For this case, testing with Django 1.10.2, the prefetch_related call did not reduce the number of queries the 'change' view executed. The 'add' view did not use the get_queryset method at all.
See complete demo app at https://github.com/spookylukey/djangoadmintips/tree/master/queryset_optimization

Related

Django admin custom form in create,update,view based on parent-child table relation

I have one table called product and it is having field category based on category i will have to save relevant information in correct category table
table like below
class Product(models.Model):
id = models.CharField(primary_key=True, max_length=36)
name = models.CharField(max_length=255)
code = models.CharField(unique=True, max_length=31)
category = models.ForeignKey('ProductCategory', models.DO_NOTHING, db_column='category')
class CategoryOneProduct(models.Model):
id = models.CharField(primary_key=True, max_length=36)
product = models.OneToOneField('Product', models.DO_NOTHING, unique=True, blank=True, null=True)
traffic_code = models.IntegerField()
routing = models.IntegerField(blank=True, null=True)
i want to build admin UI which will display field from category table based on category and same thing i will have to achieve in create ,update , display
is it possible to do it ?
Finally after lots of searching i found Stackedinline and tabularinline facility provided by Django which is giving above ability to show the required data

Grails/Gorm: how to filter a list of domain objects without affecting the database

Say we have something like the standard Book domain object and bookCategory object. In my controller I want to return a subset of list of books to the view. That subset is not achievable using a find query. When I try to filer the return object, it deletes relationships from the database!
I tried this:
class BookCategory{
String name
static hasMany = [books:Book]
}
class Book{
String title
}
def myController() {
def categories
categories = BookCategory.list()
def user = getCurrentUser()
categories.each { category ->
category.books.removeAll { book ->
!isBookBannedForThisUser(book.title, user)
}
[bookCategories: categories]
}
}
The problem is that it permanently removes these books from the categories for all users from the database!!!
I tried putting the method in a service and using a readonly transaction, but this did not help.
I assume that even if I copy all the categories and books into new list, they will still update the DB as they will still have the book IDs (which I need)
Saving to the database when you dont say save() is very dangerous. is there a way to disable this feature completely?
There is a fundamental flaw in your approach. Do not modify your domain instances if you don't intend to have the changes persisted. Doing so is going to cause you headaches.
Your domain model is suppose to be your system of record. Any changes to it are suppose to be persisted.
If you need to gather up data and manipulate it without having it reflected in your domain model then use a DTO (data transfer object) or similar pattern.
Simply calling .discard() will discard the changes you have made from being persisted when the session automatically flushes.
Instead of working against the framework, and disabling behavior, change your approach to be correct.

Rails/Mongoid best way to save sortable ordered list

I've got a sortable list of players pulled from my db. Users can rank them in order of their preference.
What's the best way to store that order in the db associated with each player's preference and retrieve player stats back in that same order?
Right now I'm retrieving by #playersdraftlist = #draftlist.map { |id| Player.find(id) }, but this hits the db many times.
So, currently, I have a User model and a Draft model. The draft only has an associated user_id.
1) Would it be better to just store the draftees as a user field instead of a separate model?
2) Is there a better way to store a list like this when I need to retain dupes and order?
Structure:
PlayerDraft model has an associated user_id:
draftees: [
"52f4fd9f52e39bc0c15674ea", #Peyton
"52f4fd9f52e39bc0c15674eb", #Tom
"52f4fd9f52e39bc0c15674ea" #Peyton
],
user_id: "52f581096b657612fe020000"
#draftees = Draft.find(user_id = current_user._id).first.draftees will return the draftees array.
Calling Player.find(#draftees) will remove the duplicates and order by _id.
Edit
Here is my poor solution:
def index
#players = Player.all
if current_user.draft_list.draftees == [""]
#playersdraftlist = ""
else
# Get all picks for current user
#picks = current_user.draft_list.draftees
#draft = Player.find(#picks)
#playersdraftlist = #picks.map { |id| Player.find(id) }
end
end
The way to do this is to use the identity map in mongoid.
There are more details on this here: http://mongoid.org/en/mongoid/docs/identity_map.html
But the short and simple way to do this is in your mongoid.yml set the following property:
identity_map_enabled: true
Then search for the following: Player.find(#draftees)
And this will return your draftees in the order of the array that you passed in. One caveat is that it won't return duplicates so your #draftees array above if used would return only two values (the order is based on the first appearance of the id, so it will return the player with id "52f4fd9f52e39bc0c15674ea" first and then the player with id "52f4fd9f52e39bc0c15674eb"). So you will have to recreate the duplicates via some sort of abstraction.
Here is one possible way to do this, and it was inspired by a comment posted by #muistooshort below:
In your User class add the following function
def get_draftee_list
players = Player.find(self.draftees).each_with_object({}) { |p, h| h[p.id] = p }
return self.draftees.map { |id| players[Moped::BSON::ObjectId.from_string(id)] }
end
Note: This was tested on Mongoid 3
This should solve your problem since each call to raw_data.find(id) is searching the result set from your the initial query which is stored in memory and not making another query to the db

mapping table fields with grails

I have a business requirement where I need to map several pair of tables based on pre-defined set of rules. The tables contain the same data but have different schemas. For example: field name in table abc is same as field fullname in table def. My business requirement needs me to map several tables like that. I thought of using grails 'DRY' principle.
To try out with an example, I created two domain classes Abc and Def in Grails and a third class called AbcDefMapping. Then in Controller template I created a method called convert where I split AbcDefMapping string into Abc, Def and Mapping strings. Now I want to call method Abc.list() in my generated controller. How do I do that? (Edit: I solved this one, please look at my other question i.e. if there is a better way to do this)
This is my code:
def convert(){
def cn = "${className}"
def lis = []
def str = ""
for (i in cn){
if (i == i.toUpperCase() && str!=""){
lis.add(str)
str = i
}
else{
str = str +i
}
}
lis.add(str)
def inputs = \${lis[0]}.list() // lis[0] = "Abc"
}
I want the inputs to go as Abc.list() in the generated code.
Otherwise, could you please tell me a better way of achieving what I want to do here?

Django List Admin allow sort on Reverse Foreign Key

When Creating your own Admin Class for django.contrib.comments, I want to allow sorting of flagged comments. I can a custom method to the admin class that return comment.flags.count(), but not sure I get the admin to sort by this.
The problem I see is that its CommentFlag model that contains the foreign key to Comment model.
Anybody know a solution with out changing django.contrib.comments?
def queryset(self, request):
qs = super(CommentsAdmin, self).queryset(request)
return qs.extra(select={
'flag_count': 'SELECT COUNT(*) FROM django_comment_flags WHERE django_comment_flags.comment_id = django_comments.id'
},)
def flags(self):
return self.flags.count()
flags.admin_order_field = 'flag_count'
Its a bit messy, but Django complains that flag_count is not an accessor for the Model Comment when you register the admin.

Resources