Add direct message

This commit is contained in:
cuom1999 2021-11-20 22:23:03 -06:00
parent 259cb95b43
commit 2f8ef1b524
20 changed files with 1066 additions and 195 deletions

View file

@ -0,0 +1,28 @@
# Generated by Django 2.2.17 on 2021-10-11 00:14
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('judge', '0116_auto_20211011_0645'),
('chat_box', '0004_auto_20200505_2336'),
]
operations = [
migrations.CreateModel(
name='Room',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('user_one', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='user_one', to='judge.Profile', verbose_name='user 1')),
('user_two', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='user_two', to='judge.Profile', verbose_name='user 2')),
],
),
migrations.AddField(
model_name='message',
name='room',
field=models.ForeignKey(default=None, null=True, on_delete=django.db.models.deletion.CASCADE, to='chat_box.Room', verbose_name='room id'),
),
]

View file

@ -0,0 +1,24 @@
# Generated by Django 2.2.17 on 2021-11-12 05:27
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('judge', '0116_auto_20211011_0645'),
('chat_box', '0005_auto_20211011_0714'),
]
operations = [
migrations.CreateModel(
name='UserRoom',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('last_seen', models.DateTimeField(verbose_name='last seen')),
('room', models.ForeignKey(default=None, null=True, on_delete=django.db.models.deletion.CASCADE, to='chat_box.Room', verbose_name='room id')),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='judge.Profile', verbose_name='user')),
],
),
]

View file

@ -0,0 +1,18 @@
# Generated by Django 2.2.17 on 2021-11-12 05:55
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('chat_box', '0006_userroom'),
]
operations = [
migrations.AlterField(
model_name='userroom',
name='last_seen',
field=models.DateTimeField(auto_now_add=True, verbose_name='last seen'),
),
]

View file

@ -0,0 +1,23 @@
# Generated by Django 2.2.17 on 2021-11-18 10:26
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('judge', '0116_auto_20211011_0645'),
('chat_box', '0007_auto_20211112_1255'),
]
operations = [
migrations.CreateModel(
name='Ignore',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('ignored_users', models.ManyToManyField(to='judge.Profile')),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='ignored_chat_users', to='judge.Profile', verbose_name='user')),
],
),
]

View file

@ -8,12 +8,23 @@ from judge.models.profile import Profile
__all__ = ['Message']
class Room(models.Model):
user_one = models.ForeignKey(Profile, related_name="user_one", verbose_name='user 1', on_delete=CASCADE)
user_two = models.ForeignKey(Profile, related_name="user_two", verbose_name='user 2', on_delete=CASCADE)
def contain(self, profile):
return self.user_one == profile or self.user_two == profile
def other_user(self, profile):
return self.user_one if profile == self.user_two else self.user_two
def users(self):
return [self.user_one, self.user_two]
class Message(models.Model):
author = models.ForeignKey(Profile, verbose_name=_('user'), on_delete=CASCADE)
time = models.DateTimeField(verbose_name=_('posted time'), auto_now_add=True)
body = models.TextField(verbose_name=_('body of comment'), max_length=8192)
hidden = models.BooleanField(verbose_name='is hidden', default=False)
room = models.ForeignKey(Room, verbose_name='room id', on_delete=CASCADE, default=None, null=True)
def save(self, *args, **kwargs):
new_message = self.id
@ -25,3 +36,46 @@ class Message(models.Model):
verbose_name = 'message'
verbose_name_plural = 'messages'
ordering = ('-time',)
class UserRoom(models.Model):
user = models.ForeignKey(Profile, verbose_name=_('user'), on_delete=CASCADE)
room = models.ForeignKey(Room, verbose_name='room id', on_delete=CASCADE, default=None, null=True)
last_seen = models.DateTimeField(verbose_name=_('last seen'), auto_now_add=True)
class Ignore(models.Model):
user = models.ForeignKey(Profile, related_name="ignored_chat_users", verbose_name=_('user'), on_delete=CASCADE)
ignored_users = models.ManyToManyField(Profile)
@classmethod
def is_ignored(self, current_user, new_friend):
try:
return current_user.ignored_chat_users.get().ignored_users \
.filter(id=new_friend.id).exists()
except:
return False
@classmethod
def get_ignored_users(self, user):
return self.objects.get(user=user).ignored_users.all()
@classmethod
def add_ignore(self, current_user, friend):
ignore, created = self.objects.get_or_create(
user = current_user
)
ignore.ignored_users.add(friend)
@classmethod
def remove_ignore(self, current_user, friend):
ignore, created = self.objects.get_or_create(
user = current_user
)
ignore.ignored_users.remove(friend)
@classmethod
def toggle_ignore(self, current_user, friend):
if (self.is_ignored(current_user, friend)):
self.remove_ignore(current_user, friend)
else:
self.add_ignore(current_user, friend)

18
chat_box/utils.py Normal file
View file

@ -0,0 +1,18 @@
from cryptography.fernet import Fernet
from django.conf import settings
secret_key = settings.CHAT_SECRET_KEY
fernet = Fernet(secret_key)
def encrypt_url(creator_id, other_id):
message = str(creator_id) + '_' + str(other_id)
return fernet.encrypt(message.encode()).decode()
def decrypt_url(message_encrypted):
try:
dec_message = fernet.decrypt(message_encrypted.encode()).decode()
creator_id, other_id = dec_message.split('_')
return int(creator_id), int(other_id)
except Exception as e:
return None, None

View file

@ -1,42 +1,72 @@
from django.utils.translation import gettext as _
from django.views.generic import ListView
from django.http import HttpResponse, JsonResponse, HttpResponseBadRequest
from django.http import HttpResponse, JsonResponse, HttpResponseBadRequest, HttpResponsePermanentRedirect, HttpResponseRedirect
from django.core.paginator import Paginator
from django.core.exceptions import PermissionDenied
from django.shortcuts import render
from django.forms.models import model_to_dict
from django.db.models import Case, BooleanField
from django.db.models import Case, BooleanField, When, Q, Subquery, OuterRef, Exists, Count, IntegerField
from django.db.models.functions import Coalesce
from django.utils import timezone
from django.contrib.auth.decorators import login_required
from django.urls import reverse
import datetime
from judge import event_poster as event
from judge.jinja2.gravatar import gravatar
from .models import Message, Profile
from judge.models import Friend
from chat_box.models import Message, Profile, Room, UserRoom, Ignore
from chat_box.utils import encrypt_url, decrypt_url
import json
class ChatView(ListView):
context_object_name = 'message'
template_name = 'chat/chat.html'
title = _('Chat Box')
paginate_by = 50
messages = Message.objects.filter(hidden=False)
paginator = Paginator(messages, paginate_by)
def __init__(self):
super().__init__()
self.room_id = None
self.room = None
self.paginate_by = 50
self.messages = None
self.paginator = None
def get_queryset(self):
return self.messages
def get(self, request, *args, **kwargs):
request_room = kwargs['room_id']
page = request.GET.get('page')
if request_room:
try:
self.room = Room.objects.get(id=request_room)
if not can_access_room(request, self.room):
return HttpResponseBadRequest()
except Room.DoesNotExist:
return HttpResponseBadRequest()
else:
request_room = None
if request_room != self.room_id or not self.messages:
self.room_id = request_room
self.messages = Message.objects.filter(hidden=False, room=self.room_id)
self.paginator = Paginator(self.messages, self.paginate_by)
if page == None:
update_last_seen(request, **kwargs)
return super().get(request, *args, **kwargs)
cur_page = self.paginator.get_page(page)
return render(request, 'chat/message_list.html', {
'object_list': cur_page.object_list,
'num_pages': self.paginator.num_pages
})
def get_context_data(self, **kwargs):
@ -45,7 +75,22 @@ class ChatView(ListView):
context['title'] = self.title
context['last_msg'] = event.last()
context['status_sections'] = get_status_context(self.request)
context['today'] = timezone.now().strftime("%d-%m-%Y")
context['room'] = self.room_id
context['unread_count_lobby'] = get_unread_count(None, self.request.profile)
if self.room:
users_room = [self.room.user_one, self.room.user_two]
users_room.remove(self.request.profile)
context['other_user'] = users_room[0]
context['other_online'] = get_user_online_status(context['other_user'])
context['is_ignored'] = Ignore.is_ignored(self.request.profile, context['other_user'])
else:
context['online_count'] = get_online_count()
context['message_template'] = {
'author': self.request.profile,
'id': '$id',
'time': timezone.now(),
'body': '$body'
}
return context
@ -73,21 +118,46 @@ def delete_message(request):
@login_required
def post_message(request):
ret = {'msg': 'posted'}
if request.method != 'POST':
return HttpResponseBadRequest()
if request.method == 'GET':
return JsonResponse(ret)
room = None
if request.POST['room']:
room = Room.objects.get(id=request.POST['room'])
if not can_access_room(request, room) or request.profile.mute:
return HttpResponseBadRequest()
new_message = Message(author=request.profile,
body=request.POST['body'])
body=request.POST['body'],
room=room)
new_message.save()
event.post('chat', {
'type': 'new_message',
'message': new_message.id,
})
if not room:
event.post('chat_lobby', {
'type': 'lobby',
'author_id': request.profile.id,
'message': new_message.id,
'room': 'None',
'tmp_id': request.POST.get('tmp_id')
})
else:
for user in room.users():
event.post('chat_' + str(user.id), {
'type': 'private',
'author_id': request.profile.id,
'message': new_message.id,
'room': room.id,
'tmp_id': request.POST.get('tmp_id')
})
return JsonResponse(ret)
def can_access_room(request, room):
return not room or room.user_one == request.profile or room.user_two == request.profile
@login_required
def chat_message_ajax(request):
if request.method != 'GET':
@ -100,6 +170,9 @@ def chat_message_ajax(request):
try:
message = Message.objects.filter(hidden=False).get(id=message_id)
room = message.room
if room and not room.contain(request.profile):
return HttpResponse('Unauthorized', status=401)
except Message.DoesNotExist:
return HttpResponseBadRequest()
return render(request, 'chat/message.html', {
@ -107,53 +180,156 @@ def chat_message_ajax(request):
})
def get_user_online_status():
last_five_minutes = timezone.now()-timezone.timedelta(minutes=5)
return Profile.objects \
.filter(display_rank='user',
last_access__gte = last_five_minutes)\
.annotate(is_online=Case(default=True,output_field=BooleanField()))\
.order_by('-rating')
@login_required
def update_last_seen(request, **kwargs):
if 'room_id' in kwargs:
room_id = kwargs['room_id']
elif request.method == 'GET':
room_id = request.GET.get('room')
elif request.method == 'POST':
room_id = request.POST.get('room')
else:
return HttpResponseBadRequest()
try:
profile = request.profile
room = None
if room_id:
room = Room.objects.get(id=int(room_id))
except Room.DoesNotExist:
return HttpResponseBadRequest()
except Exception as e:
return HttpResponseBadRequest()
if room and not room.contain(profile):
return HttpResponseBadRequest()
user_room, _ = UserRoom.objects.get_or_create(user=profile, room=room)
user_room.last_seen = timezone.now()
user_room.save()
return JsonResponse({'msg': 'updated'})
def get_admin_online_status():
all_admin = Profile.objects.filter(display_rank='admin')
last_five_minutes = timezone.now()-timezone.timedelta(minutes=5)
def get_online_count():
last_two_minutes = timezone.now()-timezone.timedelta(minutes=2)
return Profile.objects.filter(last_access__gte=last_two_minutes).count()
def get_user_online_status(user):
time_diff = timezone.now() - user.last_access
is_online = time_diff <= timezone.timedelta(minutes=2)
return is_online
def user_online_status_ajax(request):
if request.method != 'GET':
return HttpResponseBadRequest()
user_id = request.GET.get('user')
if user_id:
try:
user_id = int(user_id)
user = Profile.objects.get(id=user_id)
except Exception as e:
return HttpResponseBadRequest()
is_online = get_user_online_status(user)
return render(request, 'chat/user_online_status.html', {
'other_user': user,
'other_online': is_online,
'is_ignored': Ignore.is_ignored(request.profile, user)
})
else:
return render(request, 'chat/user_online_status.html', {
'online_count': get_online_count(),
})
def get_online_status(request_user, queryset, rooms=None):
if not queryset:
return None
last_two_minutes = timezone.now()-timezone.timedelta(minutes=2)
ret = []
for admin in all_admin:
if rooms:
unread_count = get_unread_count(rooms, request_user)
count = {}
for i in unread_count:
count[i['other_user']] = i['unread_count']
for user in queryset:
is_online = False
if (admin.last_access >= last_five_minutes):
if (user.last_access >= last_two_minutes):
is_online = True
ret.append({'user': admin, 'is_online': is_online})
user_dict = {'user': user, 'is_online': is_online}
if rooms and user.id in count:
user_dict['unread_count'] = count[user.id]
user_dict['url'] = encrypt_url(request_user.id, user.id)
ret.append(user_dict)
return ret
def get_status_context(request):
friend_list = request.profile.get_friends()
all_user_status = get_user_online_status()
friend_status = []
user_status = []
def get_status_context(request, include_ignored=False):
if include_ignored:
ignored_users = Profile.objects.none()
queryset = Profile.objects
else:
ignored_users = Ignore.get_ignored_users(request.profile)
queryset = Profile.objects.exclude(id__in=ignored_users)
for user in all_user_status:
if user.username in friend_list:
friend_status.append(user)
else:
user_status.append(user)
last_two_minutes = timezone.now()-timezone.timedelta(minutes=2)
recent_profile = Room.objects.filter(
Q(user_one=request.profile) | Q(user_two=request.profile)
).annotate(
last_msg_time=Subquery(
Message.objects.filter(room=OuterRef('pk')).values('time')[:1]
),
other_user=Case(
When(user_one=request.profile, then='user_two'),
default='user_one',
)
).filter(last_msg_time__isnull=False)\
.exclude(other_user__in=ignored_users)\
.order_by('-last_msg_time').values('other_user', 'id')[:20]
recent_profile_id = [str(i['other_user']) for i in recent_profile]
joined_id = ','.join(recent_profile_id)
recent_rooms = [int(i['id']) for i in recent_profile]
recent_list = None
if joined_id:
recent_list = Profile.objects.raw(
f'SELECT * from judge_profile where id in ({joined_id}) order by field(id,{joined_id})')
friend_list = Friend.get_friend_profiles(request.profile).exclude(id__in=recent_profile_id)\
.exclude(id__in=ignored_users)\
.order_by('-last_access')
admin_list = queryset.filter(display_rank='admin')\
.exclude(id__in=friend_list).exclude(id__in=recent_profile_id)
all_user_status = queryset\
.filter(display_rank='user',
last_access__gte = last_two_minutes)\
.annotate(is_online=Case(default=True,output_field=BooleanField()))\
.order_by('-rating').exclude(id__in=friend_list).exclude(id__in=admin_list)\
.exclude(id__in=recent_profile_id)[:30]
return [
{
'title': 'Admins',
'user_list': get_admin_online_status(),
'title': 'Recent',
'user_list': get_online_status(request.profile, recent_list, recent_rooms),
},
{
'title': 'Following',
'user_list': friend_status,
'user_list': get_online_status(request.profile, friend_list),
},
{
'title': 'Users',
'user_list': user_status,
'title': 'Admins',
'user_list': get_online_status(request.profile, admin_list),
},
{
'title': 'Other',
'user_list': get_online_status(request.profile, all_user_status),
},
]
@ -162,4 +338,80 @@ def get_status_context(request):
def online_status_ajax(request):
return render(request, 'chat/online_status.html', {
'status_sections': get_status_context(request),
})
'unread_count_lobby': get_unread_count(None, request.profile),
})
@login_required
def get_room(user_one, user_two):
if user_one.id > user_two.id:
user_one, user_two = user_two, user_one
room, created = Room.objects.get_or_create(user_one=user_one, user_two=user_two)
return room
@login_required
def get_or_create_room(request):
decrypted_other_id = request.GET.get('other')
request_id, other_id = decrypt_url(decrypted_other_id)
if not other_id or not request_id or request_id != request.profile.id:
return HttpResponseBadRequest()
try:
other_user = Profile.objects.get(id=int(other_id))
except Exception:
return HttpResponseBadRequest()
user = request.profile
if not other_user or not user:
return HttpResponseBadRequest()
# TODO: each user can only create <= 300 rooms
room = get_room(other_user, user)
return JsonResponse({'room': room.id, 'other_user_id': other_user.id})
def get_unread_count(rooms, user):
if rooms:
mess = Message.objects.filter(room=OuterRef('room'),
time__gte=OuterRef('last_seen'))\
.exclude(author=user)\
.order_by().values('room')\
.annotate(unread_count=Count('pk')).values('unread_count')
return UserRoom.objects\
.filter(user=user, room__in=rooms)\
.annotate(
unread_count=Coalesce(Subquery(mess, output_field=IntegerField()), 0),
other_user=Case(
When(room__user_one=user, then='room__user_two'),
default='room__user_one',
)
).filter(unread_count__gte=1).values('other_user', 'unread_count')
else: # lobby
mess = Message.objects.filter(room__isnull=True,
time__gte=OuterRef('last_seen'))\
.exclude(author=user)\
.annotate(unread_count=Count('pk')).values('unread_count')
return UserRoom.objects\
.filter(user=user, room__isnull=True)\
.annotate(
unread_count=Coalesce(Subquery(mess, output_field=IntegerField()), 0),
).values_list('unread_count', flat=True)[0]
@login_required
def toggle_ignore(request, **kwargs):
user_id = kwargs['user_id']
if not user_id:
return HttpResponseBadRequest()
try:
other_user = Profile.objects.get(id=user_id)
except:
return HttpResponseBadRequest()
Ignore.toggle_ignore(request.profile, other_user)
next_url = request.GET.get('next', '/')
return HttpResponseRedirect(next_url)