Spaces:
Sleeping
Sleeping
| # ============================================ | |
| # apps/messaging/views.py | |
| # ============================================ | |
| from rest_framework import viewsets, status | |
| from rest_framework.decorators import action | |
| from rest_framework.response import Response | |
| from rest_framework.permissions import IsAuthenticated | |
| from apps.messaging.models import Conversation, Message | |
| from apps.messaging.serializers import ( | |
| ConversationSerializer, MessageSerializer, | |
| ConversationCreateSerializer, MessageCreateSerializer | |
| ) | |
| from apps.core.mixins import HashIdMixin | |
| class ConversationViewSet(HashIdMixin, viewsets.ModelViewSet): | |
| """Gestion des conversations""" | |
| permission_classes = [IsAuthenticated] | |
| serializer_class = ConversationSerializer | |
| def get_queryset(self): | |
| # Conversations où l'utilisateur est participant actif | |
| return Conversation.objects.filter( | |
| participants__user=self.request.user, | |
| participants__is_active=True, | |
| is_active=True | |
| ).distinct().order_by('-last_message_at') | |
| def get_serializer_class(self): | |
| if self.action == 'create': | |
| return ConversationCreateSerializer | |
| return ConversationSerializer | |
| def create(self, request, *args, **kwargs): | |
| """Créer une conversation avec le bon serializer de réponse""" | |
| serializer = self.get_serializer(data=request.data) | |
| serializer.is_valid(raise_exception=True) | |
| conversation = serializer.save() | |
| # Utiliser ConversationSerializer pour la réponse | |
| response_serializer = ConversationSerializer( | |
| conversation, | |
| context={'request': request} | |
| ) | |
| headers = self.get_success_headers(response_serializer.data) | |
| return Response( | |
| response_serializer.data, | |
| status=status.HTTP_201_CREATED, | |
| headers=headers | |
| ) | |
| def _check_active_booking(self, user1, user2): | |
| """Vérifie s'il y a un rendez-vous actif entre deux utilisateurs""" | |
| from django.utils import timezone | |
| from apps.bookings.models import Booking | |
| from django.db.models import Q | |
| now = timezone.now() | |
| # Trouver un booking CONFIRMED qui inclut 'now' | |
| # Note: Booking a date et time, mais pas duration explicitement dans le modèle montré. | |
| # On suppose une durée par défaut de 1h ou on vérifie juste date/heure de début <= now. | |
| # Pour l'instant, on va supposer que le booking est actif si on est le jour J et que l'heure est passée | |
| # MAIS le user a dit "le temps du rendez-vous". | |
| # Il faudrait idéalement une durée ou end_time dans Booking. | |
| # Le modèle Booking a 'time' (start_time). | |
| # On va assumer une durée standard de 1h pour l'instant ou vérifier si un booking existe "autour" de maintenant. | |
| # Amélioration: Vérifier si un booking est en cours (status=CONFIRMED) | |
| # On cherche un booking où (student=u1 AND mentor=u2) OR (student=u2 AND mentor=u1) | |
| # AND date = today AND time <= now AND (time + duration >= now) | |
| # Comme on n'a pas la durée, on va être permissif: | |
| # Si un booking est confirmé pour AUJOURD'HUI, on considère le chat ouvert pour la journée ? | |
| # OU Mieux: On considère ouvert si on est après l'heure de début. | |
| # Le user dit "hors du temps de rendez-vous... destinataire ne voit pas". | |
| # Donc c'est strict. | |
| # Hack: On va supposer une session de 2h max pour être large. | |
| active_booking = Booking.objects.filter( | |
| Q(student=user1, mentor=user2) | Q(student=user2, mentor=user1), | |
| status='CONFIRMED', | |
| date=now.date(), | |
| time__lte=now.time() | |
| ).first() | |
| if active_booking: | |
| # Vérifier si on est encore dans le créneau (ex: < 2h après début) | |
| # Conversion time -> datetime pour comparaison | |
| import datetime | |
| booking_start = datetime.datetime.combine(now.date(), active_booking.time) | |
| booking_start = timezone.make_aware(booking_start) | |
| # Si on est dans les 2h suivant le début | |
| if now <= booking_start + datetime.timedelta(hours=2): | |
| return True | |
| return False | |
| def messages(self, request, pk=None): | |
| """GET /api/conversations/{id}/messages/""" | |
| conversation = self.get_object() | |
| # Identifier l'autre participant | |
| other_participant = conversation.participants.exclude(user=request.user).first() | |
| other_user = other_participant.user if other_participant else None | |
| if other_user: | |
| # Vérifier s'il y a un booking actif ou passé qui devrait débloquer les messages | |
| from django.utils import timezone | |
| from apps.bookings.models import Booking | |
| from django.db.models import Q | |
| now = timezone.now() | |
| # Chercher le dernier booking commencé | |
| last_started_booking = Booking.objects.filter( | |
| Q(student=request.user, mentor=other_user) | Q(student=other_user, mentor=request.user), | |
| status='CONFIRMED', | |
| date__lte=now.date() | |
| ).filter( | |
| # Pour les dates passées, tout est bon. Pour aujourd'hui, il faut que time <= now | |
| Q(date__lt=now.date()) | Q(date=now.date(), time__lte=now.time()) | |
| ).order_by('-date', '-time').first() | |
| if last_started_booking: | |
| # Débloquer tous les messages envoyés AVANT le début de ce booking | |
| # qui sont encore cachés et destinés à MOI (request.user) | |
| # Messages envoyés par l'autre user | |
| import datetime | |
| booking_start_dt = datetime.datetime.combine(last_started_booking.date, last_started_booking.time) | |
| booking_start_dt = timezone.make_aware(booking_start_dt) | |
| # Update en masse | |
| conversation.messages.filter( | |
| sender=other_user, | |
| is_visible_to_recipient=False, | |
| created_at__lte=booking_start_dt | |
| ).update(is_visible_to_recipient=True) | |
| # Si le booking est ACTUELLEMENT en cours (ex: < 2h), on débloque TOUT | |
| if last_started_booking.date == now.date(): | |
| if now <= booking_start_dt + datetime.timedelta(hours=2): | |
| conversation.messages.filter( | |
| sender=other_user, | |
| is_visible_to_recipient=False | |
| ).update(is_visible_to_recipient=True) | |
| # Récupérer les messages | |
| # Je vois mes messages (sender=me) ET les messages visibles des autres | |
| from django.db.models import Q | |
| messages = conversation.messages.filter(is_active=True).filter( | |
| Q(sender=request.user) | Q(is_visible_to_recipient=True) | |
| ).order_by('created_at') | |
| # Marquer comme lu | |
| participant = conversation.participants.filter( | |
| user=request.user, | |
| is_active=True | |
| ).first() | |
| if participant: | |
| from django.utils import timezone | |
| participant.last_read_at = timezone.now() | |
| participant.save(update_fields=['last_read_at']) | |
| # Pagination | |
| page = self.paginate_queryset(messages) | |
| if page is not None: | |
| serializer = MessageSerializer(page, many=True) | |
| return self.get_paginated_response(serializer.data) | |
| serializer = MessageSerializer(messages, many=True) | |
| return Response(serializer.data) | |
| def send_message(self, request, pk=None): | |
| """POST /api/conversations/{id}/send_message/""" | |
| conversation = self.get_object() | |
| # Determine visibility | |
| is_visible = False | |
| other_participant = conversation.participants.exclude(user=request.user).first() | |
| if other_participant: | |
| is_visible = self._check_active_booking(request.user, other_participant.user) | |
| # Inject visibility into serializer save | |
| # Note: MessageCreateSerializer doesn't expect is_visible_to_recipient in validated_data usually, | |
| # but we can pass it via save() kwargs if we modify the serializer or just update instance after. | |
| # Better: Modify serializer.save() call or pass context. | |
| # Let's update the serializer create method to accept this, or simpler: update object after creation. | |
| serializer = MessageCreateSerializer( | |
| data=request.data, | |
| context={'request': request, 'conversation': conversation} | |
| ) | |
| serializer.is_valid(raise_exception=True) | |
| message = serializer.save() | |
| if is_visible: | |
| message.is_visible_to_recipient = True | |
| message.save(update_fields=['is_visible_to_recipient']) | |
| # Broadcast to WebSocket avec les infos de visibilité | |
| try: | |
| from channels.layers import get_channel_layer | |
| from asgiref.sync import async_to_sync | |
| channel_layer = get_channel_layer() | |
| msg_data = MessageSerializer(message).data | |
| async_to_sync(channel_layer.group_send)( | |
| f'chat_{conversation.id}', | |
| { | |
| 'type': 'chat_message', | |
| 'message': msg_data, | |
| 'sender_id': request.user.id, | |
| 'is_visible': is_visible | |
| } | |
| ) | |
| except Exception as e: | |
| pass | |
| # Préparer la réponse | |
| response_data = MessageSerializer(message).data | |
| # Ajouter un avertissement si le message est en attente | |
| if not is_visible: | |
| response_data['queued'] = True | |
| response_data['queued_message'] = "Votre message sera délivré au destinataire lors de votre prochain rendez-vous." | |
| return Response( | |
| response_data, | |
| status=status.HTTP_201_CREATED | |
| ) | |