From ec199a0e99cc8bf58a61b78684959f8c36011569 Mon Sep 17 00:00:00 2001 From: Amirhossein Khalili Date: Wed, 29 Apr 2026 00:53:54 +0330 Subject: [PATCH] feat(projects): add client strip filtering and page refresh --- apps/projects/api/views.py | 8 +++- apps/projects/tests/__init__.py | 1 + apps/projects/tests/test_views.py | 75 +++++++++++++++++++++++++++++++ 3 files changed, 83 insertions(+), 1 deletion(-) create mode 100644 apps/projects/tests/__init__.py create mode 100644 apps/projects/tests/test_views.py diff --git a/apps/projects/api/views.py b/apps/projects/api/views.py index 56a1189..8bbd839 100644 --- a/apps/projects/api/views.py +++ b/apps/projects/api/views.py @@ -67,11 +67,17 @@ class ProjectViewSet(ModelViewSet): if getattr(self, "swagger_fake_view", False) or not self.request.user.is_authenticated: return Project.objects.none() - return Project.objects.filter( + queryset = Project.objects.filter( workspace__memberships__user=self.request.user, workspace__memberships__is_active=True, is_deleted=False ).distinct() + + client_ids = [client_id for client_id in self.request.query_params.getlist("clients") if client_id] + if client_ids: + queryset = queryset.filter(client_id__in=client_ids) + + return queryset def get_serializer_class(self): """ diff --git a/apps/projects/tests/__init__.py b/apps/projects/tests/__init__.py new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/apps/projects/tests/__init__.py @@ -0,0 +1 @@ + diff --git a/apps/projects/tests/test_views.py b/apps/projects/tests/test_views.py new file mode 100644 index 0000000..ac80b6d --- /dev/null +++ b/apps/projects/tests/test_views.py @@ -0,0 +1,75 @@ +import pytest +from rest_framework.test import APIClient + +from apps.clients.models import Client +from apps.projects.models import Project +from apps.users.models import User +from apps.workspaces.models import Workspace, WorkspaceMembership + + +@pytest.fixture() +def api_client(): + return APIClient() + + +@pytest.fixture() +def owner(db): + return User.objects.create_user(mobile="09121110001", password="secret123", first_name="Owner") + + +@pytest.fixture() +def workspace(owner): + return Workspace.objects.create(name="Projects", owner=owner) + + +@pytest.fixture() +def member(db, workspace): + user = User.objects.create_user(mobile="09121110002", password="secret123", first_name="Member") + WorkspaceMembership.objects.create( + workspace=workspace, + user=user, + role=WorkspaceMembership.Role.MEMBER, + is_active=True, + ) + return user + + +@pytest.fixture() +def clients(workspace): + first = Client.objects.create(workspace=workspace, name="Acme") + second = Client.objects.create(workspace=workspace, name="Globex") + third = Client.objects.create(workspace=workspace, name="Initech") + return first, second, third + + +@pytest.fixture() +def projects(workspace, clients): + first, second, third = clients + return [ + Project.objects.create(workspace=workspace, client=first, name="Alpha"), + Project.objects.create(workspace=workspace, client=second, name="Beta"), + Project.objects.create(workspace=workspace, client=third, name="Gamma"), + ] + + +def test_project_list_supports_multi_client_filter(api_client, member, workspace, clients, projects): + api_client.force_authenticate(user=member) + first, second, _ = clients + + response = api_client.get( + "/api/projects/", + [ + ("workspace", str(workspace.id)), + ("clients", str(first.id)), + ("clients", str(second.id)), + ], + ) + + assert response.status_code == 200 + items = ( + response.data + if isinstance(response.data, list) + else response.data.get("results") or response.data.get("items", []) + ) + result_ids = {str(item["client"]["id"]) for item in items} + assert result_ids == {str(first.id), str(second.id)}