265 lines
10 KiB
Python
265 lines
10 KiB
Python
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}")
|