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 %}