import os import platform from django import get_version as django_version from django.contrib import messages from django.db.models import Count, Prefetch, Q from django.http import Http404 from django.shortcuts import get_object_or_404, redirect, render from django.urls import reverse from django.utils import timezone from .forms import ConversationStartForm, MessageForm, ProfileForm, REACTION_CHOICES, ReactionForm from .models import Conversation, Message, ResakaProfile DEFAULT_META_DESCRIPTION = ( "RJL Resaka est une messagerie privée moderne pour lancer des conversations, " "envoyer des messages et suivre les notifications de lecture." ) def _profile_from_request(request): profiles = ResakaProfile.objects.order_by("display_name") profile = None requested_id = request.GET.get("profile") or request.session.get("active_profile_id") if requested_id: try: profile = profiles.get(pk=requested_id) except (ResakaProfile.DoesNotExist, ValueError, TypeError): profile = None if not profile and profiles.exists(): profile = profiles.first() if profile: request.session["active_profile_id"] = profile.pk return profile, profiles def _profile_query_suffix(profile): return f"?profile={profile.pk}" if profile else "" def _conversation_queryset(active_profile): base = Conversation.objects.select_related("starter", "recipient").prefetch_related( Prefetch( "messages", queryset=Message.objects.select_related("author").order_by("created_at"), ) ) if active_profile: base = base.filter(Q(starter=active_profile) | Q(recipient=active_profile)).annotate( unread_count=Count( "messages", filter=Q(messages__is_read=False) & ~Q(messages__author=active_profile), ) ) else: base = base.none() return base.order_by("-updated_at") def home(request): host_name = request.get_host().lower() agent_brand = "AppWizzy" if host_name == "appwizzy.com" else "Flatlogic" now = timezone.now() active_profile, profiles = _profile_from_request(request) conversations = list(_conversation_queryset(active_profile)) for conversation in conversations: message_list = list(conversation.messages.all()) conversation.last_message = message_list[-1] if message_list else None conversation.counterpart = conversation.counterpart_for(active_profile) stats = { "profiles": profiles.count(), "conversations": len(conversations), "messages": Message.objects.count(), } context = { "project_name": "RJL Resaka", "agent_brand": agent_brand, "django_version": django_version(), "python_version": platform.python_version(), "current_time": now, "host_name": host_name, "project_description": os.getenv("PROJECT_DESCRIPTION", DEFAULT_META_DESCRIPTION), "project_image_url": os.getenv("PROJECT_IMAGE_URL", ""), "page_title": "RJL Resaka | Messagerie privée moderne", "page_description": DEFAULT_META_DESCRIPTION, "active_profile": active_profile, "profiles": profiles, "profile_form": ProfileForm(), "conversation_form": ConversationStartForm(active_profile=active_profile), "conversations": conversations, "stats": stats, "profile_query": _profile_query_suffix(active_profile), } return render(request, "core/index.html", context) def create_profile(request): if request.method != "POST": return redirect("home") form = ProfileForm(request.POST) if form.is_valid(): profile = form.save() request.session["active_profile_id"] = profile.pk messages.success(request, f"Profil {profile.display_name} créé. Vous pouvez maintenant lancer une discussion.") return redirect(f"{reverse('home')}?profile={profile.pk}") active_profile, profiles = _profile_from_request(request) conversations = list(_conversation_queryset(active_profile)) for conversation in conversations: message_list = list(conversation.messages.all()) conversation.last_message = message_list[-1] if message_list else None conversation.counterpart = conversation.counterpart_for(active_profile) context = { "project_name": "RJL Resaka", "page_title": "Créer un profil | RJL Resaka", "page_description": DEFAULT_META_DESCRIPTION, "active_profile": active_profile, "profiles": profiles, "profile_form": form, "conversation_form": ConversationStartForm(active_profile=active_profile), "conversations": conversations, "stats": { "profiles": profiles.count(), "conversations": len(conversations), "messages": Message.objects.count(), }, "profile_query": _profile_query_suffix(active_profile), "current_time": timezone.now(), "django_version": django_version(), "python_version": platform.python_version(), } return render(request, "core/index.html", context, status=400) def start_conversation(request): if request.method != "POST": return redirect("home") active_profile, _profiles = _profile_from_request(request) if not active_profile: messages.error(request, "Créez ou sélectionnez d'abord un profil pour discuter.") return redirect("home") form = ConversationStartForm(request.POST, active_profile=active_profile) if not form.is_valid(): messages.error(request, "Impossible de lancer la discussion. Vérifiez les champs du formulaire.") return redirect(f"{reverse('home')}?profile={active_profile.pk}") recipient = form.cleaned_data["recipient"] body = form.cleaned_data["body"] conversation = ( Conversation.objects.filter( (Q(starter=active_profile) & Q(recipient=recipient)) | (Q(starter=recipient) & Q(recipient=active_profile)) ) .select_related("starter", "recipient") .first() ) created = False if not conversation: conversation = Conversation.objects.create( starter=active_profile, recipient=recipient, subject=f"Discussion privée · {active_profile.display_name} & {recipient.display_name}", ) created = True Message.objects.create( conversation=conversation, author=active_profile, body=body, is_read=False, ) conversation.save(update_fields=["updated_at"]) if created: messages.success(request, f"Nouvelle conversation lancée avec {recipient.display_name}.") else: messages.success(request, f"Message envoyé à {recipient.display_name}.") return redirect(f"{conversation.get_absolute_url()}?profile={active_profile.pk}") def conversation_detail(request, pk): active_profile, profiles = _profile_from_request(request) conversation = get_object_or_404( Conversation.objects.select_related("starter", "recipient").prefetch_related( Prefetch("messages", queryset=Message.objects.select_related("author").order_by("created_at")) ), pk=pk, ) if not active_profile or active_profile.pk not in {conversation.starter_id, conversation.recipient_id}: if active_profile: messages.error(request, "Cette conversation n'appartient pas au profil sélectionné.") return redirect(f"{reverse('home')}?profile={active_profile.pk}") messages.error(request, "Sélectionnez un profil pour ouvrir une conversation.") return redirect("home") if request.method == "POST": form = MessageForm(request.POST) if form.is_valid(): message = form.save(commit=False) message.conversation = conversation message.author = active_profile message.is_read = False message.save() conversation.save(update_fields=["updated_at"]) messages.success(request, "Message envoyé.") return redirect(f"{conversation.get_absolute_url()}?profile={active_profile.pk}") else: form = MessageForm() Message.objects.filter(conversation=conversation).exclude(author=active_profile).filter(is_read=False).update(is_read=True) message_list = list(conversation.messages.all()) counterpart = conversation.counterpart_for(active_profile) reaction_forms = {message.pk: ReactionForm() for message in message_list} context = { "project_name": "RJL Resaka", "page_title": f"Chat avec {counterpart.display_name} | RJL Resaka", "page_description": f"Conversation privée entre {active_profile.display_name} et {counterpart.display_name} sur RJL Resaka.", "active_profile": active_profile, "profiles": profiles, "conversation": conversation, "counterpart": counterpart, "message_form": form, "messages_list": message_list, "reaction_forms": reaction_forms, "reaction_choices": REACTION_CHOICES, "profile_query": _profile_query_suffix(active_profile), } return render(request, "core/chat_detail.html", context) def react_message(request, pk): if request.method != "POST": raise Http404 active_profile, _profiles = _profile_from_request(request) message = get_object_or_404(Message.objects.select_related("conversation", "author"), pk=pk) conversation = message.conversation if not active_profile or active_profile.pk not in {conversation.starter_id, conversation.recipient_id}: messages.error(request, "Sélectionnez un profil valide pour réagir au message.") return redirect("home") form = ReactionForm(request.POST) if form.is_valid(): message.reaction = form.cleaned_data["reaction"] message.save(update_fields=["reaction"]) messages.success(request, "Réaction envoyée.") else: messages.error(request, "Réaction invalide.") return redirect(f"{conversation.get_absolute_url()}?profile={active_profile.pk}")