From f089fdc20ba0f77f645e61b6048e5235f38d0bb2 Mon Sep 17 00:00:00 2001 From: Toksi Date: Fri, 16 Jan 2026 13:04:50 +0500 Subject: [PATCH] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D0=B0=20=D1=80=D1=83=D1=87=D0=BA=D0=B0=20=D0=B4=D0=BB?= =?UTF-8?q?=D1=8F=20=D0=BF=D0=BE=D0=BB=D1=83=D1=87=D0=B5=D0=BD=D0=B8=D1=8F?= =?UTF-8?q?=20=D0=BF=D1=80=D0=BE=D0=B5=D0=BA=D1=82=D0=BE=D0=B2=20=D0=BF?= =?UTF-8?q?=D0=BE=D0=BB=D1=8C=D0=B7=D0=BE=D0=B2=D0=B0=D1=82=D0=B5=D0=BB?= =?UTF-8?q?=D1=8F,=20=D0=B3=D0=B4=D0=B5=20=D0=BE=D0=BD=20=D1=8F=D0=B2?= =?UTF-8?q?=D0=BB=D1=8F=D0=B5=D1=82=D1=81=D1=8F=20=D0=BB=D0=B8=D0=B4=D0=B5?= =?UTF-8?q?=D1=80=D0=BE=D0=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- invites/tests.py | 51 ++++++++++++++++++++++++++++++++++++++++++++++- projects/admin.py | 4 +++- users/tests.py | 37 +++++++++++++++++++++++++++++++++- users/urls.py | 2 ++ users/views.py | 22 ++++++++++++++++++++ 5 files changed, 113 insertions(+), 3 deletions(-) diff --git a/invites/tests.py b/invites/tests.py index afbef07d..5f4b3d21 100644 --- a/invites/tests.py +++ b/invites/tests.py @@ -1,7 +1,11 @@ +from datetime import timedelta + from django.test import TestCase +from django.utils import timezone from rest_framework.test import APIRequestFactory, force_authenticate -from projects.models import Project +from partner_programs.models import PartnerProgram, PartnerProgramProject +from projects.models import Collaborator, Project from tests.constants import USER_CREATE_DATA from users.views import UserList @@ -112,6 +116,51 @@ def test_invites_creation_with_empty_data(self): self.assertEqual(response.status_code, 400) + def test_invites_creation_for_existing_collaborator(self): + sender = self._user_create("sender@example.com") + recipient = self._user_create("recipient@example.com") + project = self._project_create(sender) + Collaborator.objects.create(user=recipient, project=project, role="Developer") + + create_user = self.invite_create_data.copy() + create_user["user"] = recipient.id + create_user["project"] = project.id + request = self.factory.post("invites/", create_user, format="json") + force_authenticate(request, user=sender) + + response = self.invite_list_view(request) + + self.assertEqual(response.status_code, 400) + self.assertIn("user", response.data) + + def test_invites_creation_for_non_program_member(self): + sender = self._user_create("sender@example.com") + recipient = self._user_create("recipient@example.com") + project = self._project_create(sender) + now = timezone.now() + program = PartnerProgram.objects.create( + name="Test program", + tag="test", + city="Moscow", + datetime_registration_ends=now + timedelta(days=1), + datetime_started=now, + datetime_finished=now + timedelta(days=30), + ) + PartnerProgramProject.objects.create( + partner_program=program, project=project + ) + + create_user = self.invite_create_data.copy() + create_user["user"] = recipient.id + create_user["project"] = project.id + request = self.factory.post("invites/", create_user, format="json") + force_authenticate(request, user=sender) + + response = self.invite_list_view(request) + + self.assertEqual(response.status_code, 400) + self.assertIn("user", response.data) + def test_accept_invite_by_intended_user(self): sender = self._user_create("sender@example.com") recipient = self._user_create("recipient@example.com") diff --git a/projects/admin.py b/projects/admin.py index 6f5e8e75..060be9ee 100644 --- a/projects/admin.py +++ b/projects/admin.py @@ -54,6 +54,7 @@ class ProjectAdmin(admin.ModelAdmin): "id", "name", "draft", + "is_public", "is_company", "trl", "target_audience", @@ -61,7 +62,7 @@ class ProjectAdmin(admin.ModelAdmin): ) list_display_links = ("id", "name") search_fields = ("name",) - list_filter = ("draft", "is_company", "trl") + list_filter = ("draft", "is_public", "is_company", "trl") fieldsets = ( ( @@ -74,6 +75,7 @@ class ProjectAdmin(admin.ModelAdmin): "industry", "region", "draft", + "is_public", "is_company", ) }, diff --git a/users/tests.py b/users/tests.py index 409fd63c..3fac35b5 100644 --- a/users/tests.py +++ b/users/tests.py @@ -2,8 +2,9 @@ from rest_framework.test import APIRequestFactory, force_authenticate from tests.constants import USER_CREATE_DATA +from projects.models import Collaborator, Project from users.models import CustomUser -from users.views import UserList, UserDetail +from users.views import UserLeaderProjectsList, UserList, UserDetail class UserTestCase(TestCase): @@ -11,6 +12,7 @@ def setUp(self): self.factory = APIRequestFactory() self.user_list_view = UserList.as_view() self.user_detail_view = UserDetail.as_view() + self.user_leader_projects_view = UserLeaderProjectsList.as_view() def test_user_creation(self): request = self.factory.post("auth/users/", USER_CREATE_DATA) @@ -58,3 +60,36 @@ def test_user_update(self): response = self.user_detail_view(request, pk=user.pk) self.assertEqual(response.status_code, 200) self.assertEqual(response.data["first_name"], "Сергей") + + def test_user_leader_projects_list(self): + leader = self._user_create("leader@example.com") + collaborator = self._user_create("collaborator@example.com") + + leader_project = Project.objects.create(name="Leader project", leader=leader) + second_leader_project = Project.objects.create( + name="Leader project 2", leader=leader, draft=False + ) + other_project = Project.objects.create(name="Other project", leader=collaborator) + Collaborator.objects.create(user=leader, project=other_project, role="Member") + + request = self.factory.get("users/projects/leader/") + force_authenticate(request, user=leader) + response = self.user_leader_projects_view(request) + + self.assertEqual(response.status_code, 200) + self.assertEqual(response.data["count"], 2) + returned_ids = {item["id"] for item in response.data["results"]} + self.assertSetEqual( + returned_ids, {leader_project.id, second_leader_project.id} + ) + + def _user_create(self, email): + tmp_create_data = USER_CREATE_DATA.copy() + tmp_create_data["email"] = email + request = self.factory.post("auth/users/", tmp_create_data) + response = self.user_list_view(request) + user_id = response.data["id"] + user = CustomUser.objects.get(id=user_id) + user.is_active = True + user.save() + return user diff --git a/users/urls.py b/users/urls.py index cf799bef..907a9c15 100644 --- a/users/urls.py +++ b/users/urls.py @@ -9,6 +9,7 @@ SpecialistsList, UserAdditionalRolesView, UserDetail, + UserLeaderProjectsList, UserProjectsList, UserList, UserTypesView, @@ -41,6 +42,7 @@ path("users/", UserList.as_view()), path('public-users/', PublicUserListView.as_view(), name='public-users'), path("users/projects/", UserProjectsList.as_view()), + path("users/projects/leader/", UserLeaderProjectsList.as_view()), path("users/liked/", LikedProjectList.as_view()), path("users/roles/", UserAdditionalRolesView.as_view()), path("users/types/", UserTypesView.as_view()), diff --git a/users/views.py b/users/views.py index e9223709..4a59332f 100644 --- a/users/views.py +++ b/users/views.py @@ -426,6 +426,28 @@ def get(self, request): ) +class UserLeaderProjectsList(GenericAPIView): + permission_classes = [IsAuthenticated] + pagination_class = ProjectsPagination + serializer_class = UserProjectListSerializer + + def get(self, request): + queryset = ( + Project.objects.filter(leader_id=self.request.user.id) + .prefetch_related("program_links__partner_program") + .distinct() + ) + + page = self.paginate_queryset(queryset) + if page is not None: + serializer = self.get_serializer(page, many=True) + return self.get_paginated_response(serializer.data) + return Response( + {"detail": "Unable to return paginated list"}, + status=status.HTTP_400_BAD_REQUEST, + ) + + class LogoutView(APIView): permission_classes = [IsAuthenticated]