feat(reports): refine exports and restore project access

This commit is contained in:
2026-05-14 17:06:35 +03:30
parent 77c07adec8
commit d4a52d6f3b
16 changed files with 1594 additions and 136 deletions

View File

@@ -40,3 +40,17 @@ class ProjectUpdateSerializer(serializers.Serializer):
description = serializers.CharField(required=False, allow_blank=True)
color = serializers.CharField(max_length=7, required=False, allow_blank=True)
is_archived = serializers.BooleanField(required=False)
class ProjectAccessQuerySerializer(serializers.Serializer):
workspace = serializers.UUIDField()
user = serializers.UUIDField()
class ProjectAccessMutationSerializer(serializers.Serializer):
workspace = serializers.UUIDField()
user = serializers.UUIDField()
project_ids = serializers.ListField(
child=serializers.UUIDField(),
allow_empty=False,
)

View File

@@ -15,8 +15,17 @@ from apps.clients.models import Client
from apps.projects.models import Project
from apps.projects.api.serializers import (
ProjectSerializer, ProjectCreateSerializer, ProjectUpdateSerializer,
ProjectAccessMutationSerializer, ProjectAccessQuerySerializer,
)
from apps.projects.api.permissions import IsProjectMember, IsProjectManager
from apps.projects.services.access import (
build_project_access_items,
ensure_workspace_project_access,
filter_projects_for_user,
get_access_managed_membership,
grant_project_accesses,
revoke_project_accesses,
)
from apps.projects.services.projects import (
create_project,
update_project,
@@ -67,11 +76,10 @@ class ProjectViewSet(ModelViewSet):
if getattr(self, "swagger_fake_view", False) or not self.request.user.is_authenticated:
return Project.objects.none()
queryset = Project.objects.filter(
workspace__memberships__user=self.request.user,
workspace__memberships__is_active=True,
is_deleted=False
).distinct()
queryset = filter_projects_for_user(
self.request.user,
Project.objects.filter(is_deleted=False),
)
client_ids = [client_id for client_id in self.request.query_params.getlist("clients") if client_id]
if client_ids:
@@ -150,12 +158,76 @@ class ProjectViewSet(ModelViewSet):
return Response(status=status.HTTP_204_NO_CONTENT)
@action(detail=True, methods=["post"])
def archive(self, request, pk=None):
"""
Custom endpoint to toggle the archive status of a project.
"""
project = self.get_object()
def archive(self, request, pk=None):
"""
Custom endpoint to toggle the archive status of a project.
"""
project = self.get_object()
updated_project = toggle_project_archive(project)
output_serializer = ProjectSerializer(updated_project)
return Response(output_serializer.data, status=status.HTTP_200_OK)
@action(detail=False, methods=["get"], url_path="access")
def access(self, request):
serializer = ProjectAccessQuerySerializer(data=request.query_params)
serializer.is_valid(raise_exception=True)
workspace = get_object_or_404(
Workspace,
id=serializer.validated_data["workspace"],
is_deleted=False,
)
ensure_workspace_project_access(request.user, workspace)
membership = get_access_managed_membership(workspace, str(serializer.validated_data["user"]))
return Response(
{
"workspace": {"id": str(workspace.id), "name": workspace.name},
"user": {
"id": str(membership.user_id),
"name": membership.user.full_name or membership.user.mobile,
"mobile": membership.user.mobile,
"role": membership.role,
},
"items": build_project_access_items(workspace=workspace, target_user=membership.user),
}
)
@action(detail=False, methods=["post"], url_path="access/grant")
def grant_access(self, request):
serializer = ProjectAccessMutationSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
workspace = get_object_or_404(
Workspace,
id=serializer.validated_data["workspace"],
is_deleted=False,
)
membership = get_access_managed_membership(workspace, str(serializer.validated_data["user"]))
changed = grant_project_accesses(
actor=request.user,
workspace=workspace,
target_user=membership.user,
project_ids=[str(project_id) for project_id in serializer.validated_data["project_ids"]],
)
return Response({"changed": changed}, status=status.HTTP_200_OK)
@action(detail=False, methods=["post"], url_path="access/revoke")
def revoke_access(self, request):
serializer = ProjectAccessMutationSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
workspace = get_object_or_404(
Workspace,
id=serializer.validated_data["workspace"],
is_deleted=False,
)
membership = get_access_managed_membership(workspace, str(serializer.validated_data["user"]))
changed = revoke_project_accesses(
actor=request.user,
workspace=workspace,
target_user=membership.user,
project_ids=[str(project_id) for project_id in serializer.validated_data["project_ids"]],
)
return Response({"changed": changed}, status=status.HTTP_200_OK)