diff --git a/src/api/serializers.py b/src/api/serializers.py index 2434911e88e10b6c9caf6e33a4e7cb9292c95941..e0341bd899ad99a43ac6e8fe8549e497d41c0611 100644 --- a/src/api/serializers.py +++ b/src/api/serializers.py @@ -11,9 +11,10 @@ from core.models.assemblies import Assembly from core.models.badges import Badge, BadgeToken, BadgeTokenTimeConstraint from core.models.conference import Conference, ConferenceMember, ConferenceTrack from core.models.events import Event +from core.models.messages import DirectMessage from core.models.metanavi import MetaNavItem from core.models.rooms import Room -from core.models.users import UserTimelineEntry +from core.models.users import PlatformUser, UserTimelineEntry class ParameterisedHyperlinkedIdentityField(HyperlinkedIdentityField): @@ -297,3 +298,50 @@ class MetaNavItemSerializer(HubModelSerializer): 'graphic_light', 'graphic_dark', ] + + +class PlatformUserVisibleSerializer(serializers.ModelSerializer): + class Meta: + model = PlatformUser + fields = ['username', 'display_name'] + read_only_fields = fields + + +class DirectMessageSerializerSentShort(HubModelSerializer): + class Meta: + model = DirectMessage + fields = ['id', 'sender', 'recipient', 'timestamp', 'in_reply_to', 'subject'] + read_only_fields = fields + + sender = PlatformUserVisibleSerializer(read_only=True) + recipient = PlatformUserVisibleSerializer(read_only=True) + + +class DirectMessageSerializerReceivedShort(HubModelSerializer): + class Meta: + model = DirectMessage + fields = [*DirectMessageSerializerSentShort.Meta.fields, 'has_responded'] + read_only_fields = fields + + sender = PlatformUserVisibleSerializer(read_only=True) + recipient = PlatformUserVisibleSerializer(read_only=True) + + +class DirectMessageSerializerSent(HubModelSerializer): + class Meta: + model = DirectMessage + fields = [*DirectMessageSerializerSentShort.Meta.fields, 'body'] + read_only_fields = fields + + sender = PlatformUserVisibleSerializer(read_only=True) + recipient = PlatformUserVisibleSerializer(read_only=True) + + +class DirectMessageSerializerReceived(HubModelSerializer): + class Meta: + model = DirectMessage + fields = [*DirectMessageSerializerReceivedShort.Meta.fields, 'body'] + read_only_fields = [f for f in fields if f != 'was_read'] + + sender = PlatformUserVisibleSerializer(read_only=True) + recipient = PlatformUserVisibleSerializer(read_only=True) diff --git a/src/api/tests/__init__.py b/src/api/tests/__init__.py index 8fb7973a17a79486bf8775d3b886df46eb305234..5f3d708f7a0cb7923a0e711001468b5e408cf603 100644 --- a/src/api/tests/__init__.py +++ b/src/api/tests/__init__.py @@ -1,7 +1,9 @@ from .badges import * # noqa: F401, F403 from .bbb import * # noqa: F401, F403 from .engelsystem import * # noqa: F401, F403 +from .events import * # noqa: F401, F403 from .map import * # noqa: F401, F403 +from .messages import * # noqa: F401, F403 from .metrics import * # noqa: F401, F403 from .schedule import * # noqa: F401, F403 from .workadventure import * # noqa: F401, F403 diff --git a/src/api/tests/events.py b/src/api/tests/events.py new file mode 100644 index 0000000000000000000000000000000000000000..581e8f958a64f6ad37531bd4dfaf47ee045746b4 --- /dev/null +++ b/src/api/tests/events.py @@ -0,0 +1,73 @@ +import uuid +from datetime import datetime, timedelta + +from pytz import UTC + +from django.test import Client, TestCase, override_settings +from django.urls import reverse + +from core.models import Assembly, Conference, ConferenceMember, Event, PlatformUser + +TEST_CONF_ID = uuid.uuid4() + + +@override_settings(SELECTED_CONFERENCE_ID=TEST_CONF_ID) +class EventsTestCase(TestCase): + def setUp(self): + self.conference1 = Conference(id=TEST_CONF_ID, slug='foo', name='Foo Conference', is_public=True) + self.conference1.save() + + self.human_user = PlatformUser(username='bernd', user_type=PlatformUser.Type.HUMAN) + self.human_user.save() + self.human_cm = ConferenceMember(conference=self.conference1, user=self.human_user) + self.human_cm.save() + + assembly = Assembly(conference=self.conference1, name='DUMMY', slug='dummy', state_assembly=Assembly.State.ACCEPTED, is_official=True) + assembly.save() + + self.event = Event( + conference=self.conference1, + slug='event1', + assembly=assembly, + name='Event1_1', + is_public=True, + schedule_start=datetime(2020, 1, 2, 0, 0, 0, tzinfo=UTC), + schedule_duration=timedelta(minutes=45), + kind=Event.Kind.OFFICIAL, + ) + self.event.save() + + def test_EventMyFavorites(self): + c = Client() + resp = c.get(reverse('api:my-events')) + self.assertEqual(resp.status_code, 403) + c.force_login(self.human_user) + + resp = c.get(reverse('api:my-events')) + self.assertEqual(resp.json(), []) + + self.human_user.favorite_events.add(self.event) + resp = c.get(reverse('api:my-events')) + self.assertEqual(resp.json(), [str(self.event.pk)]) + + def test_EventMyFavoritesSet_put(self): + c = Client() + resp = c.put(reverse('api:my-events-set', kwargs={'pk': str(uuid.uuid4())})) + self.assertEqual(resp.status_code, 403) + c.force_login(self.human_user) + + self.assertEqual(list(self.human_user.favorite_events.all()), []) + resp = c.put(reverse('api:my-events-set', kwargs={'pk': str(self.event.pk)})) + self.assertEqual(list(self.human_user.favorite_events.all()), [self.event]) + + def test_EventMyFavoritesSet_delete(self): + c = Client() + resp = c.delete(reverse('api:my-events-set', kwargs={'pk': str(uuid.uuid4())})) + self.assertEqual(resp.status_code, 403) + c.force_login(self.human_user) + + self.human_user.favorite_events.add(self.event) + + self.assertEqual(list(self.human_user.favorite_events.all()), [self.event]) + resp = c.delete(reverse('api:my-events-set', kwargs={'pk': str(self.event.pk)})) + self.assertEqual(list(self.human_user.favorite_events.all()), []) diff --git a/src/api/tests/messages.py b/src/api/tests/messages.py new file mode 100644 index 0000000000000000000000000000000000000000..b80b0e9f64a3275ee6a71471de3f4f2ed875460e --- /dev/null +++ b/src/api/tests/messages.py @@ -0,0 +1,261 @@ +import uuid +from datetime import datetime + +from pytz import UTC + +from django.test import Client, TestCase, override_settings +from django.urls import reverse +from django.utils.timezone import localtime + +from core.models import Conference, ConferenceMember, DirectMessage, PlatformUser + +TEST_CONF_ID = uuid.uuid4() + + +@override_settings(SELECTED_CONFERENCE_ID=TEST_CONF_ID) +class MessagesTestCase(TestCase): + def setUp(self): + self.conference1 = Conference(id=TEST_CONF_ID, slug='foo', name='Foo Conference', is_public=True) + self.conference1.save() + + self.human_user1 = PlatformUser(username='bernd', user_type=PlatformUser.Type.HUMAN) + self.human_user1.save() + self.human_cm1 = ConferenceMember(conference=self.conference1, user=self.human_user1) + self.human_cm1.save() + + self.human_user2 = PlatformUser(username='björnd', user_type=PlatformUser.Type.HUMAN) + self.human_user2.save() + self.human_cm2 = ConferenceMember(conference=self.conference1, user=self.human_user2) + self.human_cm2.save() + + def test_DirectMessagesReceived(self): + c = Client() + resp = c.get(reverse('api:my-messages-received')) + self.assertEqual(resp.status_code, 403) + c.force_login(self.human_user1) + + resp = c.get(reverse('api:my-messages-received')) + self.assertEqual(resp.json(), []) + + dm = DirectMessage( + conference=self.conference1, + sender=self.human_user2, + recipient=self.human_user1, + timestamp=datetime(2020, 5, 4, 3, 2, 1, tzinfo=UTC), + subject='Test Message2', + body='Message Body2', + ) + dm.save() + resp = c.get(reverse('api:my-messages-received')) + self.assertEqual( + resp.json(), + [ + { + 'id': str(dm.pk), + 'sender': {'username': self.human_user2.username, 'display_name': self.human_user2.display_name}, + 'recipient': {'username': self.human_user1.username, 'display_name': self.human_user1.display_name}, + 'timestamp': localtime(dm.timestamp).isoformat(), + 'in_reply_to': None, + 'subject': dm.subject, + 'has_responded': False, + } + ], + ) + + dm.has_responded = True + dm.deleted_by_sender = True + dm.save() + resp = c.get(reverse('api:my-messages-received')) + self.assertEqual( + resp.json(), + [ + { + 'id': str(dm.pk), + 'sender': {'username': self.human_user2.username, 'display_name': self.human_user2.display_name}, + 'recipient': {'username': self.human_user1.username, 'display_name': self.human_user1.display_name}, + 'timestamp': localtime(dm.timestamp).isoformat(), + 'in_reply_to': None, + 'subject': dm.subject, + 'has_responded': True, + } + ], + ) + + dm.deleted_by_recipient = True + dm.save() + resp = c.get(reverse('api:my-messages-received')) + self.assertEqual(resp.json(), []) + + def test_DirectMessagesSent(self): + c = Client() + resp = c.get(reverse('api:my-messages-sent')) + self.assertEqual(resp.status_code, 403) + c.force_login(self.human_user1) + + resp = c.get(reverse('api:my-messages-sent')) + self.assertEqual(resp.json(), []) + + dm = DirectMessage( + conference=self.conference1, + sender=self.human_user1, + recipient=self.human_user2, + timestamp=datetime(2020, 1, 2, 3, 4, 5, tzinfo=UTC), + subject='Test Message', + body='Message Body', + ) + dm.save() + resp = c.get(reverse('api:my-messages-sent')) + self.assertEqual( + resp.json(), + [ + { + 'id': str(dm.pk), + 'sender': {'username': self.human_user1.username, 'display_name': self.human_user1.display_name}, + 'recipient': {'username': self.human_user2.username, 'display_name': self.human_user2.display_name}, + 'timestamp': localtime(dm.timestamp).isoformat(), + 'in_reply_to': None, + 'subject': dm.subject, + } + ], + ) + + dm.deleted_by_recipient = True + dm.save() + resp = c.get(reverse('api:my-messages-sent')) + self.assertEqual(len(resp.json()), 1) + + dm.deleted_by_sender = True + dm.save() + resp = c.get(reverse('api:my-messages-sent')) + self.assertEqual(resp.json(), []) + + def test_DirectMessageReceived(self): + dm = DirectMessage( + conference=self.conference1, + sender=self.human_user2, + recipient=self.human_user1, + timestamp=datetime(2020, 1, 2, 3, 4, 5, tzinfo=UTC), + subject='Test Message', + body='Message Body', + ) + dm.save() + + other_dm = DirectMessage( + conference=self.conference1, + sender=self.human_user2, + recipient=self.human_user2, + timestamp=datetime(2020, 1, 2, 3, 4, 5, tzinfo=UTC), + subject='Test Message', + body='Message Body', + ) + other_dm.save() + + c = Client() + resp = c.get(reverse('api:my-message-received', kwargs={'pk': str(dm.pk)})) + self.assertEqual(resp.status_code, 403) + c.force_login(self.human_user1) + + resp = c.get(reverse('api:my-message-received', kwargs={'pk': str(other_dm.pk)})) + self.assertEqual(resp.status_code, 404) + + resp = c.get(reverse('api:my-message-received', kwargs={'pk': str(dm.pk)})) + self.assertEqual( + resp.json(), + { + 'id': str(dm.pk), + 'sender': {'username': self.human_user2.username, 'display_name': self.human_user2.display_name}, + 'recipient': {'username': self.human_user1.username, 'display_name': self.human_user1.display_name}, + 'timestamp': localtime(dm.timestamp).isoformat(), + 'in_reply_to': None, + 'subject': dm.subject, + 'body': dm.body, + 'has_responded': False, + }, + ) + + dm.has_responded = True + dm.deleted_by_sender = True + dm.save() + resp = c.get(reverse('api:my-message-received', kwargs={'pk': str(dm.pk)})) + self.assertEqual( + resp.json(), + { + 'id': str(dm.pk), + 'sender': {'username': self.human_user2.username, 'display_name': self.human_user2.display_name}, + 'recipient': {'username': self.human_user1.username, 'display_name': self.human_user1.display_name}, + 'timestamp': localtime(dm.timestamp).isoformat(), + 'in_reply_to': None, + 'subject': dm.subject, + 'body': dm.body, + 'has_responded': True, + }, + ) + + dm.deleted_by_recipient = True + dm.save() + resp = c.get(reverse('api:my-message-received', kwargs={'pk': str(other_dm.pk)})) + self.assertEqual(resp.status_code, 404) + + def test_DirectMessageSent(self): + dm = DirectMessage( + conference=self.conference1, + sender=self.human_user1, + recipient=self.human_user2, + timestamp=datetime(2020, 1, 2, 3, 4, 5, tzinfo=UTC), + subject='Test Message', + body='Message Body', + ) + dm.save() + + other_dm = DirectMessage( + conference=self.conference1, + sender=self.human_user2, + recipient=self.human_user2, + timestamp=datetime(2020, 1, 2, 3, 4, 5, tzinfo=UTC), + subject='Test Message', + body='Message Body', + ) + other_dm.save() + + c = Client() + resp = c.get(reverse('api:my-message-sent', kwargs={'pk': str(dm.pk)})) + self.assertEqual(resp.status_code, 403) + c.force_login(self.human_user1) + + resp = c.get(reverse('api:my-message-sent', kwargs={'pk': str(other_dm.pk)})) + self.assertEqual(resp.status_code, 404) + + resp = c.get(reverse('api:my-message-sent', kwargs={'pk': str(dm.pk)})) + self.assertEqual( + resp.json(), + { + 'id': str(dm.pk), + 'sender': {'username': self.human_user1.username, 'display_name': self.human_user1.display_name}, + 'recipient': {'username': self.human_user2.username, 'display_name': self.human_user2.display_name}, + 'timestamp': localtime(dm.timestamp).isoformat(), + 'in_reply_to': None, + 'subject': dm.subject, + 'body': dm.body, + }, + ) + + dm.deleted_by_recipient = True + dm.save() + resp = c.get(reverse('api:my-message-sent', kwargs={'pk': str(dm.pk)})) + self.assertEqual( + resp.json(), + { + 'id': str(dm.pk), + 'sender': {'username': self.human_user1.username, 'display_name': self.human_user1.display_name}, + 'recipient': {'username': self.human_user2.username, 'display_name': self.human_user2.display_name}, + 'timestamp': localtime(dm.timestamp).isoformat(), + 'in_reply_to': None, + 'subject': dm.subject, + 'body': dm.body, + }, + ) + + dm.deleted_by_sender = True + dm.save() + resp = c.get(reverse('api:my-message-sent', kwargs={'pk': str(other_dm.pk)})) + self.assertEqual(resp.status_code, 404) diff --git a/src/api/urls.py b/src/api/urls.py index d10c632e9ea06adbb8e5905bf619a5fbd03f60ee..cc96f91ff392a4374f4442305095f678406579ec 100644 --- a/src/api/urls.py +++ b/src/api/urls.py @@ -4,7 +4,7 @@ from rest_framework.urlpatterns import format_suffix_patterns from django.conf import settings from django.urls import path -from .views import api_root, assemblies, badges, bbb, conferencemember, conferences, events, maps, metanav, rooms, schedule, users, workadventure +from .views import api_root, assemblies, badges, bbb, conferencemember, conferences, events, maps, messages, metanav, rooms, schedule, users, workadventure app_name = 'api' urlpatterns = [ @@ -13,6 +13,12 @@ urlpatterns = [ path('me', users.profile, name='profile'), path('me/badges.zip', users.BadgeExportView.as_view(), name='badge-export'), path('me/badges', users.badges, name='badges'), + path('me/events', events.EventMyFavorites.as_view(), name='my-events'), + path('me/events/<uuid:pk>', events.EventMyFavoritesSet.as_view(), name='my-events-set'), + path('me/received-messages/', messages.DirectMessagesReceived.as_view(), name='my-messages-received'), + path('me/sent-messages/', messages.DirectMessagesSent.as_view(), name='my-messages-sent'), + path('me/received-messages/<uuid:pk>', messages.DirectMessageReceived.as_view(), name='my-message-received'), + path('me/sent-messages/<uuid:pk>', messages.DirectMessageSent.as_view(), name='my-message-sent'), path('me/friends', users.friends, name='friends'), path('me/timeline', users.UserTimelineList.as_view(), name='timeline-list'), # conference-specific views diff --git a/src/api/views/events.py b/src/api/views/events.py index 44aec250388c25dbbe17f0e5cb5d2264e22730df..dccc9dd8ee1b305a599b6fb831013b0bc75714b9 100644 --- a/src/api/views/events.py +++ b/src/api/views/events.py @@ -1,4 +1,6 @@ -from rest_framework import generics +from rest_framework import generics, permissions +from rest_framework.response import Response +from rest_framework.views import APIView from django.shortcuts import get_object_or_404 @@ -21,3 +23,22 @@ class EventDetail(ConferenceSlugMixin, generics.RetrieveAPIView): def get_object(self, **kwargs): event_id = self.request.resolver_match.kwargs['pk'] return get_object_or_404(Event.objects.conference_accessible(conference=self.conference), pk=event_id) + + +class EventMyFavorites(ConferenceSlugMixin, generics.ListAPIView): + permission_classes = [permissions.IsAuthenticated] + + def list(self, request, *args, **kwargs): + return Response(self.request.user.favorite_events.values_list('pk', flat=True)) + + +class EventMyFavoritesSet(ConferenceSlugMixin, APIView): + permission_classes = [permissions.IsAuthenticated] + + def put(self, request, *args, pk=None, **kwargs): + self.request.user.favorite_events.add(*Event.objects.conference_accessible(conference=self.conference).filter(pk=pk)) + return Response(True) + + def delete(self, request, *args, pk=None, **kwargs): + self.request.user.favorite_events.remove(*Event.objects.conference_accessible(conference=self.conference).filter(pk=pk)) + return Response(True) diff --git a/src/api/views/messages.py b/src/api/views/messages.py new file mode 100644 index 0000000000000000000000000000000000000000..3cf37d2bc6bd03435610d793da8b062542817b0f --- /dev/null +++ b/src/api/views/messages.py @@ -0,0 +1,54 @@ +from rest_framework import generics, permissions + +from core.models.messages import DirectMessage + +from ..serializers import DirectMessageSerializerReceived, DirectMessageSerializerReceivedShort, DirectMessageSerializerSent, DirectMessageSerializerSentShort +from .mixins import ConferenceSlugMixin + + +class DirectMessagesReceived(ConferenceSlugMixin, generics.ListAPIView): + permission_classes = [permissions.IsAuthenticated] + serializer_class = DirectMessageSerializerReceivedShort + + def get_queryset(self): + return ( + DirectMessage.objects.filter(conference=self.conference, recipient=self.request.user, deleted_by_recipient=False) + .select_related('recipient', 'sender') + .order_by('-timestamp') + ) + + +class DirectMessagesSent(ConferenceSlugMixin, generics.ListAPIView): + permission_classes = [permissions.IsAuthenticated] + serializer_class = DirectMessageSerializerSentShort + + def get_queryset(self): + return ( + DirectMessage.objects.filter(conference=self.conference, sender=self.request.user, deleted_by_sender=False) + .select_related('recipient', 'sender') + .order_by('-timestamp') + ) + + +class DirectMessageReceived(ConferenceSlugMixin, generics.RetrieveUpdateAPIView): + permission_classes = [permissions.IsAuthenticated] + serializer_class = DirectMessageSerializerReceived + + def get_queryset(self): + return ( + DirectMessage.objects.filter(conference=self.conference, recipient=self.request.user, deleted_by_recipient=False) + .select_related('recipient', 'sender') + .order_by('-timestamp') + ) + + +class DirectMessageSent(ConferenceSlugMixin, generics.RetrieveAPIView): + permission_classes = [permissions.IsAuthenticated] + serializer_class = DirectMessageSerializerSent + + def get_queryset(self): + return ( + DirectMessage.objects.filter(conference=self.conference, sender=self.request.user, deleted_by_sender=False) + .select_related('recipient', 'sender') + .order_by('-timestamp') + )