Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
144 changes: 141 additions & 3 deletions chats/admin.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
from functools import partial

from asgiref.sync import async_to_sync
from channels.layers import get_channel_layer
from django import forms
from django.contrib import admin, messages
from django.core.exceptions import PermissionDenied
from django.core.cache import cache
from django.core.paginator import Paginator
from django.db import transaction
from django.shortcuts import get_object_or_404, redirect
from django.template.response import TemplateResponse
from django.urls import path, reverse
from django.utils.html import format_html
from django.views.decorators.http import require_safe
from django.views.decorators.http import require_http_methods, require_safe

from chats.attachments import download_chat_attachment, get_accessible_chat
from chats.exceptions import MessageValidationException
Expand All @@ -20,8 +24,12 @@
DirectChatMessage,
FileToMessage,
)
from chats.permissions import accessible_chats
from chats.project_reads import mark_project_message_read, publish_project_message_read
from chats.search import ChatMessageSearchForm, search_chat_messages
from chats.services.messages import delete_message, edit_message
from chats.utils import get_user_channel_cache_key
from chats.websockets_settings import ChatType, EventGroupType, EventType


@admin.display(description="Пользователи чата")
Expand Down Expand Up @@ -56,6 +64,13 @@ def get_urls(self):
self.admin_site.admin_view(require_safe(self.message_context)),
name=self.message_search_url_name + "_context",
),
path(
object_path + "/messages/<int:message_id>/change/",
self.admin_site.admin_view(
require_http_methods(["GET", "POST"])(self.change_own_message)
),
name=self.message_search_url_name + "_change",
),
path(
object_path
+ "/messages/<int:message_id>/files/<slug:attachment_id>/download/",
Expand Down Expand Up @@ -98,6 +113,9 @@ def message_search(self, request, object_id):
"message_context_route": "admin:"
+ self.message_search_url_name
+ "_context",
"message_change_route": "admin:"
+ self.message_search_url_name
+ "_change",
"attachment_download_route": "admin:"
+ self.message_search_url_name
+ "_download",
Expand All @@ -114,6 +132,91 @@ def attachment_download(self, request, object_id, message_id, attachment_id):
chat = self.get_chat_for_member(request, object_id)
return download_chat_attachment(request, chat, message_id, attachment_id)

def change_own_message(self, request, object_id, message_id):
chat = self.get_chat_for_member(request, object_id)
message = get_object_or_404(
chat.messages, pk=message_id, author=request.user, is_deleted=False
)
form = OwnMessageForm(
request.POST if request.method == "POST" else None,
initial={"text": message.text},
)
search_route = "admin:" + self.message_search_url_name
if request.method == "POST" and form.is_valid():
change = dict(
chat_model=self.model,
chat_id=chat.pk,
message_id=message.pk,
author=request.user,
)
try:
with transaction.atomic():
if form.cleaned_data["action"] == "delete":
delete_message(**change)
event_type = EventType.DELETE_MESSAGE
content = {"message_id": message.pk}
else:
event_type = EventType.EDIT_MESSAGE
content = {
"message": edit_message(
**change, text=form.cleaned_data["text"]
)
}
transaction.on_commit(
partial(self.publish_change, chat, event_type, content),
robust=True,
)
except MessageValidationException as error:
form.add_error(None, str(error))
else:
if event_type == EventType.DELETE_MESSAGE:
self.message_user(request, "Сообщение удалено.")
return redirect(search_route, chat.pk)
self.message_user(request, "Сообщение сохранено.")
return redirect(search_route + "_context", chat.pk, message.pk)
request.current_app = self.admin_site.name
return TemplateResponse(
request,
"admin/chats/message_change.html",
{
**self.admin_site.each_context(request),
"opts": self.model._meta,
"title": "Изменение сообщения",
"chat": chat,
"message": message,
"form": form,
"message_context_route": search_route + "_context",
},
status=400 if form.errors else 200,
)

def publish_change(self, chat, event_type, content):
layer = get_channel_layer()
if layer is None:
return
if isinstance(chat, ProjectChat):
async_to_sync(layer.group_send)(
f"{EventGroupType.CHATS_RELATED}_{chat.pk}",
{
"type": event_type,
"content": {
"chat_id": chat.pk,
"chat_type": ChatType.PROJECT,
**content,
},
"_project_chat_id": chat.pk,
},
)
else:
event = {
"type": event_type,
"content": {"chat_id": chat.pk, "chat_type": ChatType.DIRECT, **content},
}
for user in chat.users.all():
channel = cache.get(get_user_channel_cache_key(user))
if channel:
async_to_sync(layer.send)(channel, event)

def message_context(self, request, object_id, message_id):
chat = self.get_chat_for_member(request, object_id)
form = MessageContextForm(request.GET)
Expand Down Expand Up @@ -142,12 +245,27 @@ def message_context(self, request, object_id, message_id):
),
"search_route": search_route,
"message_context_route": search_route + "_context",
"message_change_route": search_route + "_change",
"attachment_download_route": search_route + "_download",
},
status=200 if context is not None else 400,
)


class OwnMessageForm(forms.Form):
text = forms.CharField(
label="Текст сообщения", required=False, strip=False, widget=forms.Textarea
)
action = forms.ChoiceField(
choices=[("edit", "Сохранить"), ("delete", "Удалить")],
widget=forms.HiddenInput,
error_messages={
"required": "Выберите действие.",
"invalid_choice": "Выберите доступное действие.",
},
)


@admin.register(ProjectChat)
class ChatAdmin(ChatSearchAdmin):
list_display = (
Expand Down Expand Up @@ -176,8 +294,28 @@ class DirectChatAdmin(ChatSearchAdmin):
list_display_links = ("id",)


class ChatMessageAdmin(admin.ModelAdmin):
def get_queryset(self, request):
queryset = super().get_queryset(request)
if request.user.is_superuser:
return queryset
chat_model = self.model._meta.get_field("chat").related_model
return queryset.filter(
chat_id__in=accessible_chats(request.user, chat_model).values("pk")
)

def has_add_permission(self, request):
return request.user.is_superuser and super().has_add_permission(request)

def has_change_permission(self, request, obj=None):
return request.user.is_superuser and super().has_change_permission(request, obj)

def has_delete_permission(self, request, obj=None):
return request.user.is_superuser and super().has_delete_permission(request, obj)


@admin.register(ProjectChatMessage)
class ProjectChatMessageAdmin(admin.ModelAdmin):
class ProjectChatMessageAdmin(ChatMessageAdmin):
list_display = ("id", "author", "chat", "created_at")
list_display_links = ("id", "author", "chat")
exclude = ("is_read",)
Expand Down Expand Up @@ -216,7 +354,7 @@ def mark_read_for_me(self, request, queryset):


@admin.register(DirectChatMessage)
class DirectChatMessageAdmin(admin.ModelAdmin):
class DirectChatMessageAdmin(ChatMessageAdmin):
list_display = ("id", "author", "chat", "created_at")
list_display_links = ("id", "author", "chat")

Expand Down
71 changes: 22 additions & 49 deletions chats/consumers/event_types/DirectEvent.py
Original file line number Diff line number Diff line change
@@ -1,21 +1,16 @@
from channels.db import database_sync_to_async

from chats.models import DirectChatMessage, DirectChat
from chats.websockets_settings import Event, EventType
from chats.exceptions import (
WrongChatIdException,
UserNotMessageAuthorException,
UserIsNotAuthor,
)
from chats.websockets_settings import ChatType, Event, EventType
from chats.exceptions import WrongChatIdException
from django.core.cache import cache
from chats.utils import (
get_user_channel_cache_key,
get_chat_and_user_ids_from_content,
orm_get,
orm_save,
)
from chats.serializers import DirectChatMessageListSerializer
from chats.services import create_direct_message
from chats.services.messages import create_direct_message, delete_message, edit_message


class DirectEvent:
Expand Down Expand Up @@ -81,18 +76,13 @@ async def process_read_message_event(self, event: Event, room_name: str):
await self.channel_layer.send(other_user_channel, json_thingy)

async def process_delete_message_event(self, event: Event, room_name: str):
message_id = event.content["message_id"]

message = await orm_get(DirectChatMessage.objects, pk=message_id)

if self.user.id != message.author_id:
raise UserIsNotAuthor(f"User {self.user.id} is not author {message.text}")

message.is_deleted = True
await orm_save(message, update_fields=["is_deleted"])

chat_id, other_user = await get_chat_and_user_ids_from_content(
event.content, self.user
_, other_user = await get_chat_and_user_ids_from_content(event.content, self.user)
chat_id = DirectChat.get_chat_id_from_users(self.user, other_user)
message_id = await database_sync_to_async(delete_message)(
chat_model=DirectChat,
chat_id=chat_id,
message_id=event.content["message_id"],
author=self.user,
)

# send message to user's channel
Expand All @@ -101,42 +91,25 @@ async def process_delete_message_event(self, event: Event, room_name: str):
"type": EventType.DELETE_MESSAGE,
"content": {
"message_id": message_id,
"chat_id": chat_id,
"chat_type": ChatType.DIRECT,
},
}
await self.channel_layer.send(self.channel_name, content)

if other_user_channel is None:
return

await self.channel_layer.send(other_user_channel, content)
if other_user_channel and other_user_channel != self.channel_name:
await self.channel_layer.send(other_user_channel, content)

async def process_edit_message_event(self, event, room_name):
chat_id, other_user = await get_chat_and_user_ids_from_content(
event.content, self.user
)
# if chat_id == 17_7, then chat_id will be == 7_17
_, other_user = await get_chat_and_user_ids_from_content(event.content, self.user)
chat_id = DirectChat.get_chat_id_from_users(self.user, other_user)

# check if chat exists ( this raises exception if not )
await orm_get(DirectChat.objects, pk=chat_id)

msg = await orm_get(DirectChatMessage.objects, pk=event.content["message_id"])

if msg.author_id != self.user.id:
raise UserNotMessageAuthorException(
f"User {self.user.id} is not author of message {msg.id}"
)
msg.text = event.content["text"]
msg.is_edited = True
await orm_save(msg, update_fields=["text", "is_edited"])

serialized_message = await orm_get(
DirectChatMessage.objects.select_related(
"author", "reply_to__author"
).prefetch_related("file_to_message__file"),
pk=msg.pk,
message_data = await database_sync_to_async(edit_message)(
chat_model=DirectChat,
chat_id=chat_id,
message_id=event.content["message_id"],
author=self.user,
text=event.content["text"],
)
message_data = DirectChatMessageListSerializer(serialized_message).data
content = {
"chat_id": chat_id,
"message": message_data,
Expand All @@ -147,7 +120,7 @@ async def process_edit_message_event(self, event, room_name):

event_data = {"type": EventType.EDIT_MESSAGE, "content": content}

if other_user_channel:
if other_user_channel and other_user_channel != self.channel_name:
await self.channel_layer.send(other_user_channel, event_data)

await self.channel_layer.send(self.channel_name, event_data)
Loading
Loading