Skip to content

Commit

Permalink
Feature/discussion messages actions (#314)
Browse files Browse the repository at this point in the history
* feat: add discussions drawio file with the db tables

* feat: create discussions app

* feat: add discussion related models

* feat: update models, add notify methods

* feat: fix migrations

* feat: separate models into two files for discussions and messages models

* feat: add model constraints

feat: add parametrized to help write unit tests

* feat: Add base functionality for constructing the discussion endpoints

* feat: makemigrations

* feat: add discussions drawio file with the db tables

* feat: create discussions app

* feat: add discussion related models

* feat: update models, add notify methods

* feat: fix migrations

* feat: separate models into two files for discussions and messages models

* feat: add model constraints

feat: add parametrized to help write unit tests

* feat: Add base functionality for constructing the discussion endpoints

* feat: update db model diagram

* feat: add discussion messages actions

* feat: add migration file

* feat: notify user

* feat: fix discussion serialized ws
  • Loading branch information
helllllllder authored Nov 27, 2023
1 parent 3fc79f7 commit 09a79b7
Show file tree
Hide file tree
Showing 8 changed files with 291 additions and 3 deletions.
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

0 comments on commit 09a79b7

Please sign in to comment.