Vue d'ensemble
Django 5.0, publié le 4 décembre 2023, ouvre un nouveau cycle majeur avec des fonctionnalités qui répondent à des demandes de longue date. Le GeneratedField permet de définir des colonnes calculées directement en base de données, les filtres à facettes dans l'admin affichent le nombre d'objets par valeur de filtre, et les valeurs par défaut des champs peuvent être calculées par la base de données.
Le rendu des formulaires est également simplifié grâce à Field.template_name, qui permet de personnaliser le template de chaque type de champ sans surcharger le widget entier.
Fonctionnalités principales
GeneratedField
Le nouveau GeneratedField permet de créer des colonnes dont la valeur est calculée automatiquement par la base de données à partir d'autres colonnes. Deux modes sont disponibles : STORED (la valeur est stockée physiquement) et VIRTUAL (calculée à chaque lecture). C'est idéal pour les dénormalisations qui doivent toujours rester cohérentes.
from django.db import models
from django.db.models import F, Value
from django.db.models.functions import Concat, Lower
class Employe(models.Model):
prenom = models.CharField(max_length=50)
nom = models.CharField(max_length=50)
salaire_mensuel = models.DecimalField(max_digits=10, decimal_places=2)
heures_semaine = models.PositiveIntegerField(default=35)
# Nom complet calculé en base de données
nom_complet = models.GeneratedField(
expression=Concat('prenom', Value(' '), 'nom'),
output_field=models.CharField(max_length=101),
db_persist=True, # STORED : stocké physiquement
)
# Salaire annuel (12 mois + 1 mois de prime)
salaire_annuel = models.GeneratedField(
expression=F('salaire_mensuel') * 13,
output_field=models.DecimalField(max_digits=12, decimal_places=2),
db_persist=True,
)
# Identifiant normalisé en minuscules
identifiant = models.GeneratedField(
expression=Concat(Lower('prenom'), Value('.'), Lower('nom')),
output_field=models.CharField(max_length=101),
db_persist=True,
)
# Utilisation : les champs générés sont accessibles en lecture
emp = Employe.objects.create(
prenom='Marie', nom='Dupont', salaire_mensuel=3500
)
emp.refresh_from_db()
print(emp.nom_complet) # Marie Dupont
print(emp.salaire_annuel) # 45500.00
print(emp.identifiant) # marie.dupont
Filtres à facettes dans l'admin
L'interface d'administration affiche désormais le nombre d'objets correspondant à chaque valeur de filtre, directement dans la barre latérale. Cela permet de voir d'un coup d'œil la distribution des données sans cliquer sur chaque filtre. Cette fonctionnalité s'active simplement en définissant show_facets sur le ModelAdmin.
from django.contrib import admin
class CommandeAdmin(admin.ModelAdmin):
list_display = ['reference', 'client', 'statut', 'montant', 'date']
list_filter = ['statut', 'date', 'mode_paiement']
# Activer les compteurs sur les filtres
show_facets = admin.ShowFacets.ALWAYS
# Trois options disponibles :
# ShowFacets.ALWAYS — toujours afficher les compteurs
# ShowFacets.ALLOW — afficher via un paramètre URL
# ShowFacets.NEVER — désactiver (par défaut)
# Résultat dans la barre latérale :
# Statut
# En attente (23)
# Validée (156)
# Expédiée (89)
# Livrée (412)
# Annulée (7)
Valeurs par défaut calculées en base
Les champs de modèle acceptent désormais un paramètre db_default pour définir une valeur par défaut calculée par la base de données plutôt que par Python. Cela garantit que la valeur par défaut est appliquée même lors d'insertions directes en SQL, et permet d'utiliser des fonctions de base de données comme Now().
from django.db import models
from django.db.models.functions import Now, Pi
class JournalActivite(models.Model):
action = models.CharField(max_length=200)
utilisateur = models.CharField(max_length=100)
# Horodatage géré par la base de données
date_creation = models.DateTimeField(db_default=Now())
# Compteur initialisé à zéro par la base
nb_tentatives = models.IntegerField(db_default=0)
# Priorité par défaut
priorite = models.CharField(
max_length=10,
db_default=Value('normal'),
)
# L'objet est créé sans spécifier les champs à défaut
entree = JournalActivite.objects.create(
action='connexion', utilisateur='marie'
)
entree.refresh_from_db()
print(entree.date_creation) # 2023-12-04 10:30:00+00:00
print(entree.nb_tentatives) # 0
print(entree.priorite) # normal
Rendu simplifié des formulaires
L'attribut Field.template_name permet de définir un template de rendu pour chaque type de champ de formulaire. Cela simplifie la personnalisation du rendu sans avoir à surcharger les widgets ou à écrire des templatetags complexes. Les templates par groupe de champs (div, table, ul) sont également personnalisables.
from django import forms
class InscriptionForm(forms.Form):
# Utiliser un template personnalisé pour ce champ
email = forms.EmailField(
template_name='forms/champs/email_avec_aide.html',
)
mot_de_passe = forms.CharField(
widget=forms.PasswordInput,
template_name='forms/champs/mot_de_passe.html',
)
conditions = forms.BooleanField(
template_name='forms/champs/case_a_cocher.html',
)
# Rendu global du formulaire avec un template personnalisé
template_name_div = 'forms/inscription_div.html'
# Dans le template principal :
# {{ form }} — utilise template_name_div par défaut
# {{ form.email }} — utilise le template du champ
