Accélérer les listings de l’admin Django avec beaucoup de ForeignKeys


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.

3 thoughts on “Accélérer les listings de l’admin Django avec beaucoup de ForeignKeys

  • Zanguu

    Premier bloc de code, ligne 20 (commentaire) c’est “.choices” et pas “.choide” je crois.

    Sinon sympa l’option, merci pour le partage.

  • Marc

    Le “prefetch_related” est plutôt cool aussi dans l’override du queryset.
    Je m’en sers régulièrement pour compter les ManyToMany ou aller chercher une relation plus profonde directement sur le listing de l’administration DJango.

Comments are closed.

Des questions Python sans rapport avec l'article ? Posez-les sur IndexError.