diff --git a/chat_box/__init__.py b/chat_box/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/chat_box/admin.py b/chat_box/admin.py new file mode 100644 index 0000000..8c38f3f --- /dev/null +++ b/chat_box/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/chat_box/apps.py b/chat_box/apps.py new file mode 100644 index 0000000..fbc3ca1 --- /dev/null +++ b/chat_box/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + + +class ChatBoxConfig(AppConfig): + name = 'chat_box' diff --git a/chat_box/consumers.py b/chat_box/consumers.py new file mode 100644 index 0000000..d200968 --- /dev/null +++ b/chat_box/consumers.py @@ -0,0 +1,47 @@ +import json + +from channels.generic.websocket import AsyncWebsocketConsumer + + +class ChatConsumer(AsyncWebsocketConsumer): + async def connect(self): + self.room_name = 'common' + self.room_group_name = 'chat_%s' % self.room_name + + # Join room group + await self.channel_layer.group_add( + self.room_group_name, + self.channel_name, + ) + + await self.accept() + + async def disconnect(self, close_code): + # Leave room group + await self.channel_layer.group_discard( + self.room_group_name, + self.channel_name, + ) + + # Receive message from WebSocket + async def receive(self, text_data): + text_data_json = json.loads(text_data) + message = text_data_json['message'] + + # Send message to room group + await self.channel_layer.group_send( + self.room_group_name, + { + 'type': 'chat_message', + 'message': message, + }, + ) + + # Receive message from room group + async def chat_message(self, event): + message = event['message'] + + # Send message to WebSocket + await self.send(text_data=json.dumps({ + 'message': message, + })) diff --git a/chat_box/migrations/__init__.py b/chat_box/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/chat_box/models.py b/chat_box/models.py new file mode 100644 index 0000000..71a8362 --- /dev/null +++ b/chat_box/models.py @@ -0,0 +1,3 @@ +from django.db import models + +# Create your models here. diff --git a/chat_box/routing.py b/chat_box/routing.py new file mode 100644 index 0000000..f420f4f --- /dev/null +++ b/chat_box/routing.py @@ -0,0 +1,7 @@ +from django.urls import re_path + +from . import consumers + +websocket_urlpatterns = [ + re_path(r'ws/chat/', consumers.ChatConsumer), +] diff --git a/chat_box/tests.py b/chat_box/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/chat_box/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/chat_box/views.py b/chat_box/views.py new file mode 100644 index 0000000..74efe07 --- /dev/null +++ b/chat_box/views.py @@ -0,0 +1,8 @@ +from django.shortcuts import render +from django.utils.translation import gettext as _ + + +def chat(request): + return render(request, 'chat/chat.html', { + 'title': _('Chat Box'), + }) diff --git a/dmoj/routing.py b/dmoj/routing.py new file mode 100644 index 0000000..42238b7 --- /dev/null +++ b/dmoj/routing.py @@ -0,0 +1,13 @@ +import chat_box.routing +from channels.auth import AuthMiddlewareStack +from channels.routing import ProtocolTypeRouter, URLRouter + + +application = ProtocolTypeRouter({ + # (http->django views is added by default) + 'websocket': AuthMiddlewareStack( + URLRouter( + chat_box.routing.websocket_urlpatterns, + ), + ), +}) diff --git a/dmoj/settings.py b/dmoj/settings.py index c875af5..1e9ab27 100644 --- a/dmoj/settings.py +++ b/dmoj/settings.py @@ -236,6 +236,8 @@ INSTALLED_APPS += ( 'statici18n', 'impersonate', 'django_jinja', + 'chat_box', + 'channels', ) MIDDLEWARE = ( @@ -500,3 +502,13 @@ TESTCASE_VISIBLE_LENGTH = 60 DATA_UPLOAD_MAX_NUMBER_FIELDS = 10240 DATA_UPLOAD_MAX_MEMORY_SIZE = 2621440 + +ASGI_APPLICATION = 'dmoj.routing.application' +CHANNEL_LAYERS = { + 'default': { + 'BACKEND': 'channels_redis.core.RedisChannelLayer', + 'CONFIG': { + "hosts": [('0.0.0.0', 6379)], + }, + }, +} diff --git a/dmoj/urls.py b/dmoj/urls.py index af93964..f302695 100644 --- a/dmoj/urls.py +++ b/dmoj/urls.py @@ -1,3 +1,4 @@ +from chat_box.views import chat from django.conf import settings from django.conf.urls import include, url from django.contrib import admin @@ -14,10 +15,9 @@ from judge.feed import AtomBlogFeed, AtomCommentFeed, AtomProblemFeed, BlogFeed, from judge.forms import CustomAuthenticationForm from judge.sitemap import BlogPostSitemap, ContestSitemap, HomePageSitemap, OrganizationSitemap, ProblemSitemap, \ SolutionSitemap, UrlSitemap, UserSitemap -from judge.views import TitledTemplateView, api, blog, comment, contests, language, license, mailgun, organization, \ - preview, problem, problem_manage, ranked_submission, register, stats, status, submission, tasks, ticket, totp, \ - user, widgets -from judge.views.about import about, custom_checker_sample +from judge.views import TitledTemplateView, about, api, blog, comment, contests, language, license, mailgun, \ + organization, preview, problem, problem_manage, ranked_submission, register, stats, status, submission, tasks, \ + ticket, totp, user, widgets from judge.views.problem_data import ProblemDataView, ProblemSubmissionDiff, \ problem_data_file, problem_init_view from judge.views.register import ActivationView, RegistrationView @@ -25,6 +25,7 @@ from judge.views.select2 import AssigneeSelect2View, CommentSelect2View, Contest ContestUserSearchSelect2View, OrganizationSelect2View, ProblemSelect2View, TicketUserSelect2View, \ UserSearchSelect2View, UserSelect2View + admin.autodiscover() register_patterns = [ @@ -362,9 +363,11 @@ urlpatterns = [ url(r'^progress$', tasks.demo_progress), ])), - url(r'^about/', about, name='about'), + url(r'^about/', about.about, name='about'), - url(r'^custom_checker_sample', custom_checker_sample, name='custom_checker_sample'), + url(r'^custom_checker_sample/', about.custom_checker_sample, name='custom_checker_sample'), + + url(r'chat/', chat, name='chat'), ] favicon_paths = ['apple-touch-icon-180x180.png', 'apple-touch-icon-114x114.png', 'android-chrome-72x72.png', diff --git a/judge/migrations/0100_auto_20200127_0059.py b/judge/migrations/0100_auto_20200127_0059.py new file mode 100644 index 0000000..e070f7a --- /dev/null +++ b/judge/migrations/0100_auto_20200127_0059.py @@ -0,0 +1,21 @@ +# Generated by Django 2.2.9 on 2020-01-27 00:59 + +import django.core.validators +from django.db import migrations, models +import judge.models.problem_data +import judge.utils.problem_data + + +class Migration(migrations.Migration): + + dependencies = [ + ('judge', '0099_custom_checker'), + ] + + operations = [ + migrations.AlterField( + model_name='problemdata', + name='custom_checker', + field=models.FileField(blank=True, null=True, storage=judge.utils.problem_data.ProblemDataStorage(), upload_to=judge.models.problem_data.problem_directory_file, validators=[django.core.validators.FileExtensionValidator(allowed_extensions=['py'])], verbose_name='custom checker file'), + ), + ] diff --git a/requirements.txt b/requirements.txt index 7df0c72..564fa30 100644 --- a/requirements.txt +++ b/requirements.txt @@ -30,3 +30,7 @@ packaging celery -e git://github.com/DMOJ/ansi2html.git#egg=ansi2html sqlparse +channels +channels-redis +docker + diff --git a/templates/chat/chat.html b/templates/chat/chat.html new file mode 100644 index 0000000..7b1eb9a --- /dev/null +++ b/templates/chat/chat.html @@ -0,0 +1,40 @@ +{% extends "base.html" %} + +{% block js_media %} + + +{% endblock js_media %} + +{% block body %} +
+
+ +{% endblock body %}