Corp/backend/api/v1/tasks/views.py

2174 lines
75 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

from django.contrib.contenttypes.models import ContentType
from django.contrib.auth import get_user_model
from django.db.models import (
Count,
IntegerField,
Max,
OuterRef,
Q,
Subquery,
Value,
)
from django.db.models.functions import Coalesce
from attendance.models import AttendanceRecord
from communications import comments_helpers
from communications.models import Message
from communications.utils import user_can_access_chat
from documents.models import Document
from feed.models import Post
from guests.models import Guest, GuestVisit
from procurement.models import ProcurementRequest
from requests_app.models import Request as EmployeeRequest
from rest_framework import filters, status, viewsets
from rest_framework.decorators import action
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from schedule.models import Event
from tasks.access import (
task_board_access_q,
user_can_access_calendar_event,
user_can_access_document,
user_can_access_employee,
user_can_access_employee_request,
user_can_access_feed_post,
user_can_access_guest,
user_can_access_guest_visit,
user_can_access_attendance_record,
user_can_access_procurement_request,
)
from tasks.models import (
Task,
TaskActivity,
TaskActivityAction,
TaskBoard,
TaskColumn,
TaskLabel,
TaskLinkedObject,
TaskLinkedObjectKind,
TaskUserSettings,
)
from tasks.realtime import (
get_task_board_recipient_ids,
send_task_board_update,
)
from .serializers import (
EmployeeBriefSerializer,
TaskActivitySerializer,
TaskBoardSerializer,
TaskColumnSerializer,
TaskLabelSerializer,
TaskLinkedCalendarEventSerializer,
TaskLinkedAttendanceRecordSerializer,
TaskLinkedDocumentSerializer,
TaskLinkedEmployeeSerializer,
TaskLinkedGuestSerializer,
TaskLinkedGuestVisitSerializer,
TaskLinkedMessageSerializer,
TaskLinkedPostSerializer,
TaskLinkedProcurementRequestSerializer,
TaskLinkedRequestSerializer,
TaskSerializer,
create_default_columns,
get_attendance_record_content_type,
get_calendar_event_content_type,
get_document_content_type,
get_employee_content_type,
get_guest_content_type,
get_guest_visit_content_type,
get_message_content_type,
get_post_content_type,
get_procurement_request_content_type,
get_request_content_type,
)
Employee = get_user_model()
def create_task_activity(
task,
actor,
action,
*,
object_kind="",
object_id=None,
metadata=None,
):
TaskActivity.objects.create(
task=task,
actor=actor if getattr(actor, "is_authenticated", False) else None,
action=action,
object_kind=object_kind or "",
object_id=object_id,
metadata=metadata or {},
)
def normalize_column_positions(column):
task_ids = list(
column.tasks.order_by("position", "-created_at", "id").values_list(
"id",
flat=True,
)
)
for index, task_id in enumerate(task_ids):
Task.objects.filter(id=task_id).update(position=(index + 1) * 1000)
class TaskBoardViewSet(viewsets.ModelViewSet):
permission_classes = [IsAuthenticated]
serializer_class = TaskBoardSerializer
def get_queryset(self):
return (
TaskBoard.objects.filter(is_archived=False)
.filter(task_board_access_q(self.request.user))
.select_related("created_by")
.prefetch_related(
"members",
"departments",
"columns",
"labels",
"tasks__created_by",
"tasks__assignee",
"tasks__labels",
"tasks__linked_objects",
)
.annotate(tasks_count=Count("tasks", distinct=True))
.distinct()
.order_by("name", "id")
)
def perform_create(self, serializer):
board = serializer.save(created_by=self.request.user)
create_default_columns(board)
send_task_board_update(board, "created", "board", board.id)
def perform_update(self, serializer):
board = self.get_object()
previous_recipient_ids = get_task_board_recipient_ids(board)
board = serializer.save()
recipient_ids = previous_recipient_ids | get_task_board_recipient_ids(board)
send_task_board_update(
board,
"updated",
"board",
board.id,
recipient_ids=recipient_ids,
)
def perform_destroy(self, instance):
recipient_ids = get_task_board_recipient_ids(instance)
board_id = instance.id
instance.delete()
send_task_board_update(
None,
"deleted",
"board",
board_id,
recipient_ids=recipient_ids,
board_id=board_id,
)
@action(detail=False, methods=["get"], url_path="default")
def default(self, request):
queryset = self.get_queryset()
settings_obj = (
TaskUserSettings.objects.select_related("default_board")
.filter(user=request.user)
.first()
)
board = None
if settings_obj and settings_obj.default_board_id:
board = queryset.filter(id=settings_obj.default_board_id).first()
if board is None:
settings_obj.default_board = None
settings_obj.save(update_fields=["default_board", "updated_at"])
if board is None:
board = queryset.order_by("id").first()
if not board:
board = TaskBoard.objects.create(
name="Рабочая доска",
description="",
created_by=request.user,
)
create_default_columns(board)
serializer = self.get_serializer(self.get_queryset().get(id=board.id))
return Response(serializer.data)
@action(detail=True, methods=["post"], url_path="set-default")
def set_default(self, request, pk=None):
board = self.get_object()
TaskUserSettings.objects.update_or_create(
user=request.user,
defaults={"default_board": board},
)
serializer = self.get_serializer(self.get_queryset().get(id=board.id))
return Response(serializer.data)
class TaskColumnViewSet(viewsets.ModelViewSet):
permission_classes = [IsAuthenticated]
serializer_class = TaskColumnSerializer
def get_queryset(self):
queryset = (
TaskColumn.objects.filter(board__is_archived=False)
.filter(
board__in=TaskBoard.objects.filter(
task_board_access_q(self.request.user)
)
)
.select_related("board")
.annotate(tasks_count=Count("tasks", distinct=True))
.order_by("board_id", "position", "id")
)
board = self.request.query_params.get("board")
if board:
queryset = queryset.filter(board_id=board)
return queryset
def perform_create(self, serializer):
column = serializer.save()
send_task_board_update(column.board, "created", "column", column.id)
def perform_update(self, serializer):
column = serializer.save()
send_task_board_update(column.board, "updated", "column", column.id)
def perform_destroy(self, instance):
board = instance.board
column_id = instance.id
instance.delete()
send_task_board_update(board, "deleted", "column", column_id)
class TaskLabelViewSet(viewsets.ModelViewSet):
permission_classes = [IsAuthenticated]
serializer_class = TaskLabelSerializer
def get_queryset(self):
queryset = (
TaskLabel.objects.filter(board__is_archived=False)
.filter(
board__in=TaskBoard.objects.filter(
task_board_access_q(self.request.user)
)
)
.select_related("board")
)
board = self.request.query_params.get("board")
if board:
queryset = queryset.filter(board_id=board)
return queryset.order_by("name", "id")
def perform_create(self, serializer):
label = serializer.save()
send_task_board_update(label.board, "created", "label", label.id)
def perform_update(self, serializer):
label = serializer.save()
send_task_board_update(label.board, "updated", "label", label.id)
def perform_destroy(self, instance):
board = instance.board
label_id = instance.id
instance.delete()
send_task_board_update(board, "deleted", "label", label_id)
class TaskViewSet(viewsets.ModelViewSet):
permission_classes = [IsAuthenticated]
serializer_class = TaskSerializer
filter_backends = [filters.SearchFilter, filters.OrderingFilter]
search_fields = ["title", "description"]
ordering_fields = ["position", "created_at", "updated_at", "due_date"]
ordering = ["position", "-created_at"]
def get_queryset(self):
task_content_type = ContentType.objects.get_for_model(Task)
comments_subquery = (
Message.objects.filter(
chat__type="comments",
chat__context_content_type=task_content_type,
chat__context_object_id=OuterRef("pk"),
is_deleted=False,
)
.values("chat")
.annotate(count=Count("id"))
.values("count")
)
queryset = (
Task.objects.filter(board__is_archived=False)
.filter(
board__in=TaskBoard.objects.filter(
task_board_access_q(self.request.user)
)
)
.select_related("board", "column", "created_by", "assignee")
.prefetch_related("labels")
.annotate(
linked_posts_count=Count(
"linked_objects",
filter=Q(linked_objects__kind=TaskLinkedObjectKind.POST),
distinct=True,
),
linked_messages_count=Count(
"linked_objects",
filter=Q(linked_objects__kind=TaskLinkedObjectKind.MESSAGE),
distinct=True,
),
linked_events_count=Count(
"linked_objects",
filter=Q(
linked_objects__kind=TaskLinkedObjectKind.CALENDAR_EVENT
),
distinct=True,
),
linked_documents_count=Count(
"linked_objects",
filter=Q(
linked_objects__kind=TaskLinkedObjectKind.DOCUMENT,
),
distinct=True,
),
linked_requests_count=Count(
"linked_objects",
filter=Q(
linked_objects__kind=TaskLinkedObjectKind.REQUEST,
),
distinct=True,
),
linked_procurement_requests_count=Count(
"linked_objects",
filter=Q(
linked_objects__kind=(
TaskLinkedObjectKind.PROCUREMENT_REQUEST
),
),
distinct=True,
),
linked_employees_count=Count(
"linked_objects",
filter=Q(
linked_objects__kind=TaskLinkedObjectKind.EMPLOYEE,
),
distinct=True,
),
linked_guests_count=Count(
"linked_objects",
filter=Q(
linked_objects__kind=TaskLinkedObjectKind.GUEST,
),
distinct=True,
),
linked_guest_visits_count=Count(
"linked_objects",
filter=Q(
linked_objects__kind=TaskLinkedObjectKind.GUEST_VISIT,
),
distinct=True,
),
linked_attendance_records_count=Count(
"linked_objects",
filter=Q(
linked_objects__kind=(
TaskLinkedObjectKind.ATTENDANCE_RECORD
),
),
distinct=True,
),
linked_objects_count=Count("linked_objects", distinct=True),
comments_count=Coalesce(
Subquery(comments_subquery, output_field=IntegerField()),
Value(0),
),
)
)
params = self.request.query_params
if board := params.get("board"):
queryset = queryset.filter(board_id=board)
if column := params.get("column"):
queryset = queryset.filter(column_id=column)
if assignee := params.get("assignee"):
queryset = queryset.filter(assignee_id=assignee)
if priority := params.get("priority"):
queryset = queryset.filter(priority=priority)
if params.get("unassigned") == "true":
queryset = queryset.filter(assignee__isnull=True)
return queryset
def perform_create(self, serializer):
task = serializer.save(created_by=self.request.user)
create_task_activity(
task,
self.request.user,
TaskActivityAction.CREATED,
metadata={"column": task.column.name},
)
send_task_board_update(task.board, "created", "task", task.id)
def perform_update(self, serializer):
instance = serializer.instance
previous = {
"title": instance.title,
"description": instance.description,
"assignee_id": instance.assignee_id,
"priority": instance.priority,
"due_date": instance.due_date.isoformat() if instance.due_date else None,
"label_ids": sorted(instance.labels.values_list("id", flat=True)),
}
task = serializer.save()
current = {
"title": task.title,
"description": task.description,
"assignee_id": task.assignee_id,
"priority": task.priority,
"due_date": task.due_date.isoformat() if task.due_date else None,
"label_ids": sorted(task.labels.values_list("id", flat=True)),
}
changed_fields = [
field for field, value in current.items() if previous[field] != value
]
if changed_fields:
create_task_activity(
task,
self.request.user,
TaskActivityAction.UPDATED,
metadata={"fields": changed_fields},
)
send_task_board_update(task.board, "updated", "task", task.id)
def perform_destroy(self, instance):
board = instance.board
task_id = instance.id
instance.delete()
send_task_board_update(board, "deleted", "task", task_id)
@action(detail=True, methods=["post"])
def move(self, request, pk=None):
task = self.get_object()
column_id = request.data.get("column")
position = request.data.get("position")
if not column_id:
return Response(
{"column": ["Это поле обязательно."]},
status=status.HTTP_400_BAD_REQUEST,
)
try:
column = TaskColumn.objects.get(
id=column_id,
board_id=task.board_id,
is_archived=False,
)
except TaskColumn.DoesNotExist:
return Response(
{"column": ["Колонка не найдена на этой доске."]},
status=status.HTTP_400_BAD_REQUEST,
)
old_column = task.column
if position in (None, ""):
max_position = (
Task.objects.filter(column=column).exclude(id=task.id).aggregate(
Max("position")
)["position__max"]
or 0
)
position = max_position + 1000
else:
try:
position = int(position)
except (TypeError, ValueError):
return Response(
{"position": ["Позиция должна быть числом."]},
status=status.HTTP_400_BAD_REQUEST,
)
task.column = column
task.position = position
task.save(update_fields=["column", "position", "completed_at", "updated_at"])
normalize_column_positions(column)
if old_column.id != column.id:
normalize_column_positions(old_column)
task.refresh_from_db()
serializer = self.get_serializer(task)
create_task_activity(
task,
request.user,
TaskActivityAction.MOVED,
metadata={
"from_column_id": old_column.id,
"from_column": old_column.name,
"to_column_id": column.id,
"to_column": column.name,
},
)
send_task_board_update(
task.board,
"moved",
"task",
task.id,
extra={"column_id": task.column_id},
)
return Response(serializer.data)
@action(detail=True, methods=["get"], url_path="activity")
def activity(self, request, pk=None):
task = self.get_object()
queryset = task.activities.select_related("actor").order_by(
"-created_at",
"-id",
)
serializer = TaskActivitySerializer(queryset, many=True)
return Response(serializer.data)
def _comment_payload(self, task, message):
return {
"id": message.id,
"task": task.id,
"author": EmployeeBriefSerializer(message.author).data,
"text": message.content,
"created_at": message.created_at,
}
@action(detail=True, methods=["get", "post"], url_path="comments")
def comments(self, request, pk=None):
task = self.get_object()
if request.method in ("GET", "HEAD"):
messages = comments_helpers.get_comments(task)
return Response(
[self._comment_payload(task, message) for message in messages]
)
text = request.data.get("text", "").strip()
if not text:
return Response(
{"text": ["Это поле не может быть пустым."]},
status=status.HTTP_400_BAD_REQUEST,
)
message = comments_helpers.create_comment(
obj=task,
author=request.user,
content=text,
)
send_task_board_update(
task.board,
"commented",
"task",
task.id,
extra={"comment_id": message.id},
)
return Response(
self._comment_payload(task, message),
status=status.HTTP_201_CREATED,
)
@action(
detail=True,
methods=["delete"],
url_path=r"comments/(?P<comment_id>\d+)",
)
def delete_comment(self, request, pk=None, comment_id=None):
task = self.get_object()
chat = comments_helpers.get_comments_chat_if_exists(task)
if chat is None:
return Response(
{"detail": "Комментарий не найден."},
status=status.HTTP_404_NOT_FOUND,
)
try:
message = Message.objects.get(
id=comment_id,
chat=chat,
is_deleted=False,
)
except Message.DoesNotExist:
return Response(
{"detail": "Комментарий не найден."},
status=status.HTTP_404_NOT_FOUND,
)
if not (
request.user.is_staff
or request.user.is_superuser
or message.author_id == request.user.id
):
return Response(
{"detail": "Нет прав на удаление комментария."},
status=status.HTTP_403_FORBIDDEN,
)
comments_helpers.delete_comment(
message=message,
deleted_by=request.user,
soft_delete=True,
)
send_task_board_update(
task.board,
"comment_deleted",
"task",
task.id,
extra={"comment_id": int(comment_id)},
)
return Response(status=status.HTTP_204_NO_CONTENT)
@action(detail=False, methods=["get"], url_path="linked-event-tasks")
def linked_event_tasks(self, request):
event_id = request.query_params.get("event_id")
try:
event_id = int(event_id)
except (TypeError, ValueError):
return Response(
{"event_id": ["Укажите ID события."]},
status=status.HTTP_400_BAD_REQUEST,
)
try:
event = Event.objects.select_related(
"calendar",
"calendar__binding",
"calendar__binding__context_content_type",
"rule",
"creator",
).get(id=event_id)
except Event.DoesNotExist:
return Response(
{"event_id": ["Событие не найдено."]},
status=status.HTTP_404_NOT_FOUND,
)
if not user_can_access_calendar_event(request.user, event):
return Response(
{"detail": "Нет доступа к этому событию."},
status=status.HTTP_403_FORBIDDEN,
)
queryset = (
self.get_queryset()
.filter(
linked_objects__kind=TaskLinkedObjectKind.CALENDAR_EVENT,
linked_objects__content_type=get_calendar_event_content_type(),
linked_objects__object_id=event.id,
)
.distinct()
)
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)
@action(detail=False, methods=["get"], url_path="linked-document-tasks")
def linked_document_tasks(self, request):
document_id = request.query_params.get("document_id")
try:
document_id = int(document_id)
except (TypeError, ValueError):
return Response(
{"document_id": ["Укажите ID документа."]},
status=status.HTTP_400_BAD_REQUEST,
)
try:
document = (
Document.objects.select_related("uploaded_by", "file", "folder")
.prefetch_related("recipients", "departments", "tags")
.get(id=document_id)
)
except Document.DoesNotExist:
return Response(
{"document_id": ["Документ не найден."]},
status=status.HTTP_404_NOT_FOUND,
)
if not user_can_access_document(request.user, document):
return Response(
{"detail": "Нет доступа к этому документу."},
status=status.HTTP_403_FORBIDDEN,
)
queryset = (
self.get_queryset()
.filter(
linked_objects__kind=TaskLinkedObjectKind.DOCUMENT,
linked_objects__content_type=get_document_content_type(),
linked_objects__object_id=document.id,
)
.distinct()
)
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)
@action(detail=False, methods=["get"], url_path="linked-request-tasks")
def linked_request_tasks(self, request):
request_id = request.query_params.get("request_id")
try:
request_id = int(request_id)
except (TypeError, ValueError):
return Response(
{"request_id": ["Укажите ID заявления."]},
status=status.HTTP_400_BAD_REQUEST,
)
try:
employee_request = (
EmployeeRequest.objects.select_related(
"employee",
"approver",
"department",
)
.prefetch_related("recipients", "cc_users")
.get(id=request_id)
)
except EmployeeRequest.DoesNotExist:
return Response(
{"request_id": ["Заявление не найдено."]},
status=status.HTTP_404_NOT_FOUND,
)
if not user_can_access_employee_request(request.user, employee_request):
return Response(
{"detail": "Нет доступа к этому заявлению."},
status=status.HTTP_403_FORBIDDEN,
)
queryset = (
self.get_queryset()
.filter(
linked_objects__kind=TaskLinkedObjectKind.REQUEST,
linked_objects__content_type=get_request_content_type(),
linked_objects__object_id=employee_request.id,
)
.distinct()
)
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)
@action(detail=False, methods=["get"], url_path="linked-procurement-request-tasks")
def linked_procurement_request_tasks(self, request):
procurement_request_id = request.query_params.get("procurement_request_id")
try:
procurement_request_id = int(procurement_request_id)
except (TypeError, ValueError):
return Response(
{"procurement_request_id": ["Укажите ID заявки на закупку."]},
status=status.HTTP_400_BAD_REQUEST,
)
try:
procurement_request = (
ProcurementRequest.objects.select_related(
"department",
"processing_department",
"requestor",
"executor",
)
.prefetch_related("approvals", "items")
.get(id=procurement_request_id)
)
except ProcurementRequest.DoesNotExist:
return Response(
{"procurement_request_id": ["Заявка на закупку не найдена."]},
status=status.HTTP_404_NOT_FOUND,
)
if not user_can_access_procurement_request(request.user, procurement_request):
return Response(
{"detail": "Нет доступа к этой заявке на закупку."},
status=status.HTTP_403_FORBIDDEN,
)
queryset = (
self.get_queryset()
.filter(
linked_objects__kind=TaskLinkedObjectKind.PROCUREMENT_REQUEST,
linked_objects__content_type=get_procurement_request_content_type(),
linked_objects__object_id=procurement_request.id,
)
.distinct()
)
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)
@action(detail=False, methods=["get"], url_path="linked-employee-tasks")
def linked_employee_tasks(self, request):
employee_id = request.query_params.get("employee_id")
try:
employee_id = int(employee_id)
except (TypeError, ValueError):
return Response(
{"employee_id": ["Укажите ID сотрудника."]},
status=status.HTTP_400_BAD_REQUEST,
)
try:
employee = Employee.objects.select_related("position").get(id=employee_id)
except Employee.DoesNotExist:
return Response(
{"employee_id": ["Сотрудник не найден."]},
status=status.HTTP_404_NOT_FOUND,
)
if not user_can_access_employee(request.user, employee):
return Response(
{"detail": "Нет доступа к этому сотруднику."},
status=status.HTTP_403_FORBIDDEN,
)
queryset = (
self.get_queryset()
.filter(
linked_objects__kind=TaskLinkedObjectKind.EMPLOYEE,
linked_objects__content_type=get_employee_content_type(),
linked_objects__object_id=employee.id,
)
.distinct()
)
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)
@action(detail=False, methods=["get"], url_path="linked-post-tasks")
def linked_post_tasks(self, request):
post_id = request.query_params.get("post_id")
try:
post_id = int(post_id)
except (TypeError, ValueError):
return Response(
{"post_id": ["Укажите ID новости."]},
status=status.HTTP_400_BAD_REQUEST,
)
try:
post = Post.objects.select_related("author", "department").get(
id=post_id,
)
except Post.DoesNotExist:
return Response(
{"post_id": ["Новость не найдена."]},
status=status.HTTP_404_NOT_FOUND,
)
if not user_can_access_feed_post(request.user, post):
return Response(
{"detail": "Нет доступа к этой новости."},
status=status.HTTP_403_FORBIDDEN,
)
queryset = (
self.get_queryset()
.filter(
linked_objects__kind=TaskLinkedObjectKind.POST,
linked_objects__content_type=get_post_content_type(),
linked_objects__object_id=post.id,
)
.distinct()
)
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)
@action(detail=False, methods=["get"], url_path="linked-guest-tasks")
def linked_guest_tasks(self, request):
guest_id = request.query_params.get("guest_id")
try:
guest_id = int(guest_id)
except (TypeError, ValueError):
return Response(
{"guest_id": ["Укажите ID гостя."]},
status=status.HTTP_400_BAD_REQUEST,
)
try:
guest = Guest.objects.get(id=guest_id)
except Guest.DoesNotExist:
return Response(
{"guest_id": ["Гость не найден."]},
status=status.HTTP_404_NOT_FOUND,
)
if not user_can_access_guest(request.user, guest):
return Response(
{"detail": "Нет доступа к этому гостю."},
status=status.HTTP_403_FORBIDDEN,
)
queryset = (
self.get_queryset()
.filter(
linked_objects__kind=TaskLinkedObjectKind.GUEST,
linked_objects__content_type=get_guest_content_type(),
linked_objects__object_id=guest.id,
)
.distinct()
)
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)
@action(detail=False, methods=["get"], url_path="linked-guest-visit-tasks")
def linked_guest_visit_tasks(self, request):
guest_visit_id = request.query_params.get("guest_visit_id")
try:
guest_visit_id = int(guest_visit_id)
except (TypeError, ValueError):
return Response(
{"guest_visit_id": ["Укажите ID заявки на гостевой визит."]},
status=status.HTTP_400_BAD_REQUEST,
)
try:
visit = GuestVisit.objects.select_related("guest", "inviter").get(
id=guest_visit_id,
)
except GuestVisit.DoesNotExist:
return Response(
{"guest_visit_id": ["Заявка на гостевой визит не найдена."]},
status=status.HTTP_404_NOT_FOUND,
)
if not user_can_access_guest_visit(request.user, visit):
return Response(
{"detail": "Нет доступа к этой заявке на гостевой визит."},
status=status.HTTP_403_FORBIDDEN,
)
queryset = (
self.get_queryset()
.filter(
linked_objects__kind=TaskLinkedObjectKind.GUEST_VISIT,
linked_objects__content_type=get_guest_visit_content_type(),
linked_objects__object_id=visit.id,
)
.distinct()
)
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)
@action(detail=False, methods=["get"], url_path="linked-attendance-record-tasks")
def linked_attendance_record_tasks(self, request):
attendance_record_id = request.query_params.get("attendance_record_id")
try:
attendance_record_id = int(attendance_record_id)
except (TypeError, ValueError):
return Response(
{"attendance_record_id": ["Укажите ID записи посещаемости."]},
status=status.HTTP_400_BAD_REQUEST,
)
try:
record = AttendanceRecord.objects.select_related("employee").get(
id=attendance_record_id,
)
except AttendanceRecord.DoesNotExist:
return Response(
{"attendance_record_id": ["Запись посещаемости не найдена."]},
status=status.HTTP_404_NOT_FOUND,
)
if not user_can_access_attendance_record(request.user, record):
return Response(
{"detail": "Нет доступа к этой записи посещаемости."},
status=status.HTTP_403_FORBIDDEN,
)
queryset = (
self.get_queryset()
.filter(
linked_objects__kind=TaskLinkedObjectKind.ATTENDANCE_RECORD,
linked_objects__content_type=get_attendance_record_content_type(),
linked_objects__object_id=record.id,
)
.distinct()
)
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)
def _message_links_queryset(self, task):
return (
TaskLinkedObject.objects.filter(
task=task,
kind=TaskLinkedObjectKind.MESSAGE,
)
.select_related("created_by", "content_type")
.order_by("-created_at", "-id")
)
def _event_links_queryset(self, task):
return (
TaskLinkedObject.objects.filter(
task=task,
kind=TaskLinkedObjectKind.CALENDAR_EVENT,
)
.select_related("created_by", "content_type")
.order_by("-created_at", "-id")
)
def _document_links_queryset(self, task):
return (
TaskLinkedObject.objects.filter(
task=task,
kind=TaskLinkedObjectKind.DOCUMENT,
)
.select_related("created_by", "content_type")
.order_by("-created_at", "-id")
)
def _request_links_queryset(self, task):
return (
TaskLinkedObject.objects.filter(
task=task,
kind=TaskLinkedObjectKind.REQUEST,
)
.select_related("created_by", "content_type")
.order_by("-created_at", "-id")
)
def _procurement_request_links_queryset(self, task):
return (
TaskLinkedObject.objects.filter(
task=task,
kind=TaskLinkedObjectKind.PROCUREMENT_REQUEST,
)
.select_related("created_by", "content_type")
.order_by("-created_at", "-id")
)
def _employee_links_queryset(self, task):
return (
TaskLinkedObject.objects.filter(
task=task,
kind=TaskLinkedObjectKind.EMPLOYEE,
)
.select_related("created_by", "content_type")
.order_by("-created_at", "-id")
)
def _guest_links_queryset(self, task):
return (
TaskLinkedObject.objects.filter(
task=task,
kind=TaskLinkedObjectKind.GUEST,
)
.select_related("created_by", "content_type")
.order_by("-created_at", "-id")
)
def _guest_visit_links_queryset(self, task):
return (
TaskLinkedObject.objects.filter(
task=task,
kind=TaskLinkedObjectKind.GUEST_VISIT,
)
.select_related("created_by", "content_type")
.order_by("-created_at", "-id")
)
def _attendance_record_links_queryset(self, task):
return (
TaskLinkedObject.objects.filter(
task=task,
kind=TaskLinkedObjectKind.ATTENDANCE_RECORD,
)
.select_related("created_by", "content_type")
.order_by("-created_at", "-id")
)
def _post_links_queryset(self, task):
return (
TaskLinkedObject.objects.filter(
task=task,
kind=TaskLinkedObjectKind.POST,
)
.select_related("created_by", "content_type")
.order_by("-created_at", "-id")
)
@action(detail=True, methods=["get", "post"], url_path="linked-posts")
def linked_posts(self, request, pk=None):
task = self.get_object()
if request.method == "GET":
serializer = TaskLinkedPostSerializer(
self._post_links_queryset(task),
many=True,
context={"request": request},
)
return Response(serializer.data)
post_id = request.data.get("post_id")
try:
post_id = int(post_id)
except (TypeError, ValueError):
return Response(
{"post_id": ["Укажите ID новости."]},
status=status.HTTP_400_BAD_REQUEST,
)
try:
post = Post.objects.select_related("author", "department").get(
id=post_id,
)
except Post.DoesNotExist:
return Response(
{"post_id": ["Новость не найдена."]},
status=status.HTTP_404_NOT_FOUND,
)
if not user_can_access_feed_post(request.user, post):
return Response(
{"detail": "Нет доступа к этой новости."},
status=status.HTTP_403_FORBIDDEN,
)
link, created = TaskLinkedObject.objects.get_or_create(
task=task,
kind=TaskLinkedObjectKind.POST,
content_type=get_post_content_type(),
object_id=post.id,
defaults={"created_by": request.user},
)
serializer = TaskLinkedPostSerializer(
link,
context={"request": request},
)
if created:
create_task_activity(
task,
request.user,
TaskActivityAction.LINKED,
object_kind=TaskLinkedObjectKind.POST,
object_id=post.id,
metadata={
"object_label": post.title,
"object_type": "Новость",
},
)
send_task_board_update(
task.board,
"linked",
"post",
post.id,
extra={"task_id": task.id, "link_id": link.id},
)
return Response(
serializer.data,
status=status.HTTP_201_CREATED if created else status.HTTP_200_OK,
)
@action(
detail=True,
methods=["delete"],
url_path=r"linked-posts/(?P<link_id>\d+)",
)
def unlink_post(self, request, pk=None, link_id=None):
task = self.get_object()
try:
link = self._post_links_queryset(task).get(id=link_id)
except TaskLinkedObject.DoesNotExist:
return Response(
{"detail": "Связь не найдена."},
status=status.HTTP_404_NOT_FOUND,
)
post_id = link.object_id
link.delete()
create_task_activity(
task,
request.user,
TaskActivityAction.UNLINKED,
object_kind=TaskLinkedObjectKind.POST,
object_id=post_id,
metadata={"object_type": "Новость"},
)
send_task_board_update(
task.board,
"unlinked",
"post",
post_id,
extra={"task_id": task.id, "link_id": int(link_id)},
)
return Response(status=status.HTTP_204_NO_CONTENT)
@action(detail=True, methods=["get", "post"], url_path="linked-messages")
def linked_messages(self, request, pk=None):
task = self.get_object()
if request.method == "GET":
serializer = TaskLinkedMessageSerializer(
self._message_links_queryset(task),
many=True,
context={"request": request},
)
return Response(serializer.data)
message_id = request.data.get("message_id")
try:
message_id = int(message_id)
except (TypeError, ValueError):
return Response(
{"message_id": ["Укажите ID сообщения."]},
status=status.HTTP_400_BAD_REQUEST,
)
try:
message = (
Message.objects.select_related(
"author",
"chat",
"reply_to",
"reply_to__author",
"poll",
)
.prefetch_related(
"attachments",
"reactions",
"reactions__user",
"poll__options",
)
.get(id=message_id)
)
except Message.DoesNotExist:
return Response(
{"message_id": ["Сообщение не найдено."]},
status=status.HTTP_404_NOT_FOUND,
)
if message.is_deleted:
return Response(
{"message_id": ["Удаленное сообщение нельзя связать с задачей."]},
status=status.HTTP_400_BAD_REQUEST,
)
if not user_can_access_chat(message.chat, request.user):
return Response(
{"detail": "Нет доступа к этому сообщению."},
status=status.HTTP_403_FORBIDDEN,
)
link, created = TaskLinkedObject.objects.get_or_create(
task=task,
kind=TaskLinkedObjectKind.MESSAGE,
content_type=get_message_content_type(),
object_id=message.id,
defaults={"created_by": request.user},
)
serializer = TaskLinkedMessageSerializer(
link,
context={"request": request},
)
if created:
create_task_activity(
task,
request.user,
TaskActivityAction.LINKED,
object_kind=TaskLinkedObjectKind.MESSAGE,
object_id=message.id,
metadata={
"object_label": message.content[:120],
"object_type": "Сообщение",
},
)
send_task_board_update(
task.board,
"linked",
"message",
message.id,
extra={"task_id": task.id, "link_id": link.id},
)
return Response(
serializer.data,
status=status.HTTP_201_CREATED if created else status.HTTP_200_OK,
)
@action(
detail=True,
methods=["delete"],
url_path=r"linked-messages/(?P<link_id>\d+)",
)
def unlink_message(self, request, pk=None, link_id=None):
task = self.get_object()
try:
link = self._message_links_queryset(task).get(id=link_id)
except TaskLinkedObject.DoesNotExist:
return Response(
{"detail": "Связь не найдена."},
status=status.HTTP_404_NOT_FOUND,
)
message_id = link.object_id
link.delete()
create_task_activity(
task,
request.user,
TaskActivityAction.UNLINKED,
object_kind=TaskLinkedObjectKind.MESSAGE,
object_id=message_id,
metadata={"object_type": "Сообщение"},
)
send_task_board_update(
task.board,
"unlinked",
"message",
message_id,
extra={"task_id": task.id, "link_id": int(link_id)},
)
return Response(status=status.HTTP_204_NO_CONTENT)
@action(detail=True, methods=["get", "post"], url_path="linked-documents")
def linked_documents(self, request, pk=None):
task = self.get_object()
if request.method == "GET":
serializer = TaskLinkedDocumentSerializer(
self._document_links_queryset(task),
many=True,
context={"request": request},
)
return Response(serializer.data)
document_id = request.data.get("document_id")
try:
document_id = int(document_id)
except (TypeError, ValueError):
return Response(
{"document_id": ["Укажите ID документа."]},
status=status.HTTP_400_BAD_REQUEST,
)
try:
document = (
Document.objects.select_related("uploaded_by", "file", "folder")
.prefetch_related("recipients", "departments", "tags")
.get(id=document_id)
)
except Document.DoesNotExist:
return Response(
{"document_id": ["Документ не найден."]},
status=status.HTTP_404_NOT_FOUND,
)
if not user_can_access_document(request.user, document):
return Response(
{"detail": "Нет доступа к этому документу."},
status=status.HTTP_403_FORBIDDEN,
)
link, created = TaskLinkedObject.objects.get_or_create(
task=task,
kind=TaskLinkedObjectKind.DOCUMENT,
content_type=get_document_content_type(),
object_id=document.id,
defaults={"created_by": request.user},
)
serializer = TaskLinkedDocumentSerializer(
link,
context={"request": request},
)
if created:
create_task_activity(
task,
request.user,
TaskActivityAction.LINKED,
object_kind=TaskLinkedObjectKind.DOCUMENT,
object_id=document.id,
metadata={
"object_label": document.title,
"object_type": "Документ",
},
)
send_task_board_update(
task.board,
"linked",
"document",
document.id,
extra={"task_id": task.id, "link_id": link.id},
)
return Response(
serializer.data,
status=status.HTTP_201_CREATED if created else status.HTTP_200_OK,
)
@action(
detail=True,
methods=["delete"],
url_path=r"linked-documents/(?P<link_id>\d+)",
)
def unlink_document(self, request, pk=None, link_id=None):
task = self.get_object()
try:
link = self._document_links_queryset(task).get(id=link_id)
except TaskLinkedObject.DoesNotExist:
return Response(
{"detail": "Связь не найдена."},
status=status.HTTP_404_NOT_FOUND,
)
document_id = link.object_id
link.delete()
create_task_activity(
task,
request.user,
TaskActivityAction.UNLINKED,
object_kind=TaskLinkedObjectKind.DOCUMENT,
object_id=document_id,
metadata={"object_type": "Документ"},
)
send_task_board_update(
task.board,
"unlinked",
"document",
document_id,
extra={"task_id": task.id, "link_id": int(link_id)},
)
return Response(status=status.HTTP_204_NO_CONTENT)
@action(detail=True, methods=["get", "post"], url_path="linked-requests")
def linked_requests(self, request, pk=None):
task = self.get_object()
if request.method == "GET":
serializer = TaskLinkedRequestSerializer(
self._request_links_queryset(task),
many=True,
context={"request": request},
)
return Response(serializer.data)
request_id = request.data.get("request_id")
try:
request_id = int(request_id)
except (TypeError, ValueError):
return Response(
{"request_id": ["Укажите ID заявления."]},
status=status.HTTP_400_BAD_REQUEST,
)
try:
employee_request = (
EmployeeRequest.objects.select_related(
"employee",
"approver",
"department",
)
.prefetch_related("recipients", "cc_users")
.get(id=request_id)
)
except EmployeeRequest.DoesNotExist:
return Response(
{"request_id": ["Заявление не найдено."]},
status=status.HTTP_404_NOT_FOUND,
)
if not user_can_access_employee_request(request.user, employee_request):
return Response(
{"detail": "Нет доступа к этому заявлению."},
status=status.HTTP_403_FORBIDDEN,
)
link, created = TaskLinkedObject.objects.get_or_create(
task=task,
kind=TaskLinkedObjectKind.REQUEST,
content_type=get_request_content_type(),
object_id=employee_request.id,
defaults={"created_by": request.user},
)
serializer = TaskLinkedRequestSerializer(
link,
context={"request": request},
)
if created:
create_task_activity(
task,
request.user,
TaskActivityAction.LINKED,
object_kind=TaskLinkedObjectKind.REQUEST,
object_id=employee_request.id,
metadata={
"object_label": employee_request.display_title,
"object_type": "Заявление",
},
)
send_task_board_update(
task.board,
"linked",
"request",
employee_request.id,
extra={"task_id": task.id, "link_id": link.id},
)
return Response(
serializer.data,
status=status.HTTP_201_CREATED if created else status.HTTP_200_OK,
)
@action(
detail=True,
methods=["delete"],
url_path=r"linked-requests/(?P<link_id>\d+)",
)
def unlink_request(self, request, pk=None, link_id=None):
task = self.get_object()
try:
link = self._request_links_queryset(task).get(id=link_id)
except TaskLinkedObject.DoesNotExist:
return Response(
{"detail": "Связь не найдена."},
status=status.HTTP_404_NOT_FOUND,
)
request_id = link.object_id
link.delete()
create_task_activity(
task,
request.user,
TaskActivityAction.UNLINKED,
object_kind=TaskLinkedObjectKind.REQUEST,
object_id=request_id,
metadata={"object_type": "Заявление"},
)
send_task_board_update(
task.board,
"unlinked",
"request",
request_id,
extra={"task_id": task.id, "link_id": int(link_id)},
)
return Response(status=status.HTTP_204_NO_CONTENT)
@action(detail=True, methods=["get", "post"], url_path="linked-procurement-requests")
def linked_procurement_requests(self, request, pk=None):
task = self.get_object()
if request.method == "GET":
serializer = TaskLinkedProcurementRequestSerializer(
self._procurement_request_links_queryset(task),
many=True,
context={"request": request},
)
return Response(serializer.data)
procurement_request_id = request.data.get("procurement_request_id")
try:
procurement_request_id = int(procurement_request_id)
except (TypeError, ValueError):
return Response(
{"procurement_request_id": ["Укажите ID заявки на закупку."]},
status=status.HTTP_400_BAD_REQUEST,
)
try:
procurement_request = (
ProcurementRequest.objects.select_related(
"department",
"processing_department",
"requestor",
"executor",
)
.prefetch_related("approvals", "items")
.get(id=procurement_request_id)
)
except ProcurementRequest.DoesNotExist:
return Response(
{"procurement_request_id": ["Заявка на закупку не найдена."]},
status=status.HTTP_404_NOT_FOUND,
)
if not user_can_access_procurement_request(request.user, procurement_request):
return Response(
{"detail": "Нет доступа к этой заявке на закупку."},
status=status.HTTP_403_FORBIDDEN,
)
link, created = TaskLinkedObject.objects.get_or_create(
task=task,
kind=TaskLinkedObjectKind.PROCUREMENT_REQUEST,
content_type=get_procurement_request_content_type(),
object_id=procurement_request.id,
defaults={"created_by": request.user},
)
serializer = TaskLinkedProcurementRequestSerializer(
link,
context={"request": request},
)
if created:
create_task_activity(
task,
request.user,
TaskActivityAction.LINKED,
object_kind=TaskLinkedObjectKind.PROCUREMENT_REQUEST,
object_id=procurement_request.id,
metadata={
"object_label": procurement_request.title,
"object_type": "Заявка на закупку",
},
)
send_task_board_update(
task.board,
"linked",
"procurement_request",
procurement_request.id,
extra={"task_id": task.id, "link_id": link.id},
)
return Response(
serializer.data,
status=status.HTTP_201_CREATED if created else status.HTTP_200_OK,
)
@action(
detail=True,
methods=["delete"],
url_path=r"linked-procurement-requests/(?P<link_id>\d+)",
)
def unlink_procurement_request(self, request, pk=None, link_id=None):
task = self.get_object()
try:
link = self._procurement_request_links_queryset(task).get(id=link_id)
except TaskLinkedObject.DoesNotExist:
return Response(
{"detail": "Связь не найдена."},
status=status.HTTP_404_NOT_FOUND,
)
procurement_request_id = link.object_id
link.delete()
create_task_activity(
task,
request.user,
TaskActivityAction.UNLINKED,
object_kind=TaskLinkedObjectKind.PROCUREMENT_REQUEST,
object_id=procurement_request_id,
metadata={"object_type": "Заявка на закупку"},
)
send_task_board_update(
task.board,
"unlinked",
"procurement_request",
procurement_request_id,
extra={"task_id": task.id, "link_id": int(link_id)},
)
return Response(status=status.HTTP_204_NO_CONTENT)
@action(detail=True, methods=["get", "post"], url_path="linked-employees")
def linked_employees(self, request, pk=None):
task = self.get_object()
if request.method == "GET":
serializer = TaskLinkedEmployeeSerializer(
self._employee_links_queryset(task),
many=True,
context={"request": request},
)
return Response(serializer.data)
employee_id = request.data.get("employee_id")
try:
employee_id = int(employee_id)
except (TypeError, ValueError):
return Response(
{"employee_id": ["Укажите ID сотрудника."]},
status=status.HTTP_400_BAD_REQUEST,
)
try:
employee = Employee.objects.select_related("position").get(id=employee_id)
except Employee.DoesNotExist:
return Response(
{"employee_id": ["Сотрудник не найден."]},
status=status.HTTP_404_NOT_FOUND,
)
if not user_can_access_employee(request.user, employee):
return Response(
{"detail": "Нет доступа к этому сотруднику."},
status=status.HTTP_403_FORBIDDEN,
)
link, created = TaskLinkedObject.objects.get_or_create(
task=task,
kind=TaskLinkedObjectKind.EMPLOYEE,
content_type=get_employee_content_type(),
object_id=employee.id,
defaults={"created_by": request.user},
)
serializer = TaskLinkedEmployeeSerializer(
link,
context={"request": request},
)
if created:
create_task_activity(
task,
request.user,
TaskActivityAction.LINKED,
object_kind=TaskLinkedObjectKind.EMPLOYEE,
object_id=employee.id,
metadata={
"object_label": employee.get_full_name() or employee.email,
"object_type": "Сотрудник",
},
)
send_task_board_update(
task.board,
"linked",
"employee",
employee.id,
extra={"task_id": task.id, "link_id": link.id},
)
return Response(
serializer.data,
status=status.HTTP_201_CREATED if created else status.HTTP_200_OK,
)
@action(
detail=True,
methods=["delete"],
url_path=r"linked-employees/(?P<link_id>\d+)",
)
def unlink_employee(self, request, pk=None, link_id=None):
task = self.get_object()
try:
link = self._employee_links_queryset(task).get(id=link_id)
except TaskLinkedObject.DoesNotExist:
return Response(
{"detail": "Связь не найдена."},
status=status.HTTP_404_NOT_FOUND,
)
employee_id = link.object_id
link.delete()
create_task_activity(
task,
request.user,
TaskActivityAction.UNLINKED,
object_kind=TaskLinkedObjectKind.EMPLOYEE,
object_id=employee_id,
metadata={"object_type": "Сотрудник"},
)
send_task_board_update(
task.board,
"unlinked",
"employee",
employee_id,
extra={"task_id": task.id, "link_id": int(link_id)},
)
return Response(status=status.HTTP_204_NO_CONTENT)
@action(detail=True, methods=["get", "post"], url_path="linked-guests")
def linked_guests(self, request, pk=None):
task = self.get_object()
if request.method == "GET":
serializer = TaskLinkedGuestSerializer(
self._guest_links_queryset(task),
many=True,
context={"request": request},
)
return Response(serializer.data)
guest_id = request.data.get("guest_id")
try:
guest_id = int(guest_id)
except (TypeError, ValueError):
return Response(
{"guest_id": ["Укажите ID гостя."]},
status=status.HTTP_400_BAD_REQUEST,
)
try:
guest = Guest.objects.get(id=guest_id)
except Guest.DoesNotExist:
return Response(
{"guest_id": ["Гость не найден."]},
status=status.HTTP_404_NOT_FOUND,
)
if not user_can_access_guest(request.user, guest):
return Response(
{"detail": "Нет доступа к этому гостю."},
status=status.HTTP_403_FORBIDDEN,
)
link, created = TaskLinkedObject.objects.get_or_create(
task=task,
kind=TaskLinkedObjectKind.GUEST,
content_type=get_guest_content_type(),
object_id=guest.id,
defaults={"created_by": request.user},
)
serializer = TaskLinkedGuestSerializer(
link,
context={"request": request},
)
if created:
create_task_activity(
task,
request.user,
TaskActivityAction.LINKED,
object_kind=TaskLinkedObjectKind.GUEST,
object_id=guest.id,
metadata={
"object_label": guest.full_name,
"object_type": "Гость",
},
)
send_task_board_update(
task.board,
"linked",
"guest",
guest.id,
extra={"task_id": task.id, "link_id": link.id},
)
return Response(
serializer.data,
status=status.HTTP_201_CREATED if created else status.HTTP_200_OK,
)
@action(
detail=True,
methods=["delete"],
url_path=r"linked-guests/(?P<link_id>\d+)",
)
def unlink_guest(self, request, pk=None, link_id=None):
task = self.get_object()
try:
link = self._guest_links_queryset(task).get(id=link_id)
except TaskLinkedObject.DoesNotExist:
return Response(
{"detail": "Связь не найдена."},
status=status.HTTP_404_NOT_FOUND,
)
guest_id = link.object_id
link.delete()
create_task_activity(
task,
request.user,
TaskActivityAction.UNLINKED,
object_kind=TaskLinkedObjectKind.GUEST,
object_id=guest_id,
metadata={"object_type": "Гость"},
)
send_task_board_update(
task.board,
"unlinked",
"guest",
guest_id,
extra={"task_id": task.id, "link_id": int(link_id)},
)
return Response(status=status.HTTP_204_NO_CONTENT)
@action(detail=True, methods=["get", "post"], url_path="linked-guest-visits")
def linked_guest_visits(self, request, pk=None):
task = self.get_object()
if request.method == "GET":
serializer = TaskLinkedGuestVisitSerializer(
self._guest_visit_links_queryset(task),
many=True,
context={"request": request},
)
return Response(serializer.data)
guest_visit_id = request.data.get("guest_visit_id")
try:
guest_visit_id = int(guest_visit_id)
except (TypeError, ValueError):
return Response(
{"guest_visit_id": ["Укажите ID заявки на гостевой визит."]},
status=status.HTTP_400_BAD_REQUEST,
)
try:
visit = GuestVisit.objects.select_related("guest", "inviter").get(
id=guest_visit_id,
)
except GuestVisit.DoesNotExist:
return Response(
{"guest_visit_id": ["Заявка на гостевой визит не найдена."]},
status=status.HTTP_404_NOT_FOUND,
)
if not user_can_access_guest_visit(request.user, visit):
return Response(
{"detail": "Нет доступа к этой заявке на гостевой визит."},
status=status.HTTP_403_FORBIDDEN,
)
link, created = TaskLinkedObject.objects.get_or_create(
task=task,
kind=TaskLinkedObjectKind.GUEST_VISIT,
content_type=get_guest_visit_content_type(),
object_id=visit.id,
defaults={"created_by": request.user},
)
serializer = TaskLinkedGuestVisitSerializer(
link,
context={"request": request},
)
if created:
create_task_activity(
task,
request.user,
TaskActivityAction.LINKED,
object_kind=TaskLinkedObjectKind.GUEST_VISIT,
object_id=visit.id,
metadata={
"object_label": visit.guest.full_name,
"object_type": "Заявка на гостевой визит",
},
)
send_task_board_update(
task.board,
"linked",
"guest_visit",
visit.id,
extra={"task_id": task.id, "link_id": link.id},
)
return Response(
serializer.data,
status=status.HTTP_201_CREATED if created else status.HTTP_200_OK,
)
@action(
detail=True,
methods=["delete"],
url_path=r"linked-guest-visits/(?P<link_id>\d+)",
)
def unlink_guest_visit(self, request, pk=None, link_id=None):
task = self.get_object()
try:
link = self._guest_visit_links_queryset(task).get(id=link_id)
except TaskLinkedObject.DoesNotExist:
return Response(
{"detail": "Связь не найдена."},
status=status.HTTP_404_NOT_FOUND,
)
guest_visit_id = link.object_id
link.delete()
create_task_activity(
task,
request.user,
TaskActivityAction.UNLINKED,
object_kind=TaskLinkedObjectKind.GUEST_VISIT,
object_id=guest_visit_id,
metadata={"object_type": "Заявка на гостевой визит"},
)
send_task_board_update(
task.board,
"unlinked",
"guest_visit",
guest_visit_id,
extra={"task_id": task.id, "link_id": int(link_id)},
)
return Response(status=status.HTTP_204_NO_CONTENT)
@action(detail=True, methods=["get", "post"], url_path="linked-attendance-records")
def linked_attendance_records(self, request, pk=None):
task = self.get_object()
if request.method == "GET":
serializer = TaskLinkedAttendanceRecordSerializer(
self._attendance_record_links_queryset(task),
many=True,
context={"request": request},
)
return Response(serializer.data)
attendance_record_id = request.data.get("attendance_record_id")
try:
attendance_record_id = int(attendance_record_id)
except (TypeError, ValueError):
return Response(
{"attendance_record_id": ["Укажите ID записи посещаемости."]},
status=status.HTTP_400_BAD_REQUEST,
)
try:
record = AttendanceRecord.objects.select_related("employee").get(
id=attendance_record_id,
)
except AttendanceRecord.DoesNotExist:
return Response(
{"attendance_record_id": ["Запись посещаемости не найдена."]},
status=status.HTTP_404_NOT_FOUND,
)
if not user_can_access_attendance_record(request.user, record):
return Response(
{"detail": "Нет доступа к этой записи посещаемости."},
status=status.HTTP_403_FORBIDDEN,
)
link, created = TaskLinkedObject.objects.get_or_create(
task=task,
kind=TaskLinkedObjectKind.ATTENDANCE_RECORD,
content_type=get_attendance_record_content_type(),
object_id=record.id,
defaults={"created_by": request.user},
)
serializer = TaskLinkedAttendanceRecordSerializer(
link,
context={"request": request},
)
if created:
employee_name = str(record.employee) if record.employee_id else ""
create_task_activity(
task,
request.user,
TaskActivityAction.LINKED,
object_kind=TaskLinkedObjectKind.ATTENDANCE_RECORD,
object_id=record.id,
metadata={
"object_label": f"{employee_name} {record.date}".strip(),
"object_type": "Запись посещаемости",
},
)
send_task_board_update(
task.board,
"linked",
"attendance_record",
record.id,
extra={"task_id": task.id, "link_id": link.id},
)
return Response(
serializer.data,
status=status.HTTP_201_CREATED if created else status.HTTP_200_OK,
)
@action(
detail=True,
methods=["delete"],
url_path=r"linked-attendance-records/(?P<link_id>\d+)",
)
def unlink_attendance_record(self, request, pk=None, link_id=None):
task = self.get_object()
try:
link = self._attendance_record_links_queryset(task).get(id=link_id)
except TaskLinkedObject.DoesNotExist:
return Response(
{"detail": "Связь не найдена."},
status=status.HTTP_404_NOT_FOUND,
)
attendance_record_id = link.object_id
link.delete()
create_task_activity(
task,
request.user,
TaskActivityAction.UNLINKED,
object_kind=TaskLinkedObjectKind.ATTENDANCE_RECORD,
object_id=attendance_record_id,
metadata={"object_type": "Запись посещаемости"},
)
send_task_board_update(
task.board,
"unlinked",
"attendance_record",
attendance_record_id,
extra={"task_id": task.id, "link_id": int(link_id)},
)
return Response(status=status.HTTP_204_NO_CONTENT)
@action(detail=True, methods=["get", "post"], url_path="linked-events")
def linked_events(self, request, pk=None):
task = self.get_object()
if request.method == "GET":
serializer = TaskLinkedCalendarEventSerializer(
self._event_links_queryset(task),
many=True,
context={"request": request},
)
return Response(serializer.data)
event_id = request.data.get("event_id")
try:
event_id = int(event_id)
except (TypeError, ValueError):
return Response(
{"event_id": ["Укажите ID события."]},
status=status.HTTP_400_BAD_REQUEST,
)
try:
event = Event.objects.select_related(
"calendar",
"calendar__binding",
"calendar__binding__context_content_type",
"rule",
"creator",
).get(id=event_id)
except Event.DoesNotExist:
return Response(
{"event_id": ["Событие не найдено."]},
status=status.HTTP_404_NOT_FOUND,
)
if not user_can_access_calendar_event(request.user, event):
return Response(
{"detail": "Нет доступа к этому событию."},
status=status.HTTP_403_FORBIDDEN,
)
link, created = TaskLinkedObject.objects.get_or_create(
task=task,
kind=TaskLinkedObjectKind.CALENDAR_EVENT,
content_type=get_calendar_event_content_type(),
object_id=event.id,
defaults={"created_by": request.user},
)
serializer = TaskLinkedCalendarEventSerializer(
link,
context={"request": request},
)
if created:
create_task_activity(
task,
request.user,
TaskActivityAction.LINKED,
object_kind=TaskLinkedObjectKind.CALENDAR_EVENT,
object_id=event.id,
metadata={
"object_label": event.title,
"object_type": "Календарное событие",
},
)
send_task_board_update(
task.board,
"linked",
"calendar_event",
event.id,
extra={"task_id": task.id, "link_id": link.id},
)
return Response(
serializer.data,
status=status.HTTP_201_CREATED if created else status.HTTP_200_OK,
)
@action(
detail=True,
methods=["delete"],
url_path=r"linked-events/(?P<link_id>\d+)",
)
def unlink_event(self, request, pk=None, link_id=None):
task = self.get_object()
try:
link = self._event_links_queryset(task).get(id=link_id)
except TaskLinkedObject.DoesNotExist:
return Response(
{"detail": "Связь не найдена."},
status=status.HTTP_404_NOT_FOUND,
)
event_id = link.object_id
link.delete()
create_task_activity(
task,
request.user,
TaskActivityAction.UNLINKED,
object_kind=TaskLinkedObjectKind.CALENDAR_EVENT,
object_id=event_id,
metadata={"object_type": "Календарное событие"},
)
send_task_board_update(
task.board,
"unlinked",
"calendar_event",
event_id,
extra={"task_id": task.id, "link_id": int(link_id)},
)
return Response(status=status.HTTP_204_NO_CONTENT)