diff --git a/backend/backend/settings.py b/backend/backend/settings.py index 0142811..39bb63b 100644 --- a/backend/backend/settings.py +++ b/backend/backend/settings.py @@ -14,8 +14,24 @@ DEBUG = os.getenv('DEBUG', default=False) -ALLOWED_HOSTS = os.getenv('ALLOWED_HOSTS', default='localhost,127.0.0.1,').split(',') - +ALLOWED_HOSTS = os.getenv( + 'ALLOWED_HOSTS', default='localhost,127.0.0.1,').split(',') + +LOGGING = { + 'version': 1, + 'disable_existing_loggers': False, + 'handlers': { + 'file': { + 'level': 'DEBUG', + 'class': 'logging.FileHandler', + 'filename': 'debug.log', + }, + }, + 'root': { + 'handlers': ['file'], + 'level': 'DEBUG', + }, +} INSTALLED_APPS = [ "daphne", 'django.contrib.admin', diff --git a/backend/chats/admin.py b/backend/chats/admin.py index 623523a..bd63a75 100644 --- a/backend/chats/admin.py +++ b/backend/chats/admin.py @@ -37,7 +37,7 @@ def display_members(self, obj): display_members.short_description = 'Участники' - + admin.site.register(Chat, ChatAdmin) admin.site.register(Attachment) admin.site.register(Message) diff --git a/backend/chats/models.py b/backend/chats/models.py index 8c2ceb5..c1268ee 100644 --- a/backend/chats/models.py +++ b/backend/chats/models.py @@ -146,6 +146,19 @@ class Message(DateEditedModel): verbose_name='Фото для отправки', help_text='Фото для отправки' ) + voice_message = models.FileField( + upload_to='voice_messages/', + blank=True, + null=True, + verbose_name='Голосовое сообщение', + help_text='Голосовое сообщение' + ) + emojis = models.CharField( + max_length=255, + blank=True, + verbose_name='Смайлы', + help_text='Текстовые символы смайлов' + ) responding_to = models.ForeignKey( 'self', on_delete=models.CASCADE, diff --git a/backend/chats/serializers.py b/backend/chats/serializers.py index e825cd5..48c80ce 100644 --- a/backend/chats/serializers.py +++ b/backend/chats/serializers.py @@ -1,5 +1,6 @@ """Сериализаторы приложения chats.""" + from django.contrib.auth import get_user_model from rest_framework import serializers @@ -8,8 +9,14 @@ from core.constants import MAX_MESSAGE_LENGTH from users.serializers import UserShortSerializer +from .validators import (validate_audio_extension, validate_file_size, + validate_image_extension, validate_pdf_extension) + # from django.shortcuts import get_object_or_404 +# from rest_framework.exceptions import PermissionDenied +# from django.shortcuts import get_object_or_404 +# from .models import Chat User = get_user_model() @@ -20,6 +27,7 @@ class MessageSerializer(serializers.ModelSerializer): allow_null=True, max_length=10000 ) + is_read = serializers.SerializerMethodField() read_by = UserShortSerializer( many=True, read_only=True @@ -27,19 +35,39 @@ class MessageSerializer(serializers.ModelSerializer): file_to_send = serializers.FileField( write_only=False, required=False, - allow_empty_file=True + allow_empty_file=True, + validators=[validate_file_size, validate_pdf_extension] ) + photo_to_send = serializers.ImageField( write_only=False, required=False, - allow_empty_file=True + allow_empty_file=True, + validators=[validate_file_size, validate_image_extension] + ) + voice_message = serializers.FileField( + required=False, + allow_empty_file=True, + validators=[validate_file_size, validate_audio_extension] ) + emojis = serializers.CharField(max_length=255, required=False) + sender = serializers.SlugRelatedField( slug_field='slug', read_only=True ) chat = serializers.HiddenField(default=None) + def get_is_read(self, instance): + user = self.context.get( + 'request').user if self.context.get('request') else None + if user is None: + return False + return ( + instance.read_by.filter(id=user.id).exists() and + user != instance.sender + ) + class Meta: model = Message fields = [ @@ -48,6 +76,8 @@ class Meta: 'text', 'file_to_send', 'photo_to_send', + 'voice_message', + 'emojis', 'responding_to', 'sender_keep', 'is_read', @@ -69,60 +99,91 @@ class Meta: 'timestamp', ) - # def create(self, validated_data): - # file_to_send = validated_data.pop('file_to_send', None) - # photo_to_send = validated_data.pop('photo_to_send', None) + def create(self, validated_data): + + file_to_send = validated_data.get('file_to_send', None) + photo_to_send = validated_data.get('photo_to_send', None) + voice_message = validated_data.get('voice_message', None) + emojis = validated_data.get('emojis', None) + text = validated_data.get('text', '') # chatname = validated_data['chat'] - # chat = get_object_or_404(Chat, name=chatname) - - # if not chat.messages.exists() and (file_to_send or photo_to_send): - # raise serializers.ValidationError( - # "Нельзя отправить фото или файл первым сообщением" - # ) - - # validated_data['sender'] = self.context['request'].user - # message = Message.objects.create(**validated_data) - - # if file_to_send: - # Attachment.objects.create( - # name=file_to_send.name, - # content=file_to_send.read(), - # message=message - # ) - - # if photo_to_send: - # Attachment.objects.create( - # name=photo_to_send.name, - # content=photo_to_send.read(), - # message=message - # ) - - # return message - - # def update(self, instance, validated_data): - # file_to_send = validated_data.pop('file_to_send', None) - # photo_to_send = validated_data.pop('photo_to_send', None) - - # for key, value in validated_data.items(): - # setattr(instance, key, value) - - # if file_to_send: - # Attachment.objects.create( - # name=file_to_send.name, - # content=file_to_send.read(), - # message=instance - # ) - - # if photo_to_send: - # Attachment.objects.create( - # name=photo_to_send.name, - # content=photo_to_send.read(), - # message=instance - # ) - - # instance.save() - - # return instance + + validated_data['sender_keep'] = True + + chat = self.context.get('chat') + + if not chat: + raise serializers.ValidationError( + "Chat object is missing in the context") + validated_data['chat'] = chat + if not chat.messages.exists() and (file_to_send or photo_to_send): + raise serializers.ValidationError( + "Нельзя отправить фото или файл первым сообщением" + ) + if voice_message: + text = f'[Voice Message: {voice_message.name}]' + if emojis: + text += emojis + + validated_data['sender'] = self.context['request'].user + message = Message.objects.create(**validated_data) + + if file_to_send: + Attachment.objects.create( + name=file_to_send.name, + content=file_to_send.read(), + message=message + ) + + if photo_to_send: + Attachment.objects.create( + name=photo_to_send.name, + content=photo_to_send.read(), + message=message + ) + + message.text = text + message.save() + return message + + def update(self, instance, validated_data): + file_to_send = validated_data.get('file_to_send', None) + photo_to_send = validated_data.get('photo_to_send', None) + voice_message = validated_data.get('voice_message', None) + emojis = validated_data.get('emojis', None) + text = validated_data.get('text', '') + chat = self.context.get('chat') + if not chat: + raise serializers.ValidationError( + "Chat object is missing in the context") + + for key, value in validated_data.items(): + setattr(instance, key, value) + + if voice_message: + text = f'[Voice Message: {voice_message.name}]' + if emojis: + text += emojis + + instance.text = text + + if file_to_send: + Attachment.objects.create( + name=file_to_send.name, + content=file_to_send.read(), + message=instance + ) + + if photo_to_send: + Attachment.objects.create( + name=photo_to_send.name, + content=photo_to_send.read(), + message=instance + ) + + instance.save() + + return instance class ChatListSerializer(serializers.ModelSerializer): diff --git a/backend/chats/validators.py b/backend/chats/validators.py new file mode 100644 index 0000000..1dd9c67 --- /dev/null +++ b/backend/chats/validators.py @@ -0,0 +1,25 @@ +from django.core.exceptions import ValidationError + + +def validate_file_size(value): + max_size = 20 * 1024 * 1024 # 20 MB + if value.size > max_size: + raise ValidationError( + 'Файл слишком большой. Максимальный размер: 20 МБ.') + + +def validate_file_extension(value, allowed_extensions): + if not value.name.lower().endswith(allowed_extensions): + raise ValidationError('Недопустимый формат файла.') + + +def validate_pdf_extension(value): + validate_file_extension(value, ('.pdf',)) + + +def validate_image_extension(value): + validate_file_extension(value, ('.jpg', '.jpeg', '.png', '.gif')) + + +def validate_audio_extension(value): + validate_file_extension(value, ('.mp3', '.m4a')) diff --git a/backend/chats/views.py b/backend/chats/views.py index 27f9e4c..b687ad2 100644 --- a/backend/chats/views.py +++ b/backend/chats/views.py @@ -2,6 +2,7 @@ from django.contrib.auth import get_user_model from django.db.models import Q +from django.http import HttpResponseForbidden from django.shortcuts import get_object_or_404 from asgiref.sync import async_to_sync @@ -48,7 +49,7 @@ class ChatViewSet(mixins.ListModelMixin, mixins.RetrieveModelMixin, viewsets.GenericViewSet): serializer_class = ChatSerializer - http_method_names = ['get', 'post', 'head'] + http_method_names = ['get', 'post', 'head', 'put'] permission_classes = [ IsAuthenticated, ] @@ -177,6 +178,12 @@ def send_message(self, request, pk=None): """Отправить сообщение в чат""" chat = self.get_object() +# serializer = self.get_serializer( +# data={**request.data}, +# # Передаем chat через контекст +# context={'request': request, 'chat': chat} +# ) + if chat.is_user_blocked(request.user): return Response( { @@ -205,6 +212,49 @@ def send_message(self, request, pk=None): status=status.HTTP_201_CREATED ) + @action(detail=True, methods=['put']) + def update_message(self, request, pk=None): + """Обновить сообщение в чате""" + message_id = request.data.get('message_id') + chat = self.get_object() + + try: + message = chat.messages.get(id=message_id) + except Message.DoesNotExist: + return Response( + {"detail": "Message not found"}, + status=status.HTTP_404_NOT_FOUND + ) + + serializer = MessageSerializer( + instance=message, + data=request.data, + context={'request': request}, + partial=True + ) + + if serializer.is_valid(): + serializer.save() + return Response(serializer.data, status=status.HTTP_200_OK) + + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + + @action(detail=True, methods=['get']) + def view_chat(self, request, pk=None): + """Просмотреть чат и обновить статус 'прочитано' для получателя""" + chat = self.get_object() + + user = self.request.user + + if chat.initiator == user or chat.receiver == user: + for message in chat.messages.exclude(read_by=user): + message.read_by.add(user) + return Response({"detail": "Chat read status updated."}) + + return HttpResponseForbidden( + "You don't have permission to access this chat." + ) + @action(detail=True, methods=['post']) def block_user(self, request, pk=None): """Блокировка пользователя в чате""" diff --git a/backend/core/permissions.py b/backend/core/permissions.py index 1765881..15bedaa 100644 --- a/backend/core/permissions.py +++ b/backend/core/permissions.py @@ -4,10 +4,13 @@ class ActiveChatOrReceiverOnly(permissions.BasePermission): - """ - Разрешение на отправку сообщений для участников активного чата - или только для получателя. - """ + # """ + # Разрешение на отправку сообщений для участников активного чата + # или только для получателя. + # """ + + # def has_permission(self, request, view): + # return request.user.is_authenticated def has_permission(self, request, view): return request.user.is_authenticated diff --git a/backend/users/filters.py b/backend/users/filters.py index 39e1726..13e71d7 100644 --- a/backend/users/filters.py +++ b/backend/users/filters.py @@ -44,7 +44,7 @@ class UserFilter(df.FilterSet): age = AgeFilter() country = CustomFilterList( - field_name='country__code', lookup_expr='in') + field_name='country__name', lookup_expr='in') languages = CustomFilterList( field_name='languages__isocode', lookup_expr='in') skill_level = df.ChoiceFilter( diff --git a/backend/users/serializers.py b/backend/users/serializers.py index c32f9fb..15e9ce2 100644 --- a/backend/users/serializers.py +++ b/backend/users/serializers.py @@ -174,7 +174,7 @@ class UserProfileSerializer(DjoserSerializer, UserReprSerializer): many=False, read_only=False, required=False, - slug_field='code', + slug_field='name', queryset=Country.objects.all() ) interests = CreatableSlugRelatedField( diff --git a/backend/users/views.py b/backend/users/views.py index 21320a2..ae54739 100644 --- a/backend/users/views.py +++ b/backend/users/views.py @@ -124,6 +124,10 @@ def retrieve(self, request, *args, **kwargs): instance = self.get_object() current_user = request.user + if instance == current_user: + serializer = self.get_serializer(instance) + return Response(serializer.data) + blocked_chats = PersonalChat.objects.filter( Q(initiator=instance, blocked_users=current_user) | Q(receiver=instance, blocked_users=current_user) @@ -144,7 +148,7 @@ def retrieve(self, request, *args, **kwargs): @extend_schema( summary='Просмотреть свой профиль', description='Просмотреть свой профиль', - methods=["get"], + methods=["get"] ) @extend_schema( summary='Удалить свой аккаунт',