Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/discussion messages actions #314

Merged
merged 22 commits into from
Nov 27, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
05b238e
feat: add discussions drawio file with the db tables
helllllllder Oct 25, 2023
cdfe0c4
feat: create discussions app
helllllllder Oct 25, 2023
76ace19
feat: add discussion related models
helllllllder Oct 25, 2023
78d07c6
feat: update models, add notify methods
helllllllder Oct 30, 2023
631fb66
feat: fix migrations
helllllllder Oct 30, 2023
249baa5
feat: separate models into two files for discussions and messages models
helllllllder Oct 30, 2023
16f20ee
feat: add model constraints
helllllllder Nov 8, 2023
b433685
feat: Add base functionality for constructing the discussion endpoints
helllllllder Nov 22, 2023
b7f3bb0
feat: makemigrations
helllllllder Nov 22, 2023
e70081c
feat: add discussions drawio file with the db tables
helllllllder Oct 25, 2023
5feb2d1
feat: create discussions app
helllllllder Oct 25, 2023
45fa050
feat: add discussion related models
helllllllder Oct 25, 2023
001c81f
feat: update models, add notify methods
helllllllder Oct 30, 2023
b56cb36
feat: fix migrations
helllllllder Oct 30, 2023
267cac7
feat: separate models into two files for discussions and messages models
helllllllder Oct 30, 2023
b46263e
feat: add model constraints
helllllllder Nov 8, 2023
60ff393
feat: Add base functionality for constructing the discussion endpoints
helllllllder Nov 22, 2023
56dcd78
feat: update db model diagram
helllllllder Nov 23, 2023
94da6de
feat: add discussion messages actions
helllllllder Nov 23, 2023
ad3ea98
feat: add migration file
helllllllder Nov 27, 2023
7698719
feat: notify user
helllllllder Nov 27, 2023
c5c51e4
feat: fix discussion serialized ws
helllllllder Nov 27, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions chats/apps/discussions/models/discussion_message.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,11 @@ def media(self):

@property
def serialized_ws_data(self):
# TODO: add serializer when creating message endpoints
return {}
from ..serializers.discussion_messages import ( # noqa
DiscussionReadMessageSerializer,
)

return DiscussionReadMessageSerializer(self).data

@property
def notification_groups(self) -> list:
Expand Down
3 changes: 3 additions & 0 deletions chats/apps/discussions/serializers/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
from .discussion_users import DiscussionUserListSerializer # noqa
from .discussion_messages import DiscussionCreateMessageSerializer # noqa
from .discussion_messages import DiscussionReadMessageSerializer # noqa
from .discussion_messages import MessageAndMediaSimpleSerializer # noqa
from .discussions import DiscussionCreateSerializer # noqa
from .discussions import DiscussionDetailSerializer # noqa
from .discussions import DiscussionListSerializer # noqa
56 changes: 56 additions & 0 deletions chats/apps/discussions/serializers/discussion_messages.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
from rest_framework import serializers

from chats.apps.api.v1.accounts.serializers import UserNameEmailSerializer

from ..models import DiscussionMessage, DiscussionMessageMedia


class DiscussionCreateMessageSerializer(serializers.Serializer):
text = serializers.CharField(required=True)


"""
{
"content_type": "audio/wav",
"created_on": "2022-12-15T18:06:45.654327-03:00",
"message": "28e04b5a-9e70-4826-bd24-fed837661495",
"url": "http://domain.com/recording.wav"
}
"""


class MessageMediaSimpleSerializer(serializers.ModelSerializer):
url = serializers.SerializerMethodField(read_only=True)

class Meta:
model = DiscussionMessageMedia
fields = [
"content_type",
"url",
"created_on",
]

def get_url(self, media: DiscussionMessageMedia):
return media.url


class DiscussionReadMessageSerializer(serializers.ModelSerializer):
user = UserNameEmailSerializer(many=False, required=False, read_only=True)
media = MessageMediaSimpleSerializer(many=True, required=False)

class Meta:
model = DiscussionMessage
fields = [
"uuid",
"user",
"discussion",
"text",
"media",
"created_on",
]


class MessageAndMediaSimpleSerializer(serializers.Serializer):
text = serializers.CharField(required=False)
content_type = serializers.CharField(required=True)
media_file = serializers.FileField(required=True)
111 changes: 111 additions & 0 deletions chats/apps/discussions/tests/test_discussion_msgs_actions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
from django.urls import reverse
from parameterized import parameterized
from rest_framework import status
from rest_framework.test import APITestCase


class CreateDiscussionMessageViewActionTests(APITestCase):
# ("Scenario description", room, queue, subject, initial_message, user_token, expected_response_status)
fixtures = [
"chats/fixtures/fixture_app.json",
"chats/fixtures/fixture_discussion.json",
]

parameters = [
# Success parameters
(
"Added user can send messages to the discussion",
"3c2d1694-8db9-4f09-976b-e263f9d79c99",
"super large giant text phrase very cool v2",
"d7fddba0b1dfaad72aa9e21876cbc93caa9ce3fa",
status.HTTP_201_CREATED,
),
(
"Outside room user cannot send messages to the discussion",
"3c2d1694-8db9-4f09-976b-e263f9d79c99",
"super large giant text phrase very cool v2",
"a0358e20c8755568189d3a7e688ac3ec771317e2",
status.HTTP_403_FORBIDDEN,
),
(
"Outside room admin cannot send messages to the discussion",
"3c2d1694-8db9-4f09-976b-e263f9d79c99",
"super large giant text phrase very cool v2",
"4215e6d6666e54f7db9f98100533aa68909fd855",
status.HTTP_403_FORBIDDEN,
),
]

def _create_discussion_user(self, token, discussion, body):
url = (
reverse("discussion-detail", kwargs={"uuid": discussion}) + "send_messages/"
)
client = self.client
client.credentials(HTTP_AUTHORIZATION="Token " + token)
response = client.post(url, format="json", data=body)
return response

@parameterized.expand(parameters)
def test_send_messages_to_discussion(
self, _, discussion, text, token, expected_status
):
discussion_data = {
"text": text,
}

response = self._create_discussion_user(token, discussion, discussion_data)
self.assertEqual(response.status_code, expected_status)


class ListDiscussionMsgsViewActionTests(APITestCase):
fixtures = [
"chats/fixtures/fixture_app.json",
"chats/fixtures/fixture_discussion.json",
]
parameters = [
(
"Creator can list all msgs on the discussions",
"d7fddba0b1dfaad72aa9e21876cbc93caa9ce3fa",
"3c2d1694-8db9-4f09-976b-e263f9d79c99",
status.HTTP_200_OK,
1,
),
(
"Admin can list all msgs on the discussions",
"4215e6d6666e54f7db9f98100533aa68909fd855",
"3c2d1694-8db9-4f09-976b-e263f9d79c99",
status.HTTP_200_OK,
1,
),
(
"Added user can list all msgs on the discussions",
"a0358e20c8755568189d3a7e688ac3ec771317e2",
"36584c70-aaf9-4f5c-b0c3-0547bb23879d",
status.HTTP_200_OK,
1,
),
(
"Outside project user cannot list discussion msgs",
"1218da72b087b8be7f0e2520a515e968ab866fdd",
"3c2d1694-8db9-4f09-976b-e263f9d79c99",
status.HTTP_403_FORBIDDEN,
None,
),
]

def _list_discussion_user(self, token, discussion, params={}):
url = (
reverse("discussion-detail", kwargs={"uuid": discussion}) + "list_messages/"
)
client = self.client
client.credentials(HTTP_AUTHORIZATION="Token " + token)
response = client.get(url, data=params)
return response

@parameterized.expand(parameters)
def test_discussion_msgs(
self, _, token, discussion, expected_status, expected_count
):
response = self._list_discussion_user(token=token, discussion=discussion)
self.assertEqual(response.status_code, expected_status)
self.assertEqual(response.json().get("count"), expected_count)
1 change: 1 addition & 0 deletions chats/apps/discussions/usecases/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
from .create_discussion import CreateDiscussionUseCase # noqa
from .create_message_with_media import CreateMessageWithMediaUseCase # noqa
22 changes: 22 additions & 0 deletions chats/apps/discussions/usecases/create_message_with_media.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
from ..models import DiscussionMessage


class CreateMessageWithMediaUseCase:
def __init__(self, discussion, user, msg_content: dict, notify: bool = True):
self.discussion = discussion
self.user = user
self.msg_content = msg_content
self.notify = notify

def _create_message(self, text):
return DiscussionMessage.objects.create(
discussion=self.discussion, user=self.user, text=text
)

def execute(self):
text = self.msg_content.pop("text", "")
msg = self._create_message(text)
media = msg.medias.create(**self.msg_content)
if self.notify:
msg.notify("create")
return media
89 changes: 89 additions & 0 deletions chats/apps/discussions/views/_discussion_message_actions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
from rest_framework import parsers, status
from rest_framework.decorators import action
from rest_framework.response import Response

from ..models import DiscussionMessage
from ..serializers import (
DiscussionCreateMessageSerializer,
DiscussionReadMessageSerializer,
MessageAndMediaSimpleSerializer,
)
from ..usecases import CreateMessageWithMediaUseCase


class DiscussionMessageActionsMixin:
"""This should be used with a Discussion model viewset"""

@action(
detail=True, methods=["POST"], url_name="send_messages", filterset_class=None
)
def send_messages(self, request, *args, **kwargs):
user = request.user
discussion = self.get_object()
try:
serializer = DiscussionCreateMessageSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
msg = discussion.create_discussion_message(
message=serializer.validated_data.get("text"), user=user
)
serialized_msg = DiscussionReadMessageSerializer(instance=msg)

return Response(
serialized_msg.data,
status=status.HTTP_201_CREATED,
)
except Exception as error:
return Response(
{"detail": f"{type(error)}: {error}"},
status=status.HTTP_400_BAD_REQUEST,
)

@action(
detail=True, methods=["GET"], url_name="list_messages", filterset_class=None
)
def list_messages(self, request, *args, **kwargs):
discussion = self.get_object()

queryset = DiscussionMessage.objects.filter(discussion=discussion)
ordering = request.GET.get("ordering")
if ordering:
order_list = ordering.split(",")

queryset = queryset.order_by(*order_list)

page = self.paginate_queryset(queryset)
reverse_page = request.GET.get("reverse_results")
if reverse_page:
page.reverse()

if page is not None:
serializer = DiscussionReadMessageSerializer(page, many=True)
return self.get_paginated_response(serializer.data)

serializer = DiscussionReadMessageSerializer(queryset, many=True)
return Response(serializer.data)

@action(
methods=["POST"],
detail=True,
url_name="create_media",
parser_classes=[parsers.MultiPartParser],
)
def send_media_messages(self, request, *args, **kwargs):
serializer = MessageAndMediaSimpleSerializer(data=request.data)
serializer.is_valid(raise_exception=True)

discussion = self.get_object()
user = request.user

message_media = CreateMessageWithMediaUseCase(
discussion, user, serializer.validated_data
).execute()
serialized_message = DiscussionReadMessageSerializer(
instance=message_media.message
)
headers = self.get_success_headers(serialized_message.data)

return Response(
serialized_message.data, status=status.HTTP_201_CREATED, headers=headers
)
5 changes: 4 additions & 1 deletion chats/apps/discussions/views/discussion.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,16 @@
DiscussionListSerializer,
)
from ..usecases import CreateDiscussionUseCase
from ._discussion_message_actions import DiscussionMessageActionsMixin
from ._discussion_user_actions import DiscussionUserActionsMixin
from .permissions import CanManageDiscussion

User = get_user_model()


class DiscussionViewSet(viewsets.ModelViewSet, DiscussionUserActionsMixin):
class DiscussionViewSet(
viewsets.ModelViewSet, DiscussionMessageActionsMixin, DiscussionUserActionsMixin
):
queryset = Discussion.objects.all()
filter_backends = [filters.OrderingFilter, DjangoFilterBackend]
filterset_class = DiscussionFilter
Expand Down