Affichage de la page de soumission
Lorsque l'utilisateur clique sur "Passer commande" dans le panier, il est redirigé vers la page /order/place/. Les éléments sélectionnés sont regroupés dans un fromulaire HTML utilisant des cases à cocher. Seules les valeurs des cases cochées sont soumises via une liste d'identifiants produits.
<form method="post" action="{% url 'order:place' %}">
{% csrf_token %}
{% for produit in liste_produits %}
<ul class="liste_produit">
<li><input type="checkbox" name="produit_ids" value="{{ produit.id }}" checked></li>
<!-- Détails du produit -->
</ul>
{% endfor %}
<ul class="actions">
<li><input type="submit" value='Valider la commande'/></li>
</ul>
</form>
Configuration des URLs et vue
# urls.py
path('place/', VueCommande.as_view(), name='place'),
# views.py
class VueCommande(LoginRequiredMixin, View):
def post(self, request):
identifiants_produits = request.POST.getlist('produit_ids')
if not identifiants_produits:
return redirect('panier:index')
total = 0
articles = []
connexion_redis = get_redis_connection('default')
cle_panier = f'panier_{request.user.id}'
for pid in identifiants_produits:
try:
article = Produit.objects.get(id=pid)
quantite = int(connexion_redis.hget(cle_panier, pid))
montant = article.prix * quantite
article.quantite = quantite
article.montant = montant
articles.append(article)
total += montant
except Produit.DoesNotExist:
return redirect('panier:index')
context = {
'articles': articles,
'total': total,
'frais_livraison': 10
}
return render(request, 'commande/validation.html', context)
Template de validation de commaned
{% extends 'base.html' %}
{% block contenu %}
<h3>Adresse de livraison</h3>
<!-- Sélection d'adresse -->
<h3>Méthode de paiement</h3>
<!-- Options de paiement -->
<h3>Détails des articles</h3>
{% for article in articles %}
<div class="detail_article">
<img src="{{ article.image.url }}">
<span>{{ article.nom }} - {{ article.quantite }} x {{ article.prix }}</span>
</div>
{% endfor %}
<script>
$('#btn_commander').click(function() {
$.post('/commande/creer/', {
'adresse_id': $('input[name="adresse"]:checked').val(),
'mode_paiement': $('input[name="paiement"]:checked').val(),
'articles_ids': "{{ chaine_ids }}",
'csrfmiddlewaretoken': "{{ csrf_token }}"
}, function(reponse) {
if (reponse.succes) {
// Redirection après succès
}
})
});
</script>
{% endblock %}
Création de la commande
class CreationCommande(View):
@transaction.atomic
def post(self, request):
reponse = {'succes': False, 'erreur': ''}
utilisateur = request.user
# Validation des données
adresse_id = request.POST.get('adresse_id')
mode_paiement = request.POST.get('mode_paiement')
chaine_ids = request.POST.get('articles_ids')
if not all([adresse_id, mode_paiement, chaine_ids]):
reponse['erreur'] = 'Données manquantes'
return JsonResponse(reponse)
# Génération du numéro de commande
sequence = get_next_value('commande')
numero_commande = f"{datetime.now().strftime('%Y%m%d%H%M%S')}{sequence}"
point_sauvegarde = transaction.savepoint()
try:
# Création entête commande
commande = Commande.objects.create(
numero=numero_commande,
utilisateur=utilisateur,
adresse_id=adresse_id,
mode_paiement=mode_paiement
)
connexion_redis = get_redis_connection('default')
cle_panier = f'panier_{utilisateur.id}'
ids_produits = chaine_ids.split(',')
for pid in ids_produits:
tentatives = 0
while tentatives < 3:
produit = Produit.objects.get(id=pid)
quantite = int(connexion_redis.hget(cle_panier, pid))
if quantite > produit.stock:
reponse['erreur'] = 'Stock insuffisant'
raise Exception()
nouveau_stock = produit.stock - quantite
lignes_affectees = Produit.objects.filter(
id=pid,
stock=produit.stock
).update(stock=nouveau_stock)
if lignes_affectees:
LigneCommande.objects.create(
commande=commande,
produit=produit,
quantite=quantite,
prix=produit.prix
)
break
tentatives += 1
connexion_redis.hdel(cle_panier, *ids_produits)
reponse['succes'] = True
except Exception as e:
transaction.savepoint_rollback(point_sauvegarde)
reponse['erreur'] = str(e) or 'Erreur système'
return JsonResponse(reponse)
Gestion des transactions et concurrence
L'annotation @transaction.atomic assure l'intégrité des opérations base de données. Deux stratégies gèrent les accès concurrents :
Verrouillage pesimiste :
produit = Produit.objects.select_for_update().get(id=pid)
Verrouillage optimiste :
lignes_affectees = Produit.objects.filter(
id=pid,
stock=ancien_stock
).update(stock=nouveau_stock)
Le verrouillage optimiste est préférable pour les systèmes à forte lecture, tandis que le verrouillage pessimiste convient mieux aux environnements à écriture intensive.