-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1 from ckc-org/tests_added
Populate chit_chat app
- Loading branch information
Showing
26 changed files
with
889 additions
and
72 deletions.
There are no files selected for viewing
This file contains 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
This file contains 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
This file contains 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,35 @@ | ||
from rest_framework import serializers, exceptions | ||
from django.contrib.auth import get_user_model | ||
|
||
from chit_chat.models import Room, Message | ||
|
||
|
||
User = get_user_model() | ||
|
||
|
||
class ContentSerializer(serializers.Serializer): | ||
message_type = serializers.CharField() | ||
|
||
def __init__(self, valid_message_types, *args, **kwargs): | ||
super().__init__(*args, **kwargs) | ||
self.valid_message_types = valid_message_types | ||
|
||
def validate_message_type(self, message_type): | ||
if message_type not in self.valid_message_types: | ||
raise exceptions.ValidationError(f'Must be one of the following message types: {self.valid_message_types}') | ||
return message_type | ||
|
||
|
||
class ChatMessageSerializer(serializers.Serializer): | ||
room = serializers.PrimaryKeyRelatedField(queryset=Room.objects.all()) | ||
text = serializers.CharField() | ||
user = serializers.PrimaryKeyRelatedField(queryset=User.objects.all()) | ||
|
||
def validate(self, attrs): | ||
# Make sure the user is in the room. | ||
if not attrs['room'].members.filter(pk=attrs['user'].pk).exists(): | ||
raise exceptions.ValidationError('Cannot send messages to chat rooms that you are not a member of.') | ||
return attrs | ||
|
||
def create(self, validated_data): | ||
return Message.objects.create(**validated_data) |
This file contains 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,116 @@ | ||
import json | ||
from six import string_types | ||
from importlib import import_module | ||
|
||
from django.conf import settings | ||
from rest_framework import exceptions | ||
from channels.generic.websocket import AsyncWebsocketConsumer | ||
from channels.db import database_sync_to_async | ||
|
||
from chit_chat.consumer_serializers import ChatMessageSerializer, ContentSerializer | ||
|
||
|
||
def import_callable(path_or_callable): | ||
if hasattr(path_or_callable, '__call__'): | ||
return path_or_callable | ||
else: | ||
assert isinstance(path_or_callable, string_types) | ||
package, attr = path_or_callable.rsplit('.', 1) | ||
print('package', package) | ||
return getattr(import_module(package), attr) | ||
|
||
|
||
def async_validation_exception_handler(func): | ||
async def inner(*args, **kwargs): | ||
self = args[0] | ||
assert AsyncWebsocketConsumer in self.__class__.__mro__ | ||
try: | ||
return await func(*args, **kwargs) | ||
except exceptions.ValidationError as e: | ||
errors = e.detail | ||
if 'non_field_errors' not in errors: | ||
errors = {'field_errors': errors} | ||
await self.send(text_data=json.dumps(errors)) | ||
except Exception as e: # pragma: no cover | ||
await self.send(text_data=json.dumps({'non_field_errors': ['System Error']})) | ||
raise e | ||
return inner | ||
|
||
|
||
class ChatRoomConsumer(AsyncWebsocketConsumer): | ||
MESSAGE_TYPES = [ | ||
'chat', | ||
] | ||
|
||
async def connect(self): | ||
user = self.scope['user'] | ||
if not user.is_authenticated: | ||
await self.close() | ||
return | ||
for pk in await self.get_chat_room_pks(user): | ||
await self.channel_layer.group_add(str(pk), self.channel_name) | ||
await self.accept() | ||
|
||
async def disconnect(self, close_code): | ||
for pk in await self.get_chat_room_pks(self.scope['user']): | ||
await self.channel_layer.group_discard(str(pk), self.channel_name) | ||
|
||
async def receive(self, text_data=None, bytes_data=None): | ||
user = self.scope['user'] | ||
if bytes_data is not None: | ||
text_data = bytes_data.decode() | ||
try: | ||
data = json.loads(text_data) | ||
except json.decoder.JSONDecodeError: | ||
await self.send(text_data=json.dumps({'non_field_errors': ['Invalid JSON.']})) | ||
return | ||
|
||
valid = await self.validate_content(data) | ||
if valid: | ||
message_type = data.get('message_type') | ||
|
||
if message_type == 'chat': | ||
data['user'] = user.pk | ||
|
||
if message := await self.validate_chat_message(data): | ||
await self.channel_layer.group_send( | ||
str(message.room.pk), | ||
{ | ||
'type': 'chat', | ||
'user': user.pk, | ||
'room': message.room.pk, | ||
'text': message.text, | ||
'time': message.created_when.isoformat(), | ||
} | ||
) | ||
|
||
@async_validation_exception_handler | ||
async def validate_content(self, data): | ||
serializer = ContentSerializer(ChatRoomConsumer.MESSAGE_TYPES, data=data) | ||
return serializer.is_valid(raise_exception=True) | ||
|
||
@async_validation_exception_handler | ||
@database_sync_to_async | ||
def validate_chat_message(self, data): | ||
serializers = getattr(settings, 'CKC_CHAT_SERIALIZERS', {}) | ||
chat_serializer = import_callable( | ||
serializers.get('CHAT_MESSAGE_SERIALIZER', ChatMessageSerializer) | ||
) | ||
serializer = chat_serializer(data=data) | ||
serializer.is_valid(raise_exception=True) | ||
return serializer.save() | ||
|
||
async def chat(self, event): | ||
await self.send( | ||
text_data=json.dumps({ | ||
'type': 'chat', | ||
'user': event.get('user'), | ||
'text': event.get('text'), | ||
'room': event.get('room'), | ||
'time': event.get('time'), | ||
}) | ||
) | ||
|
||
@database_sync_to_async | ||
def get_chat_room_pks(self, user): | ||
return list(user.chat_rooms.values_list('pk', flat=True)) |
This file contains 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,48 @@ | ||
# Generated by Django 3.0.7 on 2021-03-15 23:22 | ||
|
||
from django.conf import settings | ||
from django.db import migrations, models | ||
import django.db.models.deletion | ||
import django.utils.timezone | ||
|
||
|
||
class Migration(migrations.Migration): | ||
|
||
initial = True | ||
|
||
dependencies = [ | ||
migrations.swappable_dependency(settings.AUTH_USER_MODEL), | ||
] | ||
|
||
operations = [ | ||
migrations.CreateModel( | ||
name='Room', | ||
fields=[ | ||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), | ||
('created_when', models.DateTimeField(default=django.utils.timezone.now)), | ||
('members', models.ManyToManyField(related_name='chat_rooms', to=settings.AUTH_USER_MODEL)), | ||
], | ||
), | ||
migrations.CreateModel( | ||
name='RoomMembership', | ||
fields=[ | ||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), | ||
('archived', models.BooleanField(default=False)), | ||
('ignore_notifications', models.BooleanField(default=False)), | ||
('created_when', models.DateTimeField(default=django.utils.timezone.now)), | ||
('room', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='memberships', to='chit_chat.Room')), | ||
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='chat_room_memberships', to=settings.AUTH_USER_MODEL)), | ||
], | ||
), | ||
migrations.CreateModel( | ||
name='Message', | ||
fields=[ | ||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), | ||
('text', models.TextField()), | ||
('created_when', models.DateTimeField(default=django.utils.timezone.now)), | ||
('room', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='messages', to='chit_chat.Room')), | ||
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='chat_room_messages', to=settings.AUTH_USER_MODEL)), | ||
('users_who_viewed', models.ManyToManyField(related_name='chat_room_messages_viewed', to=settings.AUTH_USER_MODEL)), | ||
], | ||
), | ||
] |
Empty file.
This file contains 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,27 @@ | ||
from django.contrib.auth import get_user_model | ||
from django.utils import timezone | ||
from django.db import models | ||
|
||
|
||
User = get_user_model() | ||
|
||
|
||
class Room(models.Model): | ||
members = models.ManyToManyField(User, related_name='chat_rooms') | ||
created_when = models.DateTimeField(default=timezone.now) | ||
|
||
|
||
class RoomMembership(models.Model): | ||
room = models.ForeignKey(Room, related_name='memberships', on_delete=models.CASCADE) | ||
user = models.ForeignKey(User, related_name='chat_room_memberships', on_delete=models.CASCADE) | ||
archived = models.BooleanField(default=False) | ||
ignore_notifications = models.BooleanField(default=False) | ||
created_when = models.DateTimeField(default=timezone.now) | ||
|
||
|
||
class Message(models.Model): | ||
text = models.TextField() | ||
room = models.ForeignKey(Room, related_name='messages', on_delete=models.CASCADE) | ||
user = models.ForeignKey(User, related_name='chat_room_messages', on_delete=models.CASCADE) | ||
created_when = models.DateTimeField(default=timezone.now) | ||
users_who_viewed = models.ManyToManyField(User, related_name='chat_room_messages_viewed') |
This file contains 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,7 @@ | ||
from django.urls import re_path | ||
|
||
from . import consumers | ||
|
||
websocket_urlpatterns = [ | ||
re_path('ws/chatroom/', consumers.ChatRoomConsumer.as_asgi()), | ||
] |
This file contains 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,57 @@ | ||
from django.contrib.auth import get_user_model | ||
from rest_framework import serializers, exceptions | ||
|
||
from chit_chat.models import Room, Message | ||
from .utils import PrimaryKeyWriteSerializerReadField | ||
|
||
|
||
User = get_user_model() | ||
|
||
|
||
class MessageSerializer(serializers.ModelSerializer): | ||
class Meta: | ||
model = Message | ||
fields = ( | ||
'id', | ||
'text', | ||
'created_when', | ||
'room', | ||
'user', | ||
'users_who_viewed', | ||
) | ||
|
||
|
||
class ChatUserSerializer(serializers.ModelSerializer): | ||
class Meta: | ||
model = User | ||
fields = ( | ||
'id', | ||
'first_name', | ||
'last_name', | ||
'avatar', | ||
) | ||
|
||
|
||
class RoomSerializer(serializers.ModelSerializer): | ||
messages = MessageSerializer(read_only=True, many=True) | ||
members = PrimaryKeyWriteSerializerReadField( | ||
queryset=User.objects.all(), | ||
read_serializer=ChatUserSerializer, | ||
many=True, | ||
) | ||
|
||
class Meta: | ||
model = Room | ||
fields = ( | ||
'id', | ||
'messages', | ||
'members', | ||
) | ||
|
||
def validate_members(self, users): | ||
requestor = self.context['request'].user | ||
non_requestor_users = [user for user in users if user != requestor] | ||
if len(non_requestor_users) < 1: | ||
raise exceptions.ValidationError('Must contain at least one user other than the requestor in this list.') | ||
non_requestor_users.append(requestor) | ||
return users |
This file contains 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,32 @@ | ||
<!DOCTYPE html> | ||
<html lang="en"> | ||
<head> | ||
<meta charset="UTF-8"> | ||
<title>Chat test</title> | ||
</head> | ||
<body> | ||
<textarea id="chat_area"></textarea> | ||
|
||
<script src="https://cdnjs.cloudflare.com/ajax/libs/reconnecting-websocket/1.0.0/reconnecting-websocket.min.js" integrity="sha512-B4skI5FiLurS86aioJx9VfozI1wjqrn6aTdJH+YQUmCZum/ZibPBTX55k5d9XM6EsKePDInkLVrN7vPmJxc1qA==" crossorigin="anonymous"></script> | ||
<script> | ||
const chat_area = document.getElementById("chat_area") | ||
const ws = new WebSocket('ws://localhost:8000/ws/chatroom/') | ||
// const ws = new ReconnectingWebSocket('ws://localhost:8000/ws/chatroom/', {"Sec-WebSocket-Key": "eac1ce7c5bd217c34c11db0d0455122f8c76c452"}, {debug: true}) | ||
// const ws = new ReconnectingWebSocket('ws://localhost:8000/ws/chatroom/', "eac1ce7c5bd217c34c11db0d0455122f8c76c452", {debug: true}) | ||
|
||
ws.addEventListener('open', () => { | ||
// ws.send(JSON.stringify({"chat": "I am goku"})) | ||
}) | ||
|
||
setInterval(() => { | ||
ws.send(JSON.stringify({"message_type": "chat", "text": "I am goku", "room": "1"})) | ||
}, 2500) | ||
|
||
ws.addEventListener('onmessage', (message) => { | ||
console.log('received') | ||
console.log(message) | ||
chat_area.value += message.data + "\n" | ||
}) | ||
</script> | ||
</body> | ||
</html> |
Oops, something went wrong.