Reputation: 1172
One of my Django websites has following database models: In Django App “common”:
class Collection(models.Model):
name = models.CharField(max_length = 255, unique = True)
_short_name = models.CharField(db_column="short_name", max_length = 32, blank=True)
class Particle(models.Model):
content = models.TextField(blank=False)
owner = models.ForeignKey(Collection)
order = models.IntegerField(null=True, blank=True)
In Django App “sitcom”:
class Media(models.Model):
name = models.CharField(max_length = 248)
_short_name = models.CharField(db_column="short_name", max_length = 32, blank=True)
capital = models.CharField(max_length = 1)
description = models.TextField(blank=True)
progress = models.CharField(max_length = 32, blank=True, null=True)
class Relation(models.Model):
name = models.CharField(max_length = 128)
_short_name = models.CharField(db_column="short_name", max_length = 32, blank=True)
description = models.TextField(blank=True)
parent = models.ForeignKey('self', blank=True, null=True)
order = models.IntegerField(blank=True, null=True)
particle = models.ForeignKey(Particle, blank=True, null=True)
media = models.ForeignKey(Media, blank=True, null=True)
In short, model class Relation has 3 foreign keys to other tables. The problem is, when I use Django Admin to change a single Relation, the page (change_form) loads rather slowly. Later, I changed model class Relation as following:
class Relation(models.Model):
name = models.CharField(max_length = 128)
_short_name = models.CharField(db_column="short_name", max_length = 32, blank=True)
description = models.TextField(blank=True)
order = models.IntegerField(blank=True, null=True)
parent_id = models.IntegerField(blank=True, null=True)
particle_id = models.IntegerField(blank=True, null=True)
media_id = models.IntegerField(blank=True, null=True)
The modification changed Foreign Keys to IntegerFields, so it disabled some of the magics inside Django ORM system, and now the change form page loads really fast. My question is, what is the “disabled magics inside django orm”? what has the potential to cause the problem?
Upvotes: 26
Views: 16738
Reputation: 656
Django now has support for autocomplete_fields which uses Select2 for input. You can configure as follows:
from django.contrib import admin
class RelationAdmin(admin.ModelAdmin):
autocomplete_fields = ('Media','Particle',)
admin.site.register(Relation, RelationAdmin)
Upvotes: 5
Reputation: 14255
Another possibility:
As mentioned by @yuji-tomita-tomita elsewhere on this page, the admin change_form
renders a <select>
element for a ForeignKey
field.
This is done using the Select
widget.
By default, each <option>
in the <select>
element has a text label which is based on the __str__()
method for the model that the ForeignKey
points to. This can be seen in the source for forms.models.ModelChoiceField
.
So, if your Model.__str__()
method uses related fields, you'll get additional database queries when the admin form populates the select options.
For example, based on the OP, suppose we would have this:
class Particle(models.Model):
...
owner = models.ForeignKey(Collection, on_delete=models.CASCADE)
...
def __str__(self):
return 'particle owned by {}'.format(self.owner.name)
Then the admin would need to query the database to get the related Collection.name
value for each Particle
option.
The admin does not do this efficiently.
If you have many Particle
objects, that can become very expensive very quickly.
Upvotes: 1
Reputation: 111265
In my case the slowness was caused primarily by the outdated django-debug-toolbar
(v1.7). debug_toolbar.middleware.DebugToolbarMiddleware
would cause the admin page to literally take 20 minutes to load if it contained a ForeignKey field with a couple hundred choices. Upgrading the toolbar to v1.8 solved everything. Maybe this helps someone.
Upvotes: 1
Reputation: 5191
If a ForeignKey Model has too many records it might freeze your edit form. The best way to start, is to limit the fields that should be displayed on the form and gradually/one-by-one add other fields, checking which field makes the form slow.
from django.contrib import admin
class RelationAdmin(admin.ModelAdmin):
fields = ('name',)
admin.site.register(Relation, RelationAdmin)
Then after adding a field that causes the problem, e.g. Media, the form would freeze again. Therefore, if you still need this field on the form you can use Vishal Shah's answer with raw_id_fields = ('Media', )
Upvotes: 0
Reputation: 1107
In admin.py
from django.contrib import admin
class RelationAdmin(admin.ModelAdmin):
raw_id_fields = ('Media','Particle',)
admin.site.register(Relation, RelationAdmin)
This brings up a nice little UI element in the form and considerably improves performance since it doesn't have to load a huge number of options in the select box.
Upvotes: 43
Reputation: 672
It is not the magic of django Orm. It is magic of Form. When you create a Foreign key in Model, then in ModelForm, a ModelChoiceField creates which has all choices of ForeignKey Model. And django Admin use all the properties of Form to create HTML. So use this code.
from django import forms
class RelationForm(forms.ModelForm):
parent = forms.ChoiceField(required=False,
choices=Relation.objects.values_list('id', 'name'))
particle = forms.ChoiceField(required=False,
choices=Particle.objects.values_list('id', 'content'))
media = forms.ChoiceField(required=False,
choices=Media.objects.values_list('id', 'name'))
class Meta:
model = Relation
In Admis Site
from django.contrib import admin
class RelationAdmin(admin.ModelAdmin):
form = RelationForm
model = Relation
You can also cache the choices pass in a form.
Upvotes: 19
Reputation: 118458
I'm willing to bet the issue is due to your ForeignKey
. By default, django renders a <select>
element for every foreign key.
If you have thousands of rows, this easily starts to bloat your HTML / DOM and I've noticed browsers starting to crap out at 20k items rendered in a <select>
tag.
To fix it, look into overriding your admin form and not using the default widgets.
Upvotes: 12