Quand on expose des données depuis un modèle Django vers une API ou un serializer, les noms de champs du modèle ne correspondent pas toujours à ce qu’on veut retourner. Le réflexe habituel : récupérer les instances, puis renommer en Python. Il existe une meilleure option : laisser la base de données faire le travail via F() dans values().

Le problème : les noms de champs s’imposent

class Task(models.Model):
    name = models.CharField(...)
    created_at = models.DateTimeField(...)

Si on veut retourner nom_tache au lieu de name, on récupère les données puis on renomme en Python, soit via une compréhension de dict, soit dans le serializer. Dans les deux cas, la transformation se fait après coup, en mémoire.

F() dans values() : l’alias au niveau SQL

Django permet de passer des expressions F() comme arguments nommés à values(). L’alias devient le nom du champ dans le résultat.

from django.db.models import F

Task.objects.values(
    identifiant=F('id'),
    nom_tache=F('name'),
    date_creation=F('created_at'),
)

Le SQL généré :

SELECT id AS identifiant, name AS nom_tache, created_at AS date_creation
FROM tasks_task

La base renomme les colonnes, Django retourne directement les dictionnaires avec les bons noms. Aucune transformation Python.

Cas d’usage concrets

Adapter à une convention de nommage

Un modèle avec des noms hérités ou une convention interne peut être exposé avec des noms plus explicites sans toucher au modèle :

from django.db.models import F

TaskRelation.objects.values(
    relation_id=F('id'),
    tache_parent=F('parent_task_id'),
    ordre=F('position'),
)

Préparer une réponse API

Quand on construit un payload JSON sans passer par DRF, ou qu’on alimente un serializer flat :

def get_tasks_payload(user_id: int) -> list[dict]:
    return list(
        Task.objects.filter(owner_id=user_id).values(
            id=F('id'),
            label=F('name'),
            createdAt=F('created_at'),
        )
    )

Le payload sort directement avec les noms attendus par le front. Pas de post-traitement.

Traduire les noms de champs

Dans un contexte multilingue ou quand on expose des données à des partenaires avec une nomenclature imposée :

Produit.objects.values(
    reference=F('sku'),
    designation=F('label'),
    prix_unitaire=F('unit_price'),
)

Ce que ça change en pratique

Renommer en Python revient à boucler sur chaque dict pour en construire un nouveau. Sur un QuerySet de 10 000 lignes, c’est 10 000 allocations supplémentaires. Avec l’alias SQL, la base retourne directement les colonnes nommées correctement. Django les lit une fois, sans couche intermédiaire.

Ce n’est pas une micro-optimisation négligeable sur des exports, des rapports ou des endpoints qui retournent de gros volumes.

Limite à connaître

values() retourne des dictionnaires, pas des instances de modèle. On perd l’accès aux méthodes et propriétés du modèle. C’est adapté aux lectures orientées données (API, exports, agrégations), pas aux traitements métier qui nécessitent le comportement du modèle.


Pour aller plus loin sur les optimisations QuerySet, l’article sur in_bulk() montre comment éviter le N+1 avec un accès O(1) par identifiant.