diff --git a/padelclub_backend/settings.py b/padelclub_backend/settings.py index a2bc9d5..ff5cee1 100644 --- a/padelclub_backend/settings.py +++ b/padelclub_backend/settings.py @@ -57,6 +57,8 @@ MIDDLEWARE = [ 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', + 'tournaments.middleware.ReferrerMiddleware', # Add this line + ] ROOT_URLCONF = 'padelclub_backend.urls' @@ -154,7 +156,10 @@ AUTHENTICATION_BACKENDS = [ ] CSRF_COOKIE_SECURE = True # if using HTTPS -SESSION_COOKIE_SECURE = True # Si vous utilisez HTTPS +if DEBUG: # Development environment + SESSION_COOKIE_SECURE = False +else: # Production environment + SESSION_COOKIE_SECURE = True from .settings_local import * from .settings_app import * diff --git a/padelclub_backend/settings_app.py b/padelclub_backend/settings_app.py index 0af1365..97d1372 100644 --- a/padelclub_backend/settings_app.py +++ b/padelclub_backend/settings_app.py @@ -51,4 +51,4 @@ SHOP_MANAGERS = [ # ('Xavier Rousset', 'xavier@padelclub.app'), ] SHOP_SITE_ROOT_URL = 'https://padelclub.app' -SHOP_SUPPORT_EMAIL = 'shop-support@padelclub.app' +SHOP_SUPPORT_EMAIL = 'shop@padelclub.app' diff --git a/shop/signals.py b/shop/signals.py index 4679112..83be688 100644 --- a/shop/signals.py +++ b/shop/signals.py @@ -8,43 +8,41 @@ from django.db import transaction @receiver([post_save, post_delete], sender=Order) def send_order_notification(sender, instance, **kwargs): - print("send_order_notification") - """ - Send an email notification when an order is created, updated, or deleted. - """ - # Use transaction.on_commit to ensure database operations are complete + """Send an email notification when an order is created, updated, or deleted.""" transaction.on_commit(lambda: _send_order_email(instance, **kwargs)) def _send_order_email(instance, **kwargs): - created = kwargs.get('created', False) - update_fields = kwargs.get('update_fields', None) - - # Determine the action type - if 'signal' in kwargs and kwargs['signal'] == post_delete: - action = "DELETED" - return - elif created: - action = "CREATED" + # Skip processing for PENDING orders + if instance.status == OrderStatus.PENDING: return - else: - action = "UPDATED" - print('updated', update_fields) - if update_fields and 'status' in update_fields: - action = "UPDATED" - # Get order details - order_id = instance.id - status = instance.status + # Determine action type + action = _determine_action_type(kwargs) + if action in ["DELETED", "CREATED"]: + return # No emails for these actions - print("status", status) - if status == OrderStatus.PENDING: - return + # Build common email components + order_details = _get_order_details(instance) + items_list = _build_items_list(instance.id, action) - total_price = instance.total_price + # Send internal notification + _send_internal_notification(instance, action, order_details, items_list) - # Generate admin URL - admin_url = f"{settings.SHOP_SITE_ROOT_URL}{reverse('admin:shop_order_change', args=[order_id])}" + # Send customer notification if applicable + if order_details['customer_email']: + _send_customer_notification(instance, order_details, items_list) +def _determine_action_type(kwargs): + """Determine the action type from signal kwargs.""" + if 'signal' in kwargs and kwargs['signal'] == post_delete: + return "DELETED" + elif kwargs.get('created', False): + return "CREATED" + else: + return "UPDATED" + +def _get_order_details(instance): + """Extract and build order details dictionary.""" # Get customer info customer_email = None if instance.user: @@ -56,60 +54,65 @@ def _send_order_email(instance, **kwargs): else: customer_info = "Client inconnu" - # Build order item details + # Translate statuses + status_fr_map = { + "PENDING": "EN ATTENTE", "PAID": "PAYÉE", + "SHIPPED": "EXPÉDIÉE", "DELIVERED": "LIVRÉE", "CANCELED": "ANNULÉE" + } + + payment_status_fr_map = { + "UNPAID": "NON PAYÉE", "PAID": "PAYÉE", "FAILED": "ÉCHOUÉE" + } + + return { + 'order_id': instance.id, + 'status': instance.status, + 'status_fr': status_fr_map.get(instance.status, instance.status), + 'payment_status': instance.payment_status, + 'payment_status_fr': payment_status_fr_map.get(instance.payment_status, instance.payment_status), + 'total_price': instance.total_price, + 'customer_info': customer_info, + 'customer_email': customer_email, + 'date_ordered': instance.date_ordered, + 'admin_url': f"{settings.SHOP_SITE_ROOT_URL}{reverse('admin:shop_order_change', args=[instance.id])}" + } + +def _build_items_list(order_id, action): + """Build the list of order items.""" items_list = "" if action != "DELETED": - # Use a fresh query to ensure we have the most recent data order_items = OrderItem.objects.filter(order_id=order_id).select_related('product', 'color', 'size') for item in order_items: color = item.color.name if item.color else "N/A" size = item.size.name if item.size else "N/A" - item_line = f"- {item.quantity}x {item.product.title} (Couleur: {color}, Taille: {size}, Prix: {item.price}€)\n" - items_list += item_line - - # Compose the email - if action == "CREATED": - action_fr = "CRÉÉE" - elif action == "UPDATED": - action_fr = "MISE À JOUR" - elif action == "DELETED": - action_fr = "SUPPRIMÉE" - else: - action_fr = action + items_list += f"- {item.quantity}x {item.product.title} (Couleur: {color}, Taille: {size}, Prix: {item.price}€)\n" + return items_list - # Translate current status - status_fr_map = { - "PENDING": "EN ATTENTE", - "PAID": "PAYÉE", - "SHIPPED": "EXPÉDIÉE", - "DELIVERED": "LIVRÉE", - "CANCELED": "ANNULÉE" +def _translate_action(action): + """Translate action to French.""" + translations = { + "CREATED": "CRÉÉE", "UPDATED": "MISE À JOUR", "DELETED": "SUPPRIMÉE" } - status_fr = status_fr_map.get(status, status) + return translations.get(action, action) - # Translate payment status - payment_status_fr_map = { - "UNPAID": "NON PAYÉE", - "PAID": "PAYÉE", - "FAILED": "ÉCHOUÉE" - } - payment_status_fr = payment_status_fr_map.get(instance.payment_status, instance.payment_status) +def _send_internal_notification(instance, action, order_details, items_list): + """Send notification email to shop managers.""" + action_fr = _translate_action(action) - # Send internal notification email - subject = f"Commande #{order_id} {action_fr}: {status_fr}" + subject = f"Commande #{order_details['order_id']} {action_fr}: {order_details['status_fr']}" message = f""" -La commande #{order_id} a été {action_fr.lower()} +La commande #{order_details['order_id']} a été {action_fr.lower()} -Statut: {status_fr} -Statut de paiement: {payment_status_fr} -Prix total: {total_price}€ +Statut: {order_details['status_fr']} +Statut de paiement: {order_details['payment_status_fr']} +Prix total: {order_details['total_price']}€ -{customer_info} +{order_details['customer_info']} Articles: {items_list} -Voir la commande dans le panneau d'administration: {admin_url} +Voir la commande dans le panneau d'administration: {order_details['admin_url']} Ceci est un message automatique. Merci de ne pas répondre. """ @@ -127,20 +130,93 @@ Ceci est un message automatique. Merci de ne pas répondre. fail_silently=False, ) - # Only send customer email for PAID status and if we have customer email - if status == OrderStatus.PAID and customer_email and instance.payment_status == "PAID": - # Generate customer-facing URLs - shop_url = f"{settings.SHOP_SITE_ROOT_URL}/shop" - contact_email = f"{settings.SHOP_SUPPORT_EMAIL}" +def _send_customer_notification(instance, order_details, items_list): + """Send appropriate notification email to customer based on order status.""" + # Common email variables + contact_email = settings.SHOP_SUPPORT_EMAIL + shop_url = f"{settings.SHOP_SITE_ROOT_URL}/shop" + date_formatted = order_details['date_ordered'].strftime('%d/%m/%Y') + + # Determine email content based on status and payment status + email_content = _get_customer_email_content( + instance.status, + order_details['payment_status'], + order_details['order_id'], + date_formatted, + order_details['status_fr'], + order_details['total_price'], + items_list, + contact_email, + shop_url + ) + + # Skip if no email content returned + if not email_content: + return + + # Send email to customer + send_mail( + subject=email_content['subject'], + message=email_content['message'], + from_email=settings.DEFAULT_FROM_EMAIL, + recipient_list=[order_details['customer_email']], + fail_silently=False, + ) - # Create a customer receipt email - customer_subject = f"Confirmation de votre commande #{order_id} - PadelClub" - customer_message = f""" +def _get_customer_email_content(status, payment_status, order_id, date, status_fr, + total_price, items_list, contact_email, shop_url): + """Get the appropriate customer email content based on order status.""" + + # Payment confirmation email + if status == OrderStatus.PAID and payment_status == "PAID": + return { + 'subject': f"Confirmation de votre commande #{order_id} - PadelClub", + 'message': _build_payment_confirmation_email(order_id, date, status_fr, + total_price, items_list, + contact_email, shop_url) + } + + # Order status update email + elif status in [OrderStatus.SHIPPED, OrderStatus.DELIVERED, OrderStatus.CANCELED]: + status_message = { + OrderStatus.SHIPPED: "Votre commande a été expédiée et est en cours de livraison.", + OrderStatus.DELIVERED: "Votre commande a été livrée. Nous espérons que vous apprécierez vos produits !", + OrderStatus.CANCELED: "Votre commande a été annulée. Si vous n'êtes pas à l'origine de cette annulation, veuillez nous contacter immédiatement." + }.get(status, "") + + return { + 'subject': f"Mise à jour de votre commande #{order_id} - PadelClub", + 'message': _build_status_update_email(order_id, date, status_message, status_fr, + total_price, items_list, contact_email) + } + + # Payment issue notification + elif payment_status == "FAILED": + return { + 'subject': f"Problème de paiement pour votre commande #{order_id} - PadelClub", + 'message': _build_payment_issue_email(order_id, date, total_price, + items_list, contact_email, shop_url) + } + + # Payment reminder for unpaid orders + elif payment_status == "UNPAID" and status != OrderStatus.PENDING: + return { + 'subject': f"Rappel de paiement pour votre commande #{order_id} - PadelClub", + 'message': _build_payment_reminder_email(order_id, date, total_price, + items_list, contact_email) + } + + # No email needed + return None + +def _build_payment_confirmation_email(order_id, date, status_fr, total_price, items_list, contact_email, shop_url): + """Build payment confirmation email message.""" + return f""" Bonjour, Nous vous remercions pour votre commande sur PadelClub ! -Récapitulatif de votre commande #{order_id} du {instance.date_ordered.strftime('%d/%m/%Y')} : +Récapitulatif de votre commande #{order_id} du {date} : Statut: {status_fr} Prix total: {total_price}€ @@ -159,13 +235,75 @@ Visitez notre boutique pour découvrir d'autres produits : Merci de votre confiance et à bientôt sur PadelClub ! L'équipe PadelClub - """ - - # Send email to customer - send_mail( - subject=customer_subject, - message=customer_message, - from_email=settings.DEFAULT_FROM_EMAIL, - recipient_list=[customer_email], - fail_silently=False, - ) + """ + +def _build_status_update_email(order_id, date, status_message, status_fr, total_price, items_list, contact_email): + """Build status update email message.""" + return f""" +Bonjour, + +Mise à jour concernant votre commande PadelClub #{order_id} du {date} : + +{status_message} + +Statut actuel: {status_fr} +Prix total: {total_price}€ + +Détail de votre commande : +{items_list} + +Pour toute question concernant votre commande, n'hésitez pas à contacter notre service client : +{contact_email} + +Merci de votre confiance et à bientôt sur PadelClub ! + +L'équipe PadelClub + """ + +def _build_payment_issue_email(order_id, date, total_price, items_list, contact_email, shop_url): + """Build payment issue email message.""" + return f""" +Bonjour, + +Nous avons rencontré un problème lors du traitement du paiement de votre commande PadelClub #{order_id}. + +Détails de la commande : +Date: {date} +Prix total: {total_price}€ + +Articles: +{items_list} + +Veuillez vérifier vos informations de paiement et réessayer. Si le problème persiste, n'hésitez pas à contacter notre service client : +{contact_email} + +Vous pouvez également visiter notre boutique pour finaliser votre achat : +{shop_url} + +Merci de votre compréhension. + +L'équipe PadelClub + """ + +def _build_payment_reminder_email(order_id, date, total_price, items_list, contact_email): + """Build payment reminder email message.""" + return f""" +Bonjour, + +Nous vous rappelons que votre commande PadelClub #{order_id} du {date} n'a pas encore été payée. + +Détails de la commande : +Prix total: {total_price}€ + +Articles: +{items_list} + +Pour finaliser votre commande, veuillez procéder au paiement dès que possible. + +Si vous rencontrez des difficultés ou si vous avez des questions, n'hésitez pas à contacter notre service client : +{contact_email} + +Merci de votre confiance. + +L'équipe PadelClub + """ diff --git a/tournaments/custom_views.py b/tournaments/custom_views.py index 43a6840..01d54bb 100644 --- a/tournaments/custom_views.py +++ b/tournaments/custom_views.py @@ -8,12 +8,20 @@ class CustomLoginView(auth_views.LoginView): authentication_form = EmailOrUsernameAuthenticationForm def get_success_url(self): - next_url = self.request.POST.get('next') - print("CustomLoginView", "next_url", next_url, self.request.GET) - if next_url: + # First check the 'next' parameter which has higher priority + next_url = self.request.POST.get('next') or self.request.GET.get('next') + if next_url and next_url.strip(): return next_url - else: - return reverse('index') + + # Then check if we have a stored referrer URL + referrer = self.request.session.get('login_referrer') + if referrer: + # Clear the stored referrer to prevent reuse + del self.request.session['login_referrer'] + return referrer + + # Fall back to default + return reverse('index') def get(self, request, *args, **kwargs): messages.get_messages(request).used = True diff --git a/tournaments/middleware.py b/tournaments/middleware.py new file mode 100644 index 0000000..ad517b2 --- /dev/null +++ b/tournaments/middleware.py @@ -0,0 +1,19 @@ +from django.conf import settings +from django.urls import resolve, reverse + +class ReferrerMiddleware: + def __init__(self, get_response): + self.get_response = get_response + + def __call__(self, request): + # Check if the user is anonymous and going to the login page + if not request.user.is_authenticated and request.path == reverse('login'): + # Get the referring URL from the HTTP_REFERER header + referrer = request.META.get('HTTP_REFERER') + + # Only store referrer if it exists and is not the login page itself + if referrer and 'login' not in referrer: + request.session['login_referrer'] = referrer + + response = self.get_response(request) + return response