This commit is contained in:
AdrienLSH
2024-05-14 08:50:37 +02:00
parent 95f0097ce5
commit e308e8f012
231 changed files with 70 additions and 22 deletions

0
django/chat/__init__.py Normal file
View File

6
django/chat/admin.py Normal file
View File

@ -0,0 +1,6 @@
from django.contrib import admin
from .models import ChatChannelModel, ChatMemberModel, ChatMessageModel
admin.site.register(ChatChannelModel)
admin.site.register(ChatMemberModel)
admin.site.register(ChatMessageModel)

6
django/chat/apps.py Normal file
View File

@ -0,0 +1,6 @@
from django.apps import AppConfig
class ChatConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'chat'

View File

@ -0,0 +1,72 @@
from channels.generic.websocket import WebsocketConsumer
from asgiref.sync import async_to_sync
from .models import ChatMemberModel, ChatMessageModel
import time
import json
class ChatConsumer(WebsocketConsumer):
def connect(self):
self.user = self.scope["user"]
if not self.user.is_authenticated:
return
self.channel_id: int = int(self.scope['url_route']['kwargs']['chat_id'])
if not ChatMemberModel.objects.filter(member_id=self.user.pk, channel_id=self.channel_id).exists():
return
if self.channel_layer is None:
return
self.room_group_name = f'chat{self.channel_id}'
async_to_sync(self.channel_layer.group_add)(
self.room_group_name,
self.channel_name
)
self.accept()
def receive(self, text_data=None, bytes_data=None):
if text_data is None:
return
user = self.scope["user"]
if (user.is_anonymous or not user.is_authenticated):
return
text_data_json: dict = json.loads(text_data)
message = text_data_json.get('message')
if message is None:
return
message_time: int = int(time.time() * 1000)
async_to_sync(self.channel_layer.group_send)(
self.room_group_name,
{
'type': 'chat_message',
'author_id': user.pk,
'content': message,
'time': message_time,
}
)
ChatMessageModel(
channel_id=self.channel_id,
author_id=user.pk,
content=message,
time=message_time
).save()
def chat_message(self, event):
self.send(text_data=json.dumps({
'type': 'chat',
'author_id': event['author_id'],
'content': event['content'],
'time': event['time'],
}))

41
django/chat/models.py Normal file
View File

@ -0,0 +1,41 @@
from django.db.models import Model, IntegerField, ForeignKey, CharField, CASCADE
from django.db.models.signals import post_delete
from django.dispatch import receiver
from django.contrib.auth.models import User
class ChatChannelModel(Model):
def create(self, members: [User]):
self.save()
for member in members:
ChatMemberModel(channel=self, member=member).save()
return self
def get_members(self):
return [member_channel.member for member_channel in ChatMemberModel.objects.filter(channel=self)]
class ChatMemberModel(Model):
member = ForeignKey(User, on_delete=CASCADE)
channel = ForeignKey(ChatChannelModel, on_delete=CASCADE)
@receiver(post_delete, sender=ChatMemberModel)
def delete_channel_when_member_deleted(sender, instance, **kwargs):
print(sender, instance)
class ChatMessageModel(Model):
channel = ForeignKey(ChatChannelModel, on_delete=CASCADE)
author = ForeignKey(User, on_delete=CASCADE)
content = CharField(max_length=255)
time = IntegerField(primary_key=False)
class AskModel(Model):
asker_id = IntegerField(primary_key=False)
asked_id = IntegerField(primary_key=False)
# return if the asker ask the asked to play a game
def is_asked(self, asker_id, asked_id):
return AskModel.objects.get(asker_id=asker_id, asked_id=asked_id) is not None

7
django/chat/routing.py Normal file
View File

@ -0,0 +1,7 @@
from django.urls import re_path
from . import consumersChat
websocket_urlpatterns = [
re_path(r'ws/chat/(?P<chat_id>\d+)$', consumersChat.ChatConsumer.as_asgi()),
]

View File

@ -0,0 +1,10 @@
from rest_framework import serializers
from django.utils.translation import gettext as _
from profiles.models import ProfileModel
from ..models import ChatChannelModel, ChatMessageModel
class AskSerializer(serializers.ModelSerializer):
members_id = serializers.ListField(child=serializers.IntegerField())

View File

@ -0,0 +1,40 @@
from rest_framework import serializers
from django.utils.translation import gettext as _
from django.contrib.auth.models import User
from ..models import ChatChannelModel, ChatMessageModel
class ChatChannelSerializer(serializers.ModelSerializer):
members_id = serializers.ListField(child=serializers.IntegerField(), required=True, write_only=True)
messages = serializers.SerializerMethodField()
class Meta:
model = ChatChannelModel
fields = ["members_id", "id", 'messages']
def validate_members_id(self, value):
members_id: [int] = value
if len(members_id) < 2:
raise serializers.ValidationError(_('There is not enough members to create the channel.'))
if len(set(members_id)) != len(members_id):
raise serializers.ValidationError(_('Duplicate in members list.'))
if self.context.get('user').pk not in members_id:
raise serializers.ValidationError(_('You are trying to create a group chat without you.'))
for member_id in members_id:
if not User.objects.filter(pk=member_id).exists():
raise serializers.ValidationError(_(f"The profile {member_id} doesn't exist."))
return members_id
def get_messages(self, obj: ChatChannelModel):
messages = ChatMessageModel.objects.filter(channel=obj).order_by('time')
return ChatMessageSerializer(messages, many=True).data
class ChatMessageSerializer(serializers.ModelSerializer):
class Meta:
model = ChatMessageModel
fields = ["channel", "author", "content", "time"]

30
django/chat/tests.py Normal file
View File

@ -0,0 +1,30 @@
from django.test import TestCase
from django.test.client import Client
from django.http import HttpResponse, HttpRequest
from django.contrib.auth.models import User
# Create your tests here.
class ChatTest(TestCase):
def setUp(self):
self.client = Client()
self.username='bozo1'
self.password='password'
self.user: User = User.objects.create_user(username=self.username, password=self.password)
self.dest: User = User.objects.create_user(username="bozo2", password=self.password)
self.url = "/api/chat/"
def test_create_chat(self):
self.client.login(username=self.username, password=self.password)
response: HttpResponse = self.client.post(self.url + str(self.user.pk), {"members_id": [self.user.pk, self.dest.pk]})
response_dict: dict = eval(response.content)
self.assertDictEqual(response_dict, {})
def test_create_chat_unlogged(self):
response: HttpResponse = self.client.post(self.url + str(self.user.pk), {"members_id": [self.user.pk, self.dest.pk]})
response_dict: dict = eval(response.content)
self.assertDictEqual(response_dict, {'detail': 'Authentication credentials were not provided.'})

10
django/chat/urls.py Normal file
View File

@ -0,0 +1,10 @@
from django.urls import path
from .views import chat
from .views import ask
urlpatterns = [
path("", chat.ChannelView.as_view(), name="chats_page"),
path("ask/", ask.AskView.as_view(), name="chats_ask"),
path("ask/accept", ask.AskAcceptView.as_view(), name="chats_ask_accept"),
]

74
django/chat/views/ask.py Normal file
View File

@ -0,0 +1,74 @@
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import permissions, status
from rest_framework.authentication import SessionAuthentication
from chat.models import AskModel
from ..serializers.ask import AskSerializer
from notice.consumers import notice_manager
from django.contrib.auth.models import User
class AskView(APIView):
serializer_class = AskSerializer
permission_classes = (permissions.IsAuthenticated,)
authentication_classes = (SessionAuthentication,)
def post(self, request):
data: dict = request.data
if (data["asked"] is None):
return
asker_id = request.user.pk
asked_id = data["asked"]
if AskModel().is_asked(asker_id, asked_id):
return Response(status=status.HTTP_208_ALREADY_REPORTED)
AskModel(asker_id=asker_id, asked_id=asked_id).save()
return Response(status=status.HTTP_201_CREATED)
def delete(self, request):
data: dict = request.data
if (data["asker"] is None):
return
asker_id = data["asker"]
asked_id = request.user.pk
if not AskModel().is_asked(asker_id, asked_id):
return Response(status=status.HTTP_404_NOT_FOUND)
asker = User.objects.filter(pk=asked_id)[0]
notice_manager.refuse_game(request.user, asker)
AskModel(asker_id=asker_id, asked_id=asked_id).delete()
return Response(status=status.HTTP_200_OK)
class AskAcceptView(APIView):
serializer_class = AskSerializer
permission_classes = (permissions.IsAuthenticated,)
authentication_classes = (SessionAuthentication,)
def post(self, request):
data: dict = request.data
if (data["asker"] is None):
return
asker_id = data["asker"]
asked_id = request.user.pk
if not AskModel().is_asked(asker_id, asked_id):
return Response(status=status.HTTP_404_NOT_FOUND)
notice_manager.accept_game(asker=User.objects.filter(pk=asker_id)[0], asked=User.objects.filter(pk=asked_id)[0])
AskModel(asker_id=asker_id, asked_id=asked_id).delete()
return Response(status=status.HTTP_200_OK)

31
django/chat/views/chat.py Normal file
View File

@ -0,0 +1,31 @@
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import permissions
from rest_framework.authentication import SessionAuthentication
from django.contrib.auth.models import User
from ..models import ChatChannelModel, ChatMemberModel
from ..serializers.chat import ChatChannelSerializer
class ChannelView(APIView):
serializer_class = ChatChannelSerializer
permission_classes = (permissions.IsAuthenticated,)
authentication_classes = (SessionAuthentication,)
def post(self, request):
serializer = self.serializer_class(data=request.data, context={'user': request.user})
serializer.is_valid(raise_exception=True)
members_id = serializer.validated_data.get('members_id')
member_list = [User.objects.get(pk=member_id) for member_id in members_id]
for member_channel in ChatMemberModel.objects.filter(member=member_list[0]):
channel: ChatChannelModel = member_channel.channel
if set(channel.get_members()) == set(member_list):
break
else:
channel = ChatChannelModel().create(member_list)
return Response(self.serializer_class(channel).data)