282 lines
9.9 KiB
Python

from __future__ import annotations
import os
from django.contrib.contenttypes.models import ContentType
from django.db.models import Count, IntegerField, OuterRef, Q, Subquery, Value
from django.db.models.functions import Coalesce
from django.shortcuts import get_object_or_404
from filer.models import File as FilerFile
from rest_framework import mixins, status, viewsets
from rest_framework.decorators import action
from rest_framework.exceptions import PermissionDenied, ValidationError
from rest_framework.parsers import FormParser, JSONParser, MultiPartParser
from rest_framework.response import Response
from api.v1.documents.serializers import DocumentReadSerializer
from communications import comments_helpers
from communications.models import Message
from documents.models import Document
from guests.models import Guest
from guests.permissions import (
is_guest_admin,
)
from guests.services import (
GuestLdapService,
)
from ...employees.serializers import EmployeeBriefSerializer
from ..permissions import GuestAdminPermission
from ..serializers import (
GuestSearchSerializer,
GuestSerializer,
)
class GuestViewSet(
mixins.ListModelMixin,
mixins.RetrieveModelMixin,
mixins.UpdateModelMixin,
viewsets.GenericViewSet,
):
queryset = (
Guest.objects.select_related("document_folder")
.prefetch_related("documents")
.order_by("last_name", "first_name", "id")
)
serializer_class = GuestSerializer
permission_classes = [GuestAdminPermission]
parser_classes = (MultiPartParser, FormParser, JSONParser)
def update(self, request, *args, **kwargs):
partial = kwargs.pop("partial", False)
instance = self.get_object()
serializer = self.get_serializer(
instance,
data=request.data,
partial=partial,
)
serializer.is_valid(raise_exception=True)
guest = serializer.save()
GuestLdapService().sync_guest(
guest,
enqueue_on_error=True,
raise_on_error=False,
)
guest.refresh_from_db()
return Response(self.get_serializer(guest).data)
def get_queryset(self):
qs = super().get_queryset()
guest_ct = ContentType.objects.get_for_model(Guest)
comments_subquery = (
Message.objects.filter(
chat__type="comments",
chat__context_content_type=guest_ct,
chat__context_object_id=OuterRef("pk"),
is_deleted=False,
)
.values("chat")
.annotate(count=Count("id"))
.values("count")
)
qs = qs.annotate(
comments_count=Coalesce(
Subquery(comments_subquery, output_field=IntegerField()),
Value(0),
),
visits_count=Count("visits", distinct=True),
)
q = (self.request.query_params.get("q") or "").strip()
if q:
text_q = (
Q(last_name__icontains=q)
| Q(first_name__icontains=q)
| Q(patronymic__icontains=q)
| Q(email__icontains=q)
| Q(phone__icontains=q)
| Q(organization__icontains=q)
)
if q.isdigit():
text_q |= Q(id=int(q))
qs = qs.filter(text_q)
if self.request.query_params.get("is_active") in {"true", "false"}:
qs = qs.filter(
is_active=self.request.query_params.get("is_active") == "true"
)
if self.request.query_params.get("is_blacklisted") in {"true", "false"}:
qs = qs.filter(
is_blacklisted=self.request.query_params.get("is_blacklisted") == "true"
)
ordering = self.request.query_params.get("ordering")
allowed_ordering = {
"last_name",
"-last_name",
"created_at",
"-created_at",
"updated_at",
"-updated_at",
"ldap_last_synced_at",
"-ldap_last_synced_at",
}
if ordering in allowed_ordering:
qs = qs.order_by(ordering, "id")
return qs
@action(detail=False, methods=["get"])
def search(self, request):
serializer = GuestSearchSerializer(
self.get_queryset()[:20],
many=True,
context=self.get_serializer_context(),
)
return Response(serializer.data)
def _guest_document_response(
self,
guest: Guest,
document: Document,
response_status=status.HTTP_200_OK,
):
guest.refresh_from_db()
return Response(
{
"guest": self.get_serializer(guest).data,
"document": DocumentReadSerializer(
document,
context=self.get_serializer_context(),
).data,
},
status=response_status,
)
@action(detail=True, methods=["get", "post"])
def comments(self, request, pk=None):
guest = self.get_object()
if request.method == "GET":
messages = comments_helpers.get_comments(guest)
return Response(
[
{
"id": msg.id,
"author": EmployeeBriefSerializer(msg.author).data,
"text": msg.content,
"metadata": msg.system_metadata,
"created_at": msg.created_at,
}
for msg in messages
]
)
text = (request.data.get("text") or "").strip()
if not text:
raise ValidationError({"text": "Это поле не может быть пустым."})
msg = comments_helpers.create_comment(guest, request.user, text)
return Response(
{
"id": msg.id,
"author": EmployeeBriefSerializer(msg.author).data,
"text": msg.content,
"metadata": msg.system_metadata,
"created_at": msg.created_at,
},
status=status.HTTP_201_CREATED,
)
@action(
detail=True,
methods=["delete"],
url_path="comments/(?P<comment_id>[0-9]+)",
)
def delete_comment(self, request, pk=None, comment_id=None):
guest = self.get_object()
chat = comments_helpers.get_or_create_comments_chat(guest)
message = get_object_or_404(Message, id=comment_id, chat=chat)
if message.author_id != request.user.id and not is_guest_admin(request.user):
raise PermissionDenied("Недостаточно прав для удаления комментария.")
comments_helpers.delete_comment(message, request.user, soft_delete=True)
return Response(status=status.HTTP_204_NO_CONTENT)
@action(detail=True, methods=["post"], url_path="documents")
def documents(self, request, pk=None):
guest = self.get_object()
document_id = request.data.get("document_id")
if document_id:
document = get_object_or_404(Document, pk=document_id)
guest.documents.add(document)
return self._guest_document_response(guest, document)
upload = request.FILES.get("file")
if not upload:
raise ValidationError({"file": "Передайте файл или document_id."})
folder = guest.ensure_document_folder(owner=request.user)
file_name = getattr(upload, "name", "") or "document"
title = (request.data.get("title") or "").strip()
if not title:
title = os.path.splitext(os.path.basename(file_name))[0] or file_name
filer_file = FilerFile.objects.create(
file=upload,
folder=folder,
original_filename=file_name,
name=file_name,
owner=request.user,
is_public=True,
)
document = Document.objects.create(
title=title,
description=(request.data.get("description") or "").strip(),
file=filer_file,
folder=folder,
uploaded_by=request.user,
modified_by=request.user,
sent_to_all=False,
)
guest.documents.add(document)
return self._guest_document_response(
guest,
document,
response_status=status.HTTP_201_CREATED,
)
@action(
detail=True,
methods=["delete"],
url_path="documents/(?P<document_id>[0-9]+)",
)
def remove_document(self, request, pk=None, document_id=None):
if not is_guest_admin(request.user):
raise PermissionDenied("Недостаточно прав для изменения гостя.")
guest = self.get_object()
document = get_object_or_404(Document, pk=document_id)
guest.documents.remove(document)
return self._guest_document_response(guest, document)
@action(detail=True, methods=["post"])
def blacklist(self, request, pk=None):
guest = self.get_object()
GuestLdapService().blacklist_guest(guest)
guest.refresh_from_db()
return Response(self.get_serializer(guest).data)
@action(detail=True, methods=["post"], url_path="unblacklist")
def unblacklist(self, request, pk=None):
guest = self.get_object()
GuestLdapService().unblacklist_guest(guest)
guest.refresh_from_db()
return Response(self.get_serializer(guest).data)
@action(detail=True, methods=["post"], url_path="sync-ldap")
def sync_ldap(self, request, pk=None):
guest = self.get_object()
try:
GuestLdapService().sync_guest(
guest,
enqueue_on_error=True,
raise_on_error=True,
)
except Exception as exc:
guest.refresh_from_db()
raise ValidationError(str(exc)) from exc
guest.refresh_from_db()
return Response(self.get_serializer(guest).data)