1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75
| from django.contrib import admin
from django.db import transaction, models
from django import forms
from .models import Chapitre
class ChapitreAdminForm(forms.ModelForm):
class Meta:
model = Chapitre
fields = '__all__'
def clean(self):
"""
Cette méthode est appelée pendant la validation du formulaire.
C'est ici que nous détectons le conflit d'ordre sans bloquer la sauvegarde.
"""
cleaned_data = super().clean()
order = cleaned_data.get('order')
if order is not None:
# On cherche si un AUTRE chapitre utilise déjà ce numéro d'ordre.
query = Chapitre.objects.filter(order=order)
# Si on modifie un chapitre existant, il faut l'exclure de la recherche
if self.instance and self.instance.pk:
query = query.exclude(pk=self.instance.pk)
chapitre_conflictuel = query.first()
if chapitre_conflictuel:
# Conflit trouvé ! Au lieu de retourner une erreur,
# Il sera traité plus tard dans ModelAdmin.save_model().
self.displaced_chapitre = chapitre_conflictuel
return cleaned_data
@admin.register(Chapitre)
class ChapitreAdmin(admin.ModelAdmin):
# On dit à l'admin d'utiliser notre formulaire personnalisé.
form = ChapitreAdminForm
list_display = ('__str__', 'titre', 'order', 'description')
list_editable = ('titre', 'order', 'description')
fields = (('titre', 'order'), 'description')
ordering = ('order',)
def save_model(self, request, obj, form, change):
"""
Cette méthode est appelée juste avant la sauvegarde.
Elle orchestre les écritures en base de données de manière atomique et sécurisée.
"""
# On utilise une transaction pour s'assurer que soit TOUT réussit,
# soit TOUT est annulé.
with transaction.atomic():
# On vérifie si notre formulaire a trouvé un chapitre à déplacer.
if hasattr(form, 'displaced_chapitre'):
displaced = form.displaced_chapitre
# On calcule le nouvel `order` le plus élevé.
max_order_result = self.model.objects.aggregate(max_order=models.Max('order'))
max_order = max_order_result['max_order'] or 0
# On déplace l'ancien chapitre à la fin.
displaced.order = max_order + 1
displaced.save()
# On sauvegarde l'objet principal (celui que l'admin voulait créer/modifier).
super().save_model(request, obj, form, change) |
Partager