forked from openedx/openedx-platform
-
Notifications
You must be signed in to change notification settings - Fork 8
Add messenger react app #1
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
Merged
tehreem-sadat
merged 1 commit into
develop
from
tehreem/add_messenger_backend_and_react_base_messenger_page
Sep 7, 2021
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,38 @@ | ||
| """ | ||
| Admin registration for Messenger. | ||
| """ | ||
| from django.contrib import admin | ||
| from django.utils.translation import ugettext_lazy as _ | ||
|
|
||
| from openedx.features.wikimedia_features.messenger.models import ( | ||
| Message, Inbox | ||
| ) | ||
|
|
||
|
|
||
| class MessageAdmin(admin.ModelAdmin): | ||
| """ | ||
| Admin config clearesult credit providers. | ||
| """ | ||
| search_fields = ('sender__username', 'receiver__username') | ||
| list_display = ('id', 'sender', 'receiver', 'message', 'created') | ||
|
|
||
|
|
||
| class InboxAdmin(admin.ModelAdmin): | ||
| """ | ||
| Admin config for clearesult credits offered by the courses. | ||
| """ | ||
| search_fields = ('sender', 'receiver') | ||
| list_display = ('id', 'sender', 'receiver', 'message', 'unread_count') | ||
|
|
||
| def sender(self, obj): | ||
| return obj.last_message.sender.username | ||
|
|
||
| def receiver(self, obj): | ||
| return obj.last_message.receiver.username | ||
|
|
||
| def message(self, obj): | ||
| return obj.last_message.message[:20] | ||
|
|
||
|
|
||
| admin.site.register(Message, MessageAdmin) | ||
| admin.site.register(Inbox, InboxAdmin) |
Empty file.
Empty file.
109 changes: 109 additions & 0 deletions
109
openedx/features/wikimedia_features/messenger/api/v0/serializers.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,109 @@ | ||
| """ | ||
| Serializers for Messenger v0 API(s) | ||
| """ | ||
| from django.contrib.auth.models import User | ||
| from django.db import transaction | ||
| from django.utils.translation import ugettext as _ | ||
| from rest_framework import serializers | ||
| from openedx.features.wikimedia_features.messenger.models import Inbox, Message | ||
| from openedx.core.djangoapps.user_api.accounts.image_helpers import get_profile_image_urls_for_user | ||
|
|
||
|
|
||
| def validate_username(username): | ||
| try: | ||
| return User.objects.get(username=username) | ||
| except User.DoesNotExist: | ||
| raise serializers.ValidationError(_('User does not exist - invalid username {}'.format(username))) | ||
|
|
||
|
|
||
| class StringListField(serializers.ListField): | ||
| child = serializers.CharField() | ||
|
|
||
|
|
||
| class UserSerializer(serializers.ModelSerializer): | ||
| class Meta: | ||
| model = User | ||
| fields = ('username',) | ||
|
|
||
| class BulkMessageSerializer(serializers.Serializer): | ||
| receivers = StringListField() | ||
| message = serializers.CharField() | ||
|
|
||
| def validate_receivers(self, receivers): | ||
| if not receivers: | ||
| raise serializers.ValidationError(_('receiver list can not empty.')) | ||
| users = [validate_username(username) for username in receivers] | ||
| return users | ||
|
|
||
| def bulk_create(self, request=None): | ||
| created_messages = [] | ||
| if not request: | ||
| raise serializers.ValidationError(_('Missing request object.')) | ||
| receivers = self.validated_data.get('receivers') | ||
| message = self.validated_data.get('message') | ||
| with transaction.atomic(): | ||
| for user in receivers: | ||
| created_messages.append( | ||
| Message.objects.create(sender=request.user, receiver=user, message=message) | ||
| ) | ||
| return created_messages | ||
|
|
||
|
|
||
| class InboxSerializer(serializers.ModelSerializer): | ||
| with_user = serializers.SerializerMethodField() | ||
| request = None | ||
|
|
||
| class Meta: | ||
| model = Inbox | ||
| fields = ('id', 'with_user', 'last_message', 'unread_count') | ||
|
|
||
| def __init__(self, *args, **kwargs): | ||
| super(InboxSerializer, self).__init__(*args, **kwargs) | ||
| self.request = self.context.get('request') | ||
|
|
||
| def get_with_user(self, obj): | ||
| if self.request: | ||
| if obj.last_message.sender != self.request.user: | ||
| return obj.last_message.sender.username | ||
| return obj.last_message.receiver.username | ||
| raise serializers.ValidationError( | ||
| _('Invalid request - request object not found.') | ||
| ) | ||
|
|
||
| def to_representation(self, instance): | ||
| response = super().to_representation(instance) | ||
| with_user = User.objects.get(username=response.get('with_user')) | ||
| response['with_user_img'] = get_profile_image_urls_for_user(with_user, self.request).get('medium') | ||
| response['last_message'] =instance.last_message.message | ||
|
|
||
| # if last message is send by login-user then unread count will be 0 | ||
| if self.request and instance.last_message.sender == self.request.user: | ||
| response['unread_count'] = 0 | ||
|
|
||
| return response | ||
|
|
||
|
|
||
| class MessageSerializer(serializers.ModelSerializer): | ||
| receiver = serializers.CharField(source='receiver.username') | ||
| class Meta: | ||
| model = Message | ||
| fields = ('id', 'sender', 'receiver', 'message', 'created') | ||
| read_only_fields = ('id', 'created', 'sender') | ||
|
|
||
| def validate_receiver(self, receiver): | ||
| return validate_username(receiver) | ||
|
|
||
| def create(self, validated_data): | ||
| request = self.context.get('request') | ||
| validated_data['sender'] = request.user | ||
| validated_data['receiver'] = validated_data.get('receiver', {}).get('username') | ||
| return super().create(validated_data) | ||
|
|
||
| def to_representation(self, instance): | ||
| response = super().to_representation(instance) | ||
| response['sender'] = instance.sender.username | ||
| response['created'] = instance.created.strftime('%x-%I:%M %p') | ||
| response['sender_img']= get_profile_image_urls_for_user( | ||
| instance.sender, self.context.get('request') | ||
| ).get('medium') | ||
| return response |
55 changes: 55 additions & 0 deletions
55
openedx/features/wikimedia_features/messenger/api/v0/urls.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,55 @@ | ||
| """ | ||
| Urls for Messenger v0 API(s) | ||
| """ | ||
| from django.conf.urls import url | ||
|
|
||
| from openedx.features.wikimedia_features.messenger.api.v0.views import ( | ||
| InboxView, ConversationView, MessageCreateView, UserSearchView, BulkMessageView | ||
| ) | ||
|
|
||
| app_name = 'messenger_api_v0' | ||
|
|
||
|
|
||
| urlpatterns = [ | ||
| url( | ||
| r'^bulk_message/$', | ||
| BulkMessageView.as_view({ | ||
| 'post': 'bulk_message' | ||
| }), | ||
| name="bulk_message" | ||
| ), | ||
| url( | ||
| r'^user/$', | ||
| UserSearchView.as_view({ | ||
| 'get': 'list' | ||
| }), | ||
| name="user_search" | ||
| ), | ||
| url( | ||
| r'^inbox/$', | ||
| InboxView.as_view({ | ||
| 'get': 'list' | ||
| }), | ||
| name="user_inbox_list" | ||
| ), | ||
| url( | ||
| r'^inbox/(?P<pk>\d+)/$', | ||
| InboxView.as_view({ | ||
| 'patch': 'partial_update', | ||
| 'get': 'retrieve' | ||
| }), | ||
| name="user_inbox_detail" | ||
| ), | ||
| url( | ||
| r'^conversation/$', | ||
| ConversationView.as_view({ | ||
| 'get': 'list', | ||
| }), | ||
| name="conversation_list" | ||
| ), | ||
| url( | ||
| r'^message/$', | ||
| MessageCreateView.as_view(), | ||
| name="message_create" | ||
| ), | ||
| ] |
190 changes: 190 additions & 0 deletions
190
openedx/features/wikimedia_features/messenger/api/v0/views.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,190 @@ | ||
| """ | ||
| Views for Messenger v0 API(s) | ||
| """ | ||
| from django.contrib.auth.models import User | ||
| from django.utils.translation import ugettext as _ | ||
| from rest_framework import generics | ||
| from rest_framework import permissions | ||
| from rest_framework import viewsets | ||
| from rest_framework.authentication import SessionAuthentication | ||
| from rest_framework.exceptions import NotFound, status | ||
| from rest_framework.pagination import PageNumberPagination | ||
| from rest_framework.response import Response | ||
|
|
||
| from openedx.features.wikimedia_features.messenger.models import Inbox, Message | ||
| from openedx.features.wikimedia_features.messenger.api.v0.serializers import ( | ||
| InboxSerializer, MessageSerializer, UserSerializer, BulkMessageSerializer | ||
| ) | ||
|
|
||
|
|
||
| class MesssengerResultsSetPagination(PageNumberPagination): | ||
| page_size = 15 | ||
|
|
||
| def get_paginated_response(self, data): | ||
| response = super(MesssengerResultsSetPagination, self).get_paginated_response(data) | ||
| response.data['num_pages'] = self.page.paginator.num_pages | ||
| response.data['count']: self.page.paginator.count | ||
| return response | ||
|
|
||
|
|
||
| class UserSearchView(viewsets.ReadOnlyModelViewSet): | ||
| """ | ||
| Search user's username containing given query string. | ||
|
|
||
| GET /messenger/api/v0/user/?search=name | ||
| Return list of users | ||
| [ | ||
| { | ||
| "username": "username1" | ||
| }, | ||
| ... | ||
| ] | ||
| """ | ||
| serializer_class = UserSerializer | ||
| authentication_classes = (SessionAuthentication,) | ||
| permission_classes = (permissions.IsAuthenticated,) | ||
|
|
||
| def get_queryset(self): | ||
| query = self.request.GET.get('search', '') | ||
| if query: | ||
| return User.objects.filter( | ||
| username__icontains=query | ||
| ).exclude(username=self.request.user.username) | ||
| raise NotFound(_('Search query param is required.')) | ||
|
|
||
|
|
||
| class InboxView(viewsets.ModelViewSet): | ||
| """ | ||
| Returns list of all inbox messages of request user | ||
| Get /messenger/api/v0/inbox/ | ||
| { | ||
| "count": 2, | ||
| "next": null, | ||
| "previous": null, | ||
| "num_pages": 1 | ||
| "results": [ | ||
| { | ||
| "id": 37, | ||
| "with_user": "staff", | ||
| "last_message": "hello, how are you d...", | ||
| "unread_count": 0, | ||
| "with_user_img": "profile_image_url" | ||
| }, | ||
| ... | ||
| ], | ||
| } | ||
|
|
||
| Retrieve single inbox message object | ||
| Get /messenger/api/v0/inbox/pk | ||
| { | ||
| "id": 37, | ||
| "with_user": "staff", | ||
| "last_message": "hello, how are you d...", | ||
| "unread_count": 0, | ||
| "with_user_img": "profile_image_url" | ||
| } | ||
| ``` | ||
|
|
||
| Update inbox message object | ||
| PATCH /messenger/api/v0/inbox/pk | ||
| { | ||
| "unread_count": 2, | ||
| } | ||
| ``` | ||
|
|
||
| Note: | ||
| - user can view and update only his inbox resources | ||
|
|
||
| """ | ||
| authentication_classes = (SessionAuthentication,) | ||
| permission_classes = (permissions.IsAuthenticated,) | ||
| serializer_class = InboxSerializer | ||
| pagination_class = MesssengerResultsSetPagination | ||
|
|
||
| def get_queryset(self): | ||
| return Inbox.user_inbox.find_all(self.request.user) | ||
|
|
||
|
|
||
| class ConversationView(viewsets.ModelViewSet): | ||
| """ | ||
| Return conversation between two users -> All messages sent or received between request.user and | ||
| user with given username. | ||
| GET /conversation/?with_user=username2 | ||
| [ | ||
| { | ||
| "id": 2093, | ||
| "sender": "honor", | ||
| "receiver": "edx", | ||
| "message": "what's the progress on task1 ?", | ||
| "created": "08/31/21-01:09 PM", | ||
| "sender_img": "sender_profile_image url" | ||
| }, | ||
| ... | ||
| ] | ||
| """ | ||
| authentication_classes = (SessionAuthentication,) | ||
| permission_classes = (permissions.IsAuthenticated, ) | ||
| serializer_class = MessageSerializer | ||
| pagination_class = MesssengerResultsSetPagination | ||
|
|
||
| def get_queryset(self): | ||
| with_user = self.request.GET.get('with_user', '') | ||
| if not with_user: | ||
| raise NotFound(_('with_user param is required.')) | ||
|
|
||
| try: | ||
| with_user = User.objects.get(username=with_user) | ||
| return Message.chat.history(self.request.user, with_user) | ||
| except User.DoesNotExist: | ||
| raise NotFound(_('User with username: {} does not exist.'.format(with_user))) | ||
|
|
||
|
|
||
| class MessageCreateView(generics.CreateAPIView): | ||
| """ | ||
| Create Single Message - | ||
| POST /messenger/api/v0/message/ | ||
| { | ||
| "receiver": "receiver_username", | ||
| "message": "sample message text" | ||
| } | ||
| Return newly created message | ||
| { | ||
| "id": 2092, | ||
| "sender": "sender_username", | ||
| "receiver": "receiver_username", | ||
| "message": "sample message text", | ||
| "created": "08/31/21-01:08 PM", | ||
| "sender_img": "sender_profile_image_url" | ||
| } | ||
|
|
||
| """ | ||
| authentication_classes = (SessionAuthentication,) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. we aren't consistent with this authentication class, this isn't being used in Bulk creation. |
||
| permission_classes = (permissions.IsAuthenticated,) | ||
| serializer_class = MessageSerializer | ||
|
|
||
|
|
||
| class BulkMessageView(viewsets.ViewSet): | ||
| """ | ||
| Create Bulk messages from request.user | ||
|
|
||
| POST /messenger/api/v0/bulk_message/ | ||
| POST DATA: { | ||
| "receivers": [username1, username2] | ||
| "message": "message sample text" | ||
| } | ||
| """ | ||
| serializer_class = BulkMessageSerializer | ||
| permission_classes = (permissions.IsAuthenticated,) | ||
|
|
||
| def bulk_message(self, *ags, **kwargs): | ||
| serializer = BulkMessageSerializer(data=self.request.data) | ||
| if serializer.is_valid(raise_exception=True): | ||
| created_msgs = serializer.bulk_create(request=self.request) | ||
| return Response( | ||
| data=InboxSerializer( | ||
| Inbox.objects.filter(last_message__in=created_msgs), | ||
| context={'request': self.request}, | ||
| many=True | ||
| ).data, | ||
| status=status.HTTP_200_OK | ||
| ) | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this docstring needs to be updated now