add database to chatbox

This commit is contained in:
cuom1999 2020-03-19 16:51:56 -06:00
parent cb8eb2689c
commit 112f2b57c3
14 changed files with 77 additions and 104 deletions

View file

@ -1,17 +1,19 @@
import json import json
from channels.generic.websocket import AsyncWebsocketConsumer from channels.generic.websocket import AsyncWebsocketConsumer
from .models import Message
from judge.models.profile import Profile
class ChatConsumer(AsyncWebsocketConsumer): class ChatConsumer(AsyncWebsocketConsumer):
async def connect(self): async def connect(self):
self.room_name = 'common' self.room_name = 'room'
self.room_group_name = 'chat_%s' % self.room_name self.room_group_name = 'chat_%s' % self.room_name
# Join room group # Join room group
await self.channel_layer.group_add( await self.channel_layer.group_add(
self.room_group_name, self.room_group_name,
self.channel_name, self.channel_name
) )
await self.accept() await self.accept()
@ -20,7 +22,7 @@ class ChatConsumer(AsyncWebsocketConsumer):
# Leave room group # Leave room group
await self.channel_layer.group_discard( await self.channel_layer.group_discard(
self.room_group_name, self.room_group_name,
self.channel_name, self.channel_name
) )
# Receive message from WebSocket # Receive message from WebSocket
@ -40,8 +42,23 @@ class ChatConsumer(AsyncWebsocketConsumer):
# Receive message from room group # Receive message from room group
async def chat_message(self, event): async def chat_message(self, event):
message = event['message'] message = event['message']
time = save_data_and_get_time(message)
message['time'] = format_time(time)
# Send message to WebSocket # Send message to WebSocket
await self.send(text_data=json.dumps({ await self.send(text_data=json.dumps({
'message': message, 'message': message,
})) }))
# return time
def save_data_and_get_time(message):
new_message = Message(body=message['body'],
author=Profile.objects
.get(id=message['author_id']),
)
new_message.save()
return new_message.time
def format_time(time):
return time.strftime('%H:%M %p %d-%m-%Y')

View file

@ -1,5 +1,3 @@
# based on https://github.com/narrowfail/django-channels-chat
from asgiref.sync import async_to_sync from asgiref.sync import async_to_sync
from channels.layers import get_channel_layer from channels.layers import get_channel_layer
from django.db import models from django.db import models
@ -18,19 +16,10 @@ class Message(models.Model):
time = models.DateTimeField(verbose_name=_('posted time'), auto_now_add=True) time = models.DateTimeField(verbose_name=_('posted time'), auto_now_add=True)
body = models.TextField(verbose_name=_('body of comment'), max_length=8192) body = models.TextField(verbose_name=_('body of comment'), max_length=8192)
def notify_ws_clients(self):
notification = {
'type': 'recieve_group_message',
'message': '{}'.format(self.id)
}
channel_layer = get_channel_layer()
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
new_message = self.id new_message = self.id
self.body = self.body.strip() self.body = self.body.strip()
super(Message, self).save(*args, **kwargs) super(Message, self).save(*args, **kwargs)
if new_message is None:
self.notify_ws_clients()
class Meta: class Meta:
app_label = 'chat_box' app_label = 'chat_box'

View file

@ -1,31 +1,17 @@
from django.http import HttpResponseRedirect
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
from django.views.generic import ListView from django.views.generic import ListView
from django.urls import reverse
from django.utils import timezone
from .models import Message from .models import Message
class ChatView(ListView): class ChatView(ListView):
model = Message model = Message
context_object_name = 'messages' context_object_name = 'message'
template_name = 'chat/chat.html' template_name = 'chat/chat.html'
title = _('Chat Box') title = _('Chat Box')
paginate_by = 10
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super(ChatView, self).get_context_data(**kwargs) context = super().get_context_data(**kwargs)
context['title'] = self.title context['title'] = self.title
return context return context
def get_queryset(self):
return None
def send(request):
new_message = Message(body=request.POST['message'],
author=request.profile,
time=timezone.now())
new_message.save()
return HttpResponseRedirect(reverse('chat'))

View file

@ -1,13 +1,12 @@
import chat_box.routing
from channels.auth import AuthMiddlewareStack from channels.auth import AuthMiddlewareStack
from channels.routing import ProtocolTypeRouter, URLRouter from channels.routing import ProtocolTypeRouter, URLRouter
import chat_box.routing
application = ProtocolTypeRouter({ application = ProtocolTypeRouter({
# (http->django views is added by default) # (http->django views is added by default)
'websocket': AuthMiddlewareStack( 'websocket': AuthMiddlewareStack(
URLRouter( URLRouter(
chat_box.routing.websocket_urlpatterns, chat_box.routing.websocket_urlpatterns
), )
), ),
}) })

View file

@ -503,6 +503,8 @@ TESTCASE_VISIBLE_LENGTH = 60
DATA_UPLOAD_MAX_NUMBER_FIELDS = 10240 DATA_UPLOAD_MAX_NUMBER_FIELDS = 10240
DATA_UPLOAD_MAX_MEMORY_SIZE = 2621440 DATA_UPLOAD_MAX_MEMORY_SIZE = 2621440
MESSAGES_TO_LOAD = 15
ASGI_APPLICATION = 'dmoj.routing.application' ASGI_APPLICATION = 'dmoj.routing.application'
CHANNEL_LAYERS = { CHANNEL_LAYERS = {
'default': { 'default': {

View file

@ -1,4 +1,4 @@
from chat_box.views import ChatView, send from chat_box.views import ChatView
from django.conf import settings from django.conf import settings
from django.conf.urls import include, url from django.conf.urls import include, url
from django.contrib import admin from django.contrib import admin
@ -10,6 +10,8 @@ from django.urls import reverse
from django.utils.functional import lazystr from django.utils.functional import lazystr
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django.views.generic import RedirectView from django.views.generic import RedirectView
from django.contrib.auth.decorators import login_required
from judge.feed import AtomBlogFeed, AtomCommentFeed, AtomProblemFeed, BlogFeed, CommentFeed, ProblemFeed from judge.feed import AtomBlogFeed, AtomCommentFeed, AtomProblemFeed, BlogFeed, CommentFeed, ProblemFeed
from judge.forms import CustomAuthenticationForm from judge.forms import CustomAuthenticationForm
@ -25,7 +27,6 @@ from judge.views.select2 import AssigneeSelect2View, CommentSelect2View, Contest
ContestUserSearchSelect2View, OrganizationSelect2View, ProblemSelect2View, TicketUserSelect2View, \ ContestUserSearchSelect2View, OrganizationSelect2View, ProblemSelect2View, TicketUserSelect2View, \
UserSearchSelect2View, UserSelect2View UserSearchSelect2View, UserSelect2View
admin.autodiscover() admin.autodiscover()
register_patterns = [ register_patterns = [
@ -368,8 +369,8 @@ urlpatterns = [
url(r'^custom_checker_sample/', about.custom_checker_sample, name='custom_checker_sample'), url(r'^custom_checker_sample/', about.custom_checker_sample, name='custom_checker_sample'),
url(r'^chat/', include([ url(r'^chat/', include([
url(r'^$', ChatView.as_view(), name='chat'), url(r'^$', login_required(ChatView.as_view()), name='chat'),
url(r'send$', send, name='send_message')
])), ])),
] ]

View file

@ -219,7 +219,7 @@ header {
#nav-shadow { #nav-shadow {
height: 2px; height: 2px;
background: linear-gradient($widget_black, transparent); background: linear-gradient(#63c8f9, transparent);
} }
#nav-container { #nav-container {
@ -281,7 +281,7 @@ nav {
height: 18px; height: 18px;
&:link { &:link {
color: #FFF; color: #9c3706;
} }
&:hover { &:hover {

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.5 KiB

After

Width:  |  Height:  |  Size: 24 KiB

View file

@ -1,16 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="160" height="44">
<g transform="matrix(.08265 0 0 -.0827 -18.736 55.375)">
<path fill="#FCDB05" stroke="#FCDB05" stroke-width="10" d="M477.898 612.007c-107.844-.25-215.89-92.284-206.703-227.507 6.614-97.33 93.848-202.26 227.87-192.475 101.47 7.41 194.44 95.456 192.42 213.653-1.96 114.6-97.744 208.65-213.587 206.33m3.533-437.256c-123.62-2.002-228.5 102.77-227.64 227.46.87 125.882 103.18 228.238 229.38 226.488 124.6-1.727 225.85-98.733 224.96-230.748-.82-122.186-101.1-223.51-226.69-223.2"/>
<path fill="#fcdb05" d="M515.264 390.9c14.44-20.215 31.333-37.793 47.34-56.057 18.196-20.76 18.438-20.43-3.845-36.05-7.16-5.014-14.16-10.288-20.88-15.868-6.27-5.203-10.51-6.375-15.06 2.458-12.79 24.824-43.17 77.232-46.028 76.716-2.73-.5-32.438-53.09-46.36-77.37-4.567-7.97-8.044-6.92-14.116-1.95-10.807 8.84-21.825 17.57-33.59 25.03-7.504 4.75-7.585 8.08-2.073 14.2 18.23 20.26 57.116 65.45 59.423 68.69-28.8 5.974-56.39 11.873-84.065 17.355-10.37 2.055-13.786 4.4-9.2 16.78 4.32 11.65 10.353 23.38 11.56 36.02 1.067 11.187 6.41 11.39 14.473 7.76 24.793-11.16 77.644-34.635 80.63-35.575 1.073 21.123-7.233 79.865-8.628 88.975-1.027 6.686 1.18 9.43 8.496 9.157 14.638-.545 29.38-1.074 43.94.132 11.26.932 13.05-4.163 11.42-12.94-5.17-27.87-5.52-56.143-7.755-85.808 25.865 11.322 51.147 21.774 75.85 33.45 14 6.613 16.357 6.306 19.824-7.96 2.892-11.905 7.53-23.366 11.466-34.877 4.11-12.01-1.486-13.28-9.816-15.007-27.35-5.66-54.684-11.38-83.012-17.28"/>
</g>
<g transform="matrix(.53656 0 0 .52273 46.09 -512.12)">
<path fill="#FCDB05" d="M1.8 1045v-46.4h16.4q24.7 0 24.7 22.6 0 10.8-6.8 17.3-6.7 6.5-18 6.5H1.7zm10.4-37.9v29.4h5.2q6.8 0 10.6-4.1 3.9-4.1 3.9-11.1 0-6.6-3.8-10.4-3.8-3.8-10.7-3.8h-5.1z"/>
<path fill="#ffde05" d="M101.5 1045H91.2v-27.8q0-4.5.4-9.9h-.3q-.8 4.3-1.5 6.1l-10.9 31.5h-8.5l-11.1-31.2q-.5-1.3-1.5-6.5h-.3q.4 6.9.4 12v25.7h-9.4v-46.4h15.3l9.5 27.5q1.1 3.3 1.6 6.6h.2q.9-3.8 1.8-6.7l9.5-27.4h14.9v46.4z"/>
<g fill="#ffde05">
<path d="M113.6 1018.2q-2.5 0-4.2-1.5-1.7-1.6-1.7-3.8 0-2.3 1.7-3.8t4.3-1.5q2.6 0 4.3 1.5 1.7 1.5 1.7 3.8 0 2.4-1.7 3.8-1.7 1.5-4.4 1.5zm0 23.7q-2.5 0-4.2-1.6-1.7-1.6-1.7-3.8 0-2.3 1.7-3.8t4.3-1.5q2.6 0 4.3 1.5 1.7 1.5 1.7 3.8 0 2.4-1.7 3.9t-4.4 1.5z"/>
<path d="M129.6 1018.2q-2.5 0-4.2-1.5-1.7-1.6-1.7-3.8 0-2.3 1.7-3.8t4.3-1.5q2.6 0 4.3 1.5 1.7 1.5 1.7 3.8 0 2.4-1.7 3.8-1.7 1.5-4.4 1.5zm0 23.7q-2.5 0-4.2-1.6-1.7-1.6-1.7-3.8 0-2.3 1.7-3.8t4.3-1.5q2.6 0 4.3 1.5 1.7 1.5 1.7 3.8 0 2.4-1.7 3.9t-4.4 1.5z"/>
</g>
<path fill="#aaa" d="M161.7 1045.8q-10 0-16.2-6.5-6.3-6.5-6.3-16.9 0-11 6.4-17.8 6.4-6.8 16.9-6.8 9.9 0 16 6.5t6.1 17.1q0 10.9-6.4 17.6-6.3 6.7-16.6 6.7zm.5-39q-5.5 0-8.7 4.1-3.2 4.1-3.2 10.9 0 6.9 3.2 10.9t8.5 4q5.4 0 8.6-3.9 3.2-3.9 3.2-10.8 0-7.2-3.1-11.2-3.1-4-8.4-4z"/>
<path fill="#aaa" d="M209.6 1026.6q0 9.3-4.3 14.3-4.3 4.9-12.5 4.9-3.7 0-6.8-1.3v-9.8q2.7 2 6 2 7.1 0 7.1-10.6v-27.6h10.5v28z"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 2.9 KiB

View file

@ -42,7 +42,7 @@ $table_header_rounding: 6px;
th { th {
height: 2em; height: 2em;
color: #2b333b; color: white;
background-color: $widget_black; background-color: $widget_black;
border-color: #cccccc; border-color: #cccccc;
border-width: 1px 1px 0 0; border-width: 1px 1px 0 0;

View file

@ -1,5 +1,5 @@
$highlight_blue: white; $highlight_blue: white;
$widget_black: #63c8f9;//68BBE3, add8e6 $widget_black: #00007d;//68BBE3, add8e6
$border_gray: #ccc; $border_gray: #ccc;
$background_gray: #ededed; $background_gray: #ededed;
$background_light_gray: #fafafa; $background_light_gray: #fafafa;

View file

@ -419,7 +419,7 @@ ul.select2-selection__rendered {
margin: 0 -5px; margin: 0 -5px;
background: $widget_black; background: $widget_black;
border-radius: $widget_border_radius $widget_border_radius 0 0; border-radius: $widget_border_radius $widget_border_radius 0 0;
color: #2b333b; color: white;
padding-top: 5px; padding-top: 5px;
padding-bottom: 5px; padding-bottom: 5px;
padding-left: 7px; padding-left: 7px;
@ -427,7 +427,7 @@ ul.select2-selection__rendered {
} }
.sidebox h3 .fa { .sidebox h3 .fa {
color: #2b333b; color: white;
float: right; float: right;
margin: 0.2em 0.4em 0 0; margin: 0.2em 0.4em 0 0;
} }

View file

@ -1,7 +1,7 @@
{% if request.user.is_authenticated %}
{% extends "base.html" %} {% extends "base.html" %}
{% block js_media %} {% block js_media %}
<script src="{{ static('libs/jquery.waypoints.min.js')}}"></script>
<script src="{{ static('libs/infinite.min.js') }}"></script>
<script type="text/javascript"> <script type="text/javascript">
var chatSocket = new WebSocket( var chatSocket = new WebSocket(
'ws://' + window.location.host + 'ws://' + window.location.host +
@ -12,15 +12,14 @@
chatSocket.onmessage = function(e) { chatSocket.onmessage = function(e) {
let data = JSON.parse(e.data) let data = JSON.parse(e.data)
data = data['message'] data = data['message']
console.log(data) loadMessage(data['body'],
loadMessage(data['content'], data['author'],
data['sender'],
data['time'], data['time'],
data['image']) data['image'])
}; };
function loadMessage(content, user, time, image) { function loadMessage(content, user, time, image) {
li = `<li> li = `<li class="infinite-item">
<img src="${image}" class="profile-pic"> <img src="${image}" class="profile-pic">
<div class="body-message"> <div class="body-message">
<div class="user-time"> <div class="user-time">
@ -40,34 +39,44 @@
$('#chat-log').scrollTop($('#chat-log')[0].scrollHeight); $('#chat-log').scrollTop($('#chat-log')[0].scrollHeight);
} }
(function init_chatlog() {
ul = $('#chat-log')
{% for msg in message|reverse %}
loadMessage('{{msg.body}}', '{{msg.author}}', '12:00:00', '{{gravatar(msg.author, 32)}}')
{% endfor %}
})()
var infinite = new Waypoint.Infinite({
element: $('.infinite-container')[0],
onBeforePageLoad: function () {
$('.loading').show();
},
onAfterPageLoad: function ($items) {
$('.loading').hide();
}
});
$("#chat-submit").click(function() { $("#chat-submit").click(function() {
if ($("#chat-input").val().trim()) { if ($("#chat-input").val().trim()) {
let content = $('#chat-input').val().trim(); let body = $('#chat-input').val().trim();
let img = '{{ gravatar(request.user, 32) }}' let img = '{{ gravatar(request.user, 32) }}'
message = { message = {
'content': content, 'body': body,
'image': img, 'image': img,
'time': calcTime(6), // HCM City 'author': '{{request.profile}}',
'sender': '{{ request.user }}' 'author_id': {{request.profile.id}},
} }
// $.post("send/", message)
chatSocket.send(JSON.stringify({ chatSocket.send(JSON.stringify({
'message': message, 'message': message
})) }));
// $.post('/chat/send', message)
$('#chat-input').val('').focus(); $('#chat-input').val('').focus();
} }
}); });
function calcTime(offset) {
utc = new Date().getTime()
nd = new Date(utc + (3600000*offset));
return nd.toLocaleString();
}
chatSocket.onclose = function(e) { chatSocket.onclose = function(e) {
console.error('Chat socket closed unexpectedly'); console.error('Chat socket closed unexpectedly');
}; };
@ -108,29 +117,15 @@
{% endblock js_media %} {% endblock js_media %}
{% block body %} {% block body %}
{% csrf_token %}
<div id="chat-area"> <div id="chat-area">
<ul id="chat-log"> <ul id="chat-log" class="infinite-container">
<li>
<img src="https://via.placeholder.com/150" class="profile-pic">
<div class="body-message">
<div class="user-time">
<a href="#" class="user">
cuom1999
</a>
<span class="time">12:00:00</span>
</div>
<span class="message">
<span>Its possible that a request can come in via POST with an empty POST dictionary if, say, a form is requested via the POST HTTP method but does not include form data. Therefore, you shouldnt use if request.POST to check for use of the POST method; instead, use if request.method == "POST" (see HttpRequest.method).</span>
</span>
</div>
<div class="clear"></div>
</li>
</ul> </ul>
{{_('Your message')}} {{_('Your message')}}
<textarea rows="6" id="chat-input"></textarea> <textarea rows="6" id="chat-input"></textarea>
</div> </div>
<button id="chat-submit"> Send </button> <button id="chat-submit"> Send </button>
{% endblock body %} {% if page_obj.has_next %}
<a href="{{ request.path }}?page={{ message.next_page_number }}">next</a>
{% endif %} {% endif %}
{% endblock body %}