Je pensais que ces astuces étaient très connues, mais quand j’en ai eu besoin, j’ai galéré pour mettre la main dessus. Du coup blogage, blogation, blogitude.
L’admin Django permet de faire automatiquement un listing des objets voulus afin de les modifier. Si un modèle a une ForeignKey et qu’on définit le champ en list_editable
, Django va pondre un drop down pour choisir parmi les relations possibles.
Or, le framework fait une requête pour chaque objet de la liste. Si vous avez deux ForeignKeys sur votre modèle et 20 objets (valeur par défaut) dans la liste, ça vous fait donc 40 queries, juste pour afficher ces deux drop down. Doh.
On peut néanmoins forcer Django à cacher le résultat de la première requête, réduisant ainsi le compte à 1, avec cette étrange astuce d’une mère au foyer (consultants hate her !) :
class VotreClasseAdmin(admin.ModelAdmin)
# Cette méthode est appelée pour créer le champ de formulaire de pour
# chaque objet
def formfield_for_dbfield(self, db_field, **kwargs):
request = kwargs['request']
formfield = super(ProductPageAdmin, self).formfield_for_dbfield(db_field, **kwargs)
# Si le champ est editable dans la liste
if db_field.name in self.list_editable and hasattr(formfield, 'choices'):
# On tente de récupérer la query en cache
cache_attr_name = '_%s_cache' % db_field.name
choices_cache = getattr(request, cache_attr_name)
if choices_cache is not None:
formfield.choices = choices_cache
# Pas de cache, on force Django à faire la query en accédant
# au descripteur .choices et on met le resultat
# en cache
else:
setattr(request, cache_attr_name, formfield.choices)
# On retourne le champ de notre formulaire avec sa valeur mise
# en cache
return formfield |
Malheureusement, il peut arriver qu’il y ait tellement d’objets que c’est votre navigateur qui panique. En effet, si vous avez un modèle Promotion
avec un lien vers un modèle Produit
et que vous avez 5000 produits en base, vous allez créer 20 <select>
avec 5000 <options>
dedans. 100000 balises dans une page, ça peut faire mal.
Dans ce cas, il vaut mieux désactiver la possibilité de faire un drop down, et mettre un champ text à la place dans lequel on entre un ID en utilisant :
class VotreClasseAdmin(admin.ModelAdmin)
raw_id_fields = ('nom_de_votre_champ',) |
Django n’est pas chien, et vous mettra un petit bouton en forme de loupe pour chercher l’ID dont vous avez besoin.
Si vous avez beaucoup de ForeignKey non éditables (readonly_fields
), vous pouvez aussi vous fendre d’un select_related
:
class VotreClasseAdmin(admin.ModelAdmin)
def queryset(self, request):
# On filtre le queryset que Django va utiliser pour populer la liste
return super(MyAdmin, self).queryset(request).select_related('myfield') |
Ça ne marche que sur les non éditables, car pour eux Django ne créé pas de champ de formulaire.