From d710272c181bb07bb2a84dcc0af7bb1fa7e94f01 Mon Sep 17 00:00:00 2001 From: pablohashescobar Date: Fri, 13 Jan 2023 21:24:29 +0530 Subject: [PATCH 01/23] refactor: setup multiple select related --- apiserver/plane/api/views/cycle.py | 1 + apiserver/plane/api/views/issue.py | 103 ++++++++++++++++++++++--- apiserver/plane/api/views/module.py | 10 ++- apiserver/plane/api/views/project.py | 15 +++- apiserver/plane/api/views/workspace.py | 8 +- 5 files changed, 117 insertions(+), 20 deletions(-) diff --git a/apiserver/plane/api/views/cycle.py b/apiserver/plane/api/views/cycle.py index 62c0376b3e0..30d2bb3ac2e 100644 --- a/apiserver/plane/api/views/cycle.py +++ b/apiserver/plane/api/views/cycle.py @@ -32,6 +32,7 @@ def get_queryset(self): .filter(project__project_projectmember__member=self.request.user) .select_related("project") .select_related("workspace") + .select_related("owned_by") .distinct() ) diff --git a/apiserver/plane/api/views/issue.py b/apiserver/plane/api/views/issue.py index 78f050af80b..8dedb53c1b2 100644 --- a/apiserver/plane/api/views/issue.py +++ b/apiserver/plane/api/views/issue.py @@ -100,7 +100,7 @@ def get_queryset(self): .filter(project_id=self.kwargs.get("project_id")) .filter(workspace__slug=self.kwargs.get("slug")) .select_related("project") - .select_related("workspace") + .select_related("workspace", "workspace__owner") .select_related("state") .select_related("parent") .prefetch_related("assignees") @@ -207,9 +207,44 @@ def create(self, request, slug, project_id): class UserWorkSpaceIssues(BaseAPIView): def get(self, request, slug): try: - issues = Issue.objects.filter( - assignees__in=[request.user], workspace__slug=slug + issues = ( + Issue.objects.filter(assignees__in=[request.user], workspace__slug=slug) + .select_related("project") + .select_related("workspace", "workspace__owner") + .select_related("state") + .select_related("parent") + .prefetch_related("assignees") + .prefetch_related("labels") + .prefetch_related( + Prefetch( + "blocked_issues", + queryset=IssueBlocker.objects.select_related( + "blocked_by", "block" + ), + ) + ) + .prefetch_related( + Prefetch( + "blocker_issues", + queryset=IssueBlocker.objects.select_related( + "block", "blocked_by" + ), + ) + ) + .prefetch_related( + Prefetch( + "issue_cycle", + queryset=CycleIssue.objects.select_related("cycle", "issue"), + ), + ) + .prefetch_related( + Prefetch( + "issue_module", + queryset=ModuleIssue.objects.select_related("module", "issue"), + ), + ) ) + serializer = IssueSerializer(issues, many=True) return Response(serializer.data, status=status.HTTP_200_OK) except Exception as e: @@ -228,9 +263,45 @@ class WorkSpaceIssuesEndpoint(BaseAPIView): def get(self, request, slug): try: - issues = Issue.objects.filter(workspace__slug=slug).filter( - project__project_projectmember__member=self.request.user + issues = ( + Issue.objects.filter(workspace__slug=slug) + .filter(project__project_projectmember__member=self.request.user) + .select_related("project") + .select_related("workspace", "workspace__owner") + .select_related("state") + .select_related("parent") + .prefetch_related("assignees") + .prefetch_related("labels") + .prefetch_related( + Prefetch( + "blocked_issues", + queryset=IssueBlocker.objects.select_related( + "blocked_by", "block" + ), + ) + ) + .prefetch_related( + Prefetch( + "blocker_issues", + queryset=IssueBlocker.objects.select_related( + "block", "blocked_by" + ), + ) + ) + .prefetch_related( + Prefetch( + "issue_cycle", + queryset=CycleIssue.objects.select_related("cycle", "issue"), + ), + ) + .prefetch_related( + Prefetch( + "issue_module", + queryset=ModuleIssue.objects.select_related("module", "issue"), + ), + ) ) + serializer = IssueSerializer(issues, many=True) return Response(serializer.data, status=status.HTTP_200_OK) except Exception as e: @@ -249,16 +320,29 @@ class IssueActivityEndpoint(BaseAPIView): def get(self, request, slug, project_id, issue_id): try: + # Activities issue_activities = ( - IssueActivity.objects.filter(issue_id=issue_id) + IssueActivity.objects.filter( + issue_id=issue_id, workspace__slug=slug, project_id=project_id + ) .filter(project__project_projectmember__member=self.request.user) .select_related("actor") ).order_by("created_by") + + # Comments issue_comments = ( - IssueComment.objects.filter(issue_id=issue_id) - .filter(project__project_projectmember__member=self.request.user) - .order_by("created_at") + ( + IssueComment.objects.filter( + issue_id=issue_id, workspace__slug=slug, project_id=project_id + ) + .filter(project__project_projectmember__member=self.request.user) + .order_by("created_at") + ) + .select_related("actor") + .select_related("issue") + .select_related("project") ) + issue_activities = IssueActivitySerializer(issue_activities, many=True).data issue_comments = IssueCommentSerializer(issue_comments, many=True).data @@ -307,6 +391,7 @@ def get_queryset(self): .select_related("project") .select_related("workspace") .select_related("issue") + .select_related("actor") .distinct() ) diff --git a/apiserver/plane/api/views/module.py b/apiserver/plane/api/views/module.py index a1aff67a0c7..bf1f5f833c7 100644 --- a/apiserver/plane/api/views/module.py +++ b/apiserver/plane/api/views/module.py @@ -45,13 +45,15 @@ def get_queryset(self): .prefetch_related( Prefetch( "issue_module", - queryset=ModuleIssue.objects.select_related("module", "issue"), + queryset=ModuleIssue.objects.select_related( + "module", "issue", "issue__state" + ), ) ) .prefetch_related( Prefetch( "link_module", - queryset=ModuleLink.objects.select_related("module"), + queryset=ModuleLink.objects.select_related("module", "created_by"), ) ) ) @@ -117,7 +119,7 @@ def get_queryset(self): .select_related("project") .select_related("workspace") .select_related("module") - .select_related("issue") + .select_related("issue", "issue__state") .distinct() ) @@ -164,4 +166,4 @@ def create(self, request, slug, project_id, module_id): return Response( {"error": "Something went wrong please try again later"}, status=status.HTTP_400_BAD_REQUEST, - ) \ No newline at end of file + ) diff --git a/apiserver/plane/api/views/project.py b/apiserver/plane/api/views/project.py index a3113a10a4a..2ec6faf1e53 100644 --- a/apiserver/plane/api/views/project.py +++ b/apiserver/plane/api/views/project.py @@ -67,7 +67,9 @@ def get_queryset(self): .get_queryset() .filter(workspace__slug=self.kwargs.get("slug")) .filter(Q(project_projectmember__member=self.request.user) | Q(network=2)) - .select_related("workspace", "workspace__owner") + .select_related( + "workspace", "workspace__owner", "default_assignee", "project_lead" + ) .distinct() ) @@ -294,7 +296,7 @@ def get_queryset(self): super() .get_queryset() .filter(email=self.request.user.email) - .select_related("workspace") + .select_related("workspace", "workspace__owner", "project") ) def create(self, request): @@ -349,6 +351,7 @@ def get_queryset(self): .filter(project_id=self.kwargs.get("project_id")) .select_related("project") .select_related("member") + .select_related("workspace", "workspace__owner") ) @@ -481,6 +484,7 @@ def get_queryset(self): .filter(workspace__slug=self.kwargs.get("slug")) .filter(project_id=self.kwargs.get("project_id")) .select_related("project") + .select_related("workspace", "workspace__owner") ) @@ -496,7 +500,12 @@ class ProjectMemberInviteDetailViewSet(BaseViewSet): ] def get_queryset(self): - return self.filter_queryset(super().get_queryset().select_related("project")) + return self.filter_queryset( + super() + .get_queryset() + .select_related("project") + .select_related("workspace", "workspace__owner") + ) class ProjectIdentifierEndpoint(BaseAPIView): diff --git a/apiserver/plane/api/views/workspace.py b/apiserver/plane/api/views/workspace.py index 53f0159c47f..e03a80e821c 100644 --- a/apiserver/plane/api/views/workspace.py +++ b/apiserver/plane/api/views/workspace.py @@ -176,7 +176,7 @@ def post(self, request, slug): workspace_members = WorkspaceMember.objects.filter( workspace_id=workspace.id, member__email__in=[email.get("email") for email in emails], - ) + ).select_related("member", "worspace", "workspace__owner") if len(workspace_members): return Response( @@ -339,7 +339,7 @@ def get_queryset(self): super() .get_queryset() .filter(workspace__slug=self.kwargs.get("slug")) - .select_related("workspace") + .select_related("workspace", "workspace__owner") ) @@ -353,7 +353,7 @@ def get_queryset(self): super() .get_queryset() .filter(email=self.request.user.email) - .select_related("workspace") + .select_related("workspace", "workspace__owner") ) def create(self, request): @@ -524,7 +524,7 @@ def get(self, request): project_member = ProjectMember.objects.filter( workspace_id=last_workspace_id, member=request.user - ).select_related("workspace", "project", "member") + ).select_related("workspace", "project", "member", "workspace__owner") project_member_serializer = ProjectMemberSerializer( project_member, many=True From 70e78af4d33633296f1852aadfd697a2b6c4a447 Mon Sep 17 00:00:00 2001 From: pablohashescobar Date: Fri, 13 Jan 2023 21:53:58 +0530 Subject: [PATCH 02/23] chore: upgrade sentry sdk to latest version --- apiserver/requirements/base.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apiserver/requirements/base.txt b/apiserver/requirements/base.txt index 407c8b8c8c0..7887fa456b7 100644 --- a/apiserver/requirements/base.txt +++ b/apiserver/requirements/base.txt @@ -16,7 +16,7 @@ faker==13.4.0 django-filter==21.1 jsonmodels==2.5.0 djangorestframework-simplejwt==5.1.0 -sentry-sdk==1.5.12 +sentry-sdk==1.13.0 django-s3-storage==0.13.6 django-crum==0.7.9 django-guardian==2.4.0 From e5e4c5eb0fa97f86e5e974d98c7b729ec4bde2fe Mon Sep 17 00:00:00 2001 From: pablohashescobar Date: Fri, 13 Jan 2023 21:54:23 +0530 Subject: [PATCH 03/23] refactor: update module and cycle views to increase performance --- apiserver/plane/api/views/cycle.py | 2 +- apiserver/plane/api/views/module.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/apiserver/plane/api/views/cycle.py b/apiserver/plane/api/views/cycle.py index 30d2bb3ac2e..56b6ee360e5 100644 --- a/apiserver/plane/api/views/cycle.py +++ b/apiserver/plane/api/views/cycle.py @@ -64,7 +64,7 @@ def get_queryset(self): .select_related("workspace") .select_related("cycle") .select_related("issue") - .select_related("issue__state") + .select_related("issue__state", "issue__project") .distinct() ) diff --git a/apiserver/plane/api/views/module.py b/apiserver/plane/api/views/module.py index bf1f5f833c7..d19c586b3c5 100644 --- a/apiserver/plane/api/views/module.py +++ b/apiserver/plane/api/views/module.py @@ -46,7 +46,7 @@ def get_queryset(self): Prefetch( "issue_module", queryset=ModuleIssue.objects.select_related( - "module", "issue", "issue__state" + "module", "issue", "issue__state", "issue__project" ), ) ) @@ -119,7 +119,7 @@ def get_queryset(self): .select_related("project") .select_related("workspace") .select_related("module") - .select_related("issue", "issue__state") + .select_related("issue", "issue__state", "issue__project") .distinct() ) From f73cb098a6b96f59ea47347bbbf886944d108e8e Mon Sep 17 00:00:00 2001 From: pablohashescobar Date: Sat, 14 Jan 2023 10:32:01 +0530 Subject: [PATCH 04/23] refactor: remove pagination and make the response simillar to paginated API --- apiserver/plane/api/views/issue.py | 119 +++++------------------------ 1 file changed, 21 insertions(+), 98 deletions(-) diff --git a/apiserver/plane/api/views/issue.py b/apiserver/plane/api/views/issue.py index 8dedb53c1b2..ceae7ba227c 100644 --- a/apiserver/plane/api/views/issue.py +++ b/apiserver/plane/api/views/issue.py @@ -100,7 +100,7 @@ def get_queryset(self): .filter(project_id=self.kwargs.get("project_id")) .filter(workspace__slug=self.kwargs.get("slug")) .select_related("project") - .select_related("workspace", "workspace__owner") + .select_related("workspace") .select_related("state") .select_related("parent") .prefetch_related("assignees") @@ -162,10 +162,18 @@ def list(self, request, slug, project_id): return Response(issue_dict, status=status.HTTP_200_OK) - return self.paginate( - request=request, - queryset=issue_queryset, - on_results=lambda issues: IssueSerializer(issues, many=True).data, + return Response( + { + "next_cursor": str(0), + "prev_cursor": str(0), + "next_page_results": False, + "prev_page_results": False, + "count": issue_queryset.count(), + "total_pages": 1, + "extra_stats": {}, + "results": IssueSerializer(issue_queryset, many=True).data, + }, + status=status.HTTP_200_OK, ) except Exception as e: @@ -207,44 +215,9 @@ def create(self, request, slug, project_id): class UserWorkSpaceIssues(BaseAPIView): def get(self, request, slug): try: - issues = ( - Issue.objects.filter(assignees__in=[request.user], workspace__slug=slug) - .select_related("project") - .select_related("workspace", "workspace__owner") - .select_related("state") - .select_related("parent") - .prefetch_related("assignees") - .prefetch_related("labels") - .prefetch_related( - Prefetch( - "blocked_issues", - queryset=IssueBlocker.objects.select_related( - "blocked_by", "block" - ), - ) - ) - .prefetch_related( - Prefetch( - "blocker_issues", - queryset=IssueBlocker.objects.select_related( - "block", "blocked_by" - ), - ) - ) - .prefetch_related( - Prefetch( - "issue_cycle", - queryset=CycleIssue.objects.select_related("cycle", "issue"), - ), - ) - .prefetch_related( - Prefetch( - "issue_module", - queryset=ModuleIssue.objects.select_related("module", "issue"), - ), - ) + issues = Issue.objects.filter( + assignees__in=[request.user], workspace__slug=slug ) - serializer = IssueSerializer(issues, many=True) return Response(serializer.data, status=status.HTTP_200_OK) except Exception as e: @@ -263,45 +236,9 @@ class WorkSpaceIssuesEndpoint(BaseAPIView): def get(self, request, slug): try: - issues = ( - Issue.objects.filter(workspace__slug=slug) - .filter(project__project_projectmember__member=self.request.user) - .select_related("project") - .select_related("workspace", "workspace__owner") - .select_related("state") - .select_related("parent") - .prefetch_related("assignees") - .prefetch_related("labels") - .prefetch_related( - Prefetch( - "blocked_issues", - queryset=IssueBlocker.objects.select_related( - "blocked_by", "block" - ), - ) - ) - .prefetch_related( - Prefetch( - "blocker_issues", - queryset=IssueBlocker.objects.select_related( - "block", "blocked_by" - ), - ) - ) - .prefetch_related( - Prefetch( - "issue_cycle", - queryset=CycleIssue.objects.select_related("cycle", "issue"), - ), - ) - .prefetch_related( - Prefetch( - "issue_module", - queryset=ModuleIssue.objects.select_related("module", "issue"), - ), - ) + issues = Issue.objects.filter(workspace__slug=slug).filter( + project__project_projectmember__member=self.request.user ) - serializer = IssueSerializer(issues, many=True) return Response(serializer.data, status=status.HTTP_200_OK) except Exception as e: @@ -320,29 +257,16 @@ class IssueActivityEndpoint(BaseAPIView): def get(self, request, slug, project_id, issue_id): try: - # Activities issue_activities = ( - IssueActivity.objects.filter( - issue_id=issue_id, workspace__slug=slug, project_id=project_id - ) + IssueActivity.objects.filter(issue_id=issue_id) .filter(project__project_projectmember__member=self.request.user) .select_related("actor") ).order_by("created_by") - - # Comments issue_comments = ( - ( - IssueComment.objects.filter( - issue_id=issue_id, workspace__slug=slug, project_id=project_id - ) - .filter(project__project_projectmember__member=self.request.user) - .order_by("created_at") - ) - .select_related("actor") - .select_related("issue") - .select_related("project") + IssueComment.objects.filter(issue_id=issue_id) + .filter(project__project_projectmember__member=self.request.user) + .order_by("created_at") ) - issue_activities = IssueActivitySerializer(issue_activities, many=True).data issue_comments = IssueCommentSerializer(issue_comments, many=True).data @@ -391,7 +315,6 @@ def get_queryset(self): .select_related("project") .select_related("workspace") .select_related("issue") - .select_related("actor") .distinct() ) From 2095e4bb3edb7f35aad7a5957db9eeb4b900d090 Mon Sep 17 00:00:00 2001 From: pablohashescobar Date: Sat, 14 Jan 2023 10:32:34 +0530 Subject: [PATCH 05/23] fix: update staging to DEBUG True for all logging --- apiserver/plane/settings/staging.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apiserver/plane/settings/staging.py b/apiserver/plane/settings/staging.py index 2804d4e2917..429530d947e 100644 --- a/apiserver/plane/settings/staging.py +++ b/apiserver/plane/settings/staging.py @@ -14,7 +14,7 @@ from .common import * # noqa # Database -DEBUG = False +DEBUG = True DATABASES = { "default": { "ENGINE": "django.db.backends.postgresql_psycopg2", From 95ef68ccd97c3b3da9d005a8451552c09f9ce5cc Mon Sep 17 00:00:00 2001 From: pablohashescobar Date: Sat, 14 Jan 2023 10:36:57 +0530 Subject: [PATCH 06/23] refactor: update the query count print statement --- apiserver/plane/api/views/base.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/apiserver/plane/api/views/base.py b/apiserver/plane/api/views/base.py index 5d8e75fb3c2..d4b18cefc5e 100644 --- a/apiserver/plane/api/views/base.py +++ b/apiserver/plane/api/views/base.py @@ -1,6 +1,7 @@ # Django imports from django.urls import resolve from django.conf import settings + # Third part imports from rest_framework import status from rest_framework.viewsets import ModelViewSet @@ -39,16 +40,15 @@ def get_queryset(self): return self.model.objects.all() except Exception as e: print(e) - raise APIException( - "Please check the view", status.HTTP_400_BAD_REQUEST - ) + raise APIException("Please check the view", status.HTTP_400_BAD_REQUEST) def dispatch(self, request, *args, **kwargs): response = super().dispatch(request, *args, **kwargs) if settings.DEBUG: from django.db import connection - print(f'# of Queries: {len(connection.queries)}') + + print(f"{request.get_full_path()} of Queries: {len(connection.queries)}") return response @property @@ -110,7 +110,8 @@ def dispatch(self, request, *args, **kwargs): if settings.DEBUG: from django.db import connection - print(f'# of Queries: {len(connection.queries)}') + + print(f"{request.get_full_path()} of Queries: {len(connection.queries)}") return response @property From 8b9f084858039fa01e36cbb2a52e471c49faf499 Mon Sep 17 00:00:00 2001 From: pablohashescobar Date: Sat, 14 Jan 2023 10:42:16 +0530 Subject: [PATCH 07/23] refactor: my issues endpoint to remove n+1 --- apiserver/plane/api/views/issue.py | 38 ++++++++++++++++++++++++++++-- 1 file changed, 36 insertions(+), 2 deletions(-) diff --git a/apiserver/plane/api/views/issue.py b/apiserver/plane/api/views/issue.py index ceae7ba227c..79743c0066f 100644 --- a/apiserver/plane/api/views/issue.py +++ b/apiserver/plane/api/views/issue.py @@ -215,8 +215,42 @@ def create(self, request, slug, project_id): class UserWorkSpaceIssues(BaseAPIView): def get(self, request, slug): try: - issues = Issue.objects.filter( - assignees__in=[request.user], workspace__slug=slug + issues = ( + Issue.objects.filter(assignees__in=[request.user], workspace__slug=slug) + .select_related("project") + .select_related("workspace") + .select_related("state") + .select_related("parent") + .prefetch_related("assignees") + .prefetch_related("labels") + .prefetch_related( + Prefetch( + "blocked_issues", + queryset=IssueBlocker.objects.select_related( + "blocked_by", "block" + ), + ) + ) + .prefetch_related( + Prefetch( + "blocker_issues", + queryset=IssueBlocker.objects.select_related( + "block", "blocked_by" + ), + ) + ) + .prefetch_related( + Prefetch( + "issue_cycle", + queryset=CycleIssue.objects.select_related("cycle", "issue"), + ), + ) + .prefetch_related( + Prefetch( + "issue_module", + queryset=ModuleIssue.objects.select_related("module", "issue"), + ), + ) ) serializer = IssueSerializer(issues, many=True) return Response(serializer.data, status=status.HTTP_200_OK) From dffce74708965a811c9820b0945c06571d5bd4f9 Mon Sep 17 00:00:00 2001 From: pablohashescobar Date: Sat, 14 Jan 2023 11:48:53 +0530 Subject: [PATCH 08/23] refactor: optimize queries for workspace and project --- apiserver/plane/api/views/project.py | 2 +- apiserver/plane/api/views/workspace.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apiserver/plane/api/views/project.py b/apiserver/plane/api/views/project.py index 2ec6faf1e53..889622c4da9 100644 --- a/apiserver/plane/api/views/project.py +++ b/apiserver/plane/api/views/project.py @@ -658,7 +658,7 @@ def get(self, request, slug, project_id): project_member = ProjectMember.objects.get( project_id=project_id, workspace__slug=slug, member=request.user - ) + ).select_related("project", "member", "workspace", "workspace__owner") serializer = ProjectMemberSerializer(project_member) return Response(serializer.data, status=status.HTTP_200_OK) diff --git a/apiserver/plane/api/views/workspace.py b/apiserver/plane/api/views/workspace.py index e03a80e821c..c0efde346ac 100644 --- a/apiserver/plane/api/views/workspace.py +++ b/apiserver/plane/api/views/workspace.py @@ -553,7 +553,7 @@ def get(self, request, slug): try: workspace_member = WorkspaceMember.objects.get( member=request.user, workspace__slug=slug - ) + ).select_releated("workspace", "workspace__owner", "member") serializer = WorkSpaceMemberSerializer(workspace_member) return Response(serializer.data, status=status.HTTP_200_OK) except (Workspace.DoesNotExist, WorkspaceMember.DoesNotExist): From 41f6fafd048d5ce03ee773be3982affa30663409 Mon Sep 17 00:00:00 2001 From: pablohashescobar Date: Sat, 14 Jan 2023 11:53:20 +0530 Subject: [PATCH 09/23] fix: project member endpoint --- apiserver/plane/api/views/project.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apiserver/plane/api/views/project.py b/apiserver/plane/api/views/project.py index 889622c4da9..2ec6faf1e53 100644 --- a/apiserver/plane/api/views/project.py +++ b/apiserver/plane/api/views/project.py @@ -658,7 +658,7 @@ def get(self, request, slug, project_id): project_member = ProjectMember.objects.get( project_id=project_id, workspace__slug=slug, member=request.user - ).select_related("project", "member", "workspace", "workspace__owner") + ) serializer = ProjectMemberSerializer(project_member) return Response(serializer.data, status=status.HTTP_200_OK) From bbd0737367a6df4d6364d470819b2a0c6be2757b Mon Sep 17 00:00:00 2001 From: pablohashescobar Date: Sat, 14 Jan 2023 11:56:00 +0530 Subject: [PATCH 10/23] fix: revert back workspace members --- apiserver/plane/api/views/workspace.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apiserver/plane/api/views/workspace.py b/apiserver/plane/api/views/workspace.py index c0efde346ac..e03a80e821c 100644 --- a/apiserver/plane/api/views/workspace.py +++ b/apiserver/plane/api/views/workspace.py @@ -553,7 +553,7 @@ def get(self, request, slug): try: workspace_member = WorkspaceMember.objects.get( member=request.user, workspace__slug=slug - ).select_releated("workspace", "workspace__owner", "member") + ) serializer = WorkSpaceMemberSerializer(workspace_member) return Response(serializer.data, status=status.HTTP_200_OK) except (Workspace.DoesNotExist, WorkspaceMember.DoesNotExist): From f5c220f5b08ca93e6307417a0986cc28ce7a0ff7 Mon Sep 17 00:00:00 2001 From: pablohashescobar Date: Sat, 14 Jan 2023 14:56:34 +0530 Subject: [PATCH 11/23] refactor: update base file to remove workspace and project query and update permission layer accordingly --- apiserver/plane/api/permissions/project.py | 28 ++++++++++---- apiserver/plane/api/permissions/workspace.py | 6 +-- apiserver/plane/api/views/base.py | 40 -------------------- 3 files changed, 23 insertions(+), 51 deletions(-) diff --git a/apiserver/plane/api/permissions/project.py b/apiserver/plane/api/permissions/project.py index 019496cda69..64da4da292e 100644 --- a/apiserver/plane/api/permissions/project.py +++ b/apiserver/plane/api/permissions/project.py @@ -17,12 +17,17 @@ def has_permission(self, request, view): ## Only workspace owners or admins can create the projects if request.method == "POST": return WorkspaceMember.objects.filter( - workspace=view.workspace, member=request.user, role__in=[15, 20] + workspace__slug=view.workspace_slug, + member=request.user, + role__in=[15, 20], ).exists() ## Only Project Admins can update project attributes return ProjectMember.objects.filter( - workspace=view.workspace, member=request.user, role=20 + workspace__slug=view.workspace_slug, + member=request.user, + role=20, + project_id=view.project_id, ).exists() @@ -38,12 +43,17 @@ def has_permission(self, request, view): ## Only workspace owners or admins can create the projects if request.method == "POST": return WorkspaceMember.objects.filter( - workspace=view.workspace, member=request.user, role__in=[15, 20] + workspace__slug=view.workspace_slug, + member=request.user, + role__in=[15, 20], ).exists() ## Only Project Admins can update project attributes return ProjectMember.objects.filter( - workspace=view.workspace, member=request.user, role__in=[15, 20] + workspace__slug=view.workspace_slug, + member=request.user, + role__in=[15, 20], + project_id=view.project_id, ).exists() @@ -52,12 +62,14 @@ def has_permission(self, request, view): if request.user.is_anonymous: return False - + ## Safe Methods -> Handle the filtering logic in queryset if request.method in SAFE_METHODS: return True - ## Only workspace owners or admins can create the projects - + ## Only workspace owners or admins can create the project attributes return ProjectMember.objects.filter( - workspace=view.workspace, member=request.user, role__in=[15, 20] + workspace__slug=view.workspace_slug, + member=request.user, + role__in=[15, 20], + project_id=view.project_id, ).exists() diff --git a/apiserver/plane/api/permissions/workspace.py b/apiserver/plane/api/permissions/workspace.py index 510d87ce24d..99745b7eb78 100644 --- a/apiserver/plane/api/permissions/workspace.py +++ b/apiserver/plane/api/permissions/workspace.py @@ -22,13 +22,13 @@ def has_permission(self, request, view): # allow only admins and owners to update the workspace settings if request.method in ["PUT", "PATCH"]: return WorkspaceMember.objects.filter( - member=request.user, workspace=view.workspace, role__in=[15, 20] + member=request.user, workspace__slug=view.workspace_slug, role__in=[15, 20] ).exists() # allow only owner to delete the workspace if request.method == "DELETE": return WorkspaceMember.objects.filter( - member=request.user, workspace=view.workspace, role=20 + member=request.user, workspace__slug=view.workspace_slug, role=20 ).exists() @@ -39,5 +39,5 @@ def has_permission(self, request, view): return False return WorkspaceMember.objects.filter( - member=request.user, workspace=view.workspace, role__in=[15, 20] + member=request.user, workspace__slug=view.workspace_slug, role__in=[15, 20] ).exists() diff --git a/apiserver/plane/api/views/base.py b/apiserver/plane/api/views/base.py index d4b18cefc5e..36a362f0691 100644 --- a/apiserver/plane/api/views/base.py +++ b/apiserver/plane/api/views/base.py @@ -55,16 +55,6 @@ def dispatch(self, request, *args, **kwargs): def workspace_slug(self): return self.kwargs.get("slug", None) - @property - def workspace(self): - if self.workspace_slug: - try: - return Workspace.objects.get(slug=self.workspace_slug) - except Workspace.DoesNotExist: - raise NotFound(detail="Workspace does not exist") - else: - return None - @property def project_id(self): project_id = self.kwargs.get("project_id", None) @@ -74,16 +64,6 @@ def project_id(self): if resolve(self.request.path_info).url_name == "project": return self.kwargs.get("pk", None) - @property - def project(self): - if self.project_id: - try: - return Project.objects.get(pk=self.project_id) - except Project.DoesNotExist: - raise NotFound(detail="Project does not exist") - else: - return None - class BaseAPIView(APIView, BasePaginator): @@ -118,26 +98,6 @@ def dispatch(self, request, *args, **kwargs): def workspace_slug(self): return self.kwargs.get("slug", None) - @property - def workspace(self): - if self.workspace_slug: - try: - return Workspace.objects.get(slug=self.workspace_slug) - except Workspace.DoesNotExist: - raise NotFound(detail="Workspace does not exist") - else: - return None - @property def project_id(self): return self.kwargs.get("project_id", None) - - @property - def project(self): - if self.project_id: - try: - return Project.objects.get(pk=self.project_id) - except Project.DoesNotExist: - raise NotFound(detail="Project does not exist") - else: - return None From a217b05d5b6c5dc633737d9e324e02eb67100399 Mon Sep 17 00:00:00 2001 From: pablohashescobar Date: Sat, 14 Jan 2023 18:27:20 +0530 Subject: [PATCH 12/23] refactor: update read_only fields in read serializers --- apiserver/plane/api/serializers/__init__.py | 1 - apiserver/plane/api/serializers/issue.py | 69 +++------------------ 2 files changed, 10 insertions(+), 60 deletions(-) diff --git a/apiserver/plane/api/serializers/__init__.py b/apiserver/plane/api/serializers/__init__.py index e2d47490108..ba494ec9e55 100644 --- a/apiserver/plane/api/serializers/__init__.py +++ b/apiserver/plane/api/serializers/__init__.py @@ -29,7 +29,6 @@ IssueCommentSerializer, TimeLineIssueSerializer, IssuePropertySerializer, - IssueLabelSerializer, BlockerIssueSerializer, BlockedIssueSerializer, IssueAssigneeSerializer, diff --git a/apiserver/plane/api/serializers/issue.py b/apiserver/plane/api/serializers/issue.py index 569eef88c71..c3befcd1542 100644 --- a/apiserver/plane/api/serializers/issue.py +++ b/apiserver/plane/api/serializers/issue.py @@ -41,6 +41,7 @@ class Meta: "target_date", "sequence_id", ] + read_only_fields = fields # Issue Serializer with state details @@ -312,23 +313,7 @@ class LabelSerializer(BaseSerializer): class Meta: model = Label fields = "__all__" - read_only_fields = [ - "workspace", - "project", - ] - - -class IssueLabelSerializer(BaseSerializer): - - # label_details = LabelSerializer(read_only=True, source="label") - - class Meta: - model = IssueLabel - fields = "__all__" - read_only_fields = [ - "workspace", - "project", - ] + read_only_fields = fields class BlockedIssueSerializer(BaseSerializer): @@ -338,6 +323,7 @@ class BlockedIssueSerializer(BaseSerializer): class Meta: model = IssueBlocker fields = "__all__" + read_only_fields = fields class BlockerIssueSerializer(BaseSerializer): @@ -347,6 +333,7 @@ class BlockerIssueSerializer(BaseSerializer): class Meta: model = IssueBlocker fields = "__all__" + read_only_fields = fields class IssueAssigneeSerializer(BaseSerializer): @@ -356,20 +343,14 @@ class IssueAssigneeSerializer(BaseSerializer): class Meta: model = IssueAssignee fields = "__all__" + read_only_fields = fields class CycleBaseSerializer(BaseSerializer): class Meta: model = Cycle fields = "__all__" - read_only_fields = [ - "workspace", - "project", - "created_by", - "updated_by", - "created_at", - "updated_at", - ] + read_only_fields = fields class IssueCycleDetailSerializer(BaseSerializer): @@ -379,29 +360,13 @@ class IssueCycleDetailSerializer(BaseSerializer): class Meta: model = CycleIssue fields = "__all__" - read_only_fields = [ - "workspace", - "project", - "created_by", - "updated_by", - "created_at", - "updated_at", - ] - + read_only_fields = fields class ModuleBaseSerializer(BaseSerializer): class Meta: model = Module fields = "__all__" - read_only_fields = [ - "workspace", - "project", - "created_by", - "updated_by", - "created_at", - "updated_at", - ] - + read_only_fields = fields class IssueModuleDetailSerializer(BaseSerializer): @@ -410,14 +375,7 @@ class IssueModuleDetailSerializer(BaseSerializer): class Meta: model = ModuleIssue fields = "__all__" - read_only_fields = [ - "workspace", - "project", - "created_by", - "updated_by", - "created_at", - "updated_at", - ] + read_only_fields = fields class IssueSerializer(BaseSerializer): @@ -436,11 +394,4 @@ class IssueSerializer(BaseSerializer): class Meta: model = Issue fields = "__all__" - read_only_fields = [ - "workspace", - "project", - "created_by", - "updated_by", - "created_at", - "updated_at", - ] + read_only_fields = fields From 06d0f6fb04bf2bcfa21c564ca83d287108d1e587 Mon Sep 17 00:00:00 2001 From: pablohashescobar Date: Sat, 14 Jan 2023 18:35:31 +0530 Subject: [PATCH 13/23] fix: read only serializers --- apiserver/plane/api/serializers/issue.py | 72 +++++++++++++++++++++--- 1 file changed, 63 insertions(+), 9 deletions(-) diff --git a/apiserver/plane/api/serializers/issue.py b/apiserver/plane/api/serializers/issue.py index c3befcd1542..b084f5ae4d2 100644 --- a/apiserver/plane/api/serializers/issue.py +++ b/apiserver/plane/api/serializers/issue.py @@ -313,7 +313,12 @@ class LabelSerializer(BaseSerializer): class Meta: model = Label fields = "__all__" - read_only_fields = fields + + def get_fields(self): + fields = super().get_fields() + for field in fields.values(): + field.read_only = True + return fields class BlockedIssueSerializer(BaseSerializer): @@ -323,7 +328,13 @@ class BlockedIssueSerializer(BaseSerializer): class Meta: model = IssueBlocker fields = "__all__" - read_only_fields = fields + + def get_fields(self): + fields = super().get_fields() + for field in fields.values(): + field.read_only = True + return fields + class BlockerIssueSerializer(BaseSerializer): @@ -333,7 +344,13 @@ class BlockerIssueSerializer(BaseSerializer): class Meta: model = IssueBlocker fields = "__all__" - read_only_fields = fields + + def get_fields(self): + fields = super().get_fields() + for field in fields.values(): + field.read_only = True + return fields + class IssueAssigneeSerializer(BaseSerializer): @@ -343,14 +360,26 @@ class IssueAssigneeSerializer(BaseSerializer): class Meta: model = IssueAssignee fields = "__all__" - read_only_fields = fields + + def get_fields(self): + fields = super().get_fields() + for field in fields.values(): + field.read_only = True + return fields + class CycleBaseSerializer(BaseSerializer): class Meta: model = Cycle fields = "__all__" - read_only_fields = fields + + def get_fields(self): + fields = super().get_fields() + for field in fields.values(): + field.read_only = True + return fields + class IssueCycleDetailSerializer(BaseSerializer): @@ -360,13 +389,25 @@ class IssueCycleDetailSerializer(BaseSerializer): class Meta: model = CycleIssue fields = "__all__" - read_only_fields = fields + + def get_fields(self): + fields = super().get_fields() + for field in fields.values(): + field.read_only = True + return fields + class ModuleBaseSerializer(BaseSerializer): class Meta: model = Module fields = "__all__" - read_only_fields = fields + + def get_fields(self): + fields = super().get_fields() + for field in fields.values(): + field.read_only = True + return fields + class IssueModuleDetailSerializer(BaseSerializer): @@ -375,10 +416,18 @@ class IssueModuleDetailSerializer(BaseSerializer): class Meta: model = ModuleIssue fields = "__all__" - read_only_fields = fields + + def get_fields(self): + fields = super().get_fields() + for field in fields.values(): + field.read_only = True + return fields + class IssueSerializer(BaseSerializer): + + project_detail = ProjectSerializer(read_only=True, source="project") state_detail = StateSerializer(read_only=True, source="state") parent_detail = IssueFlatSerializer(read_only=True, source="parent") @@ -394,4 +443,9 @@ class IssueSerializer(BaseSerializer): class Meta: model = Issue fields = "__all__" - read_only_fields = fields + + def get_fields(self): + fields = super().get_fields() + for field in fields.values(): + field.read_only = True + return fields From d10e4b3058f4e886fd120b4b4e1de0d97eb7a348 Mon Sep 17 00:00:00 2001 From: pablohashescobar Date: Sat, 14 Jan 2023 19:30:30 +0530 Subject: [PATCH 14/23] chore: update drf package --- apiserver/requirements/base.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apiserver/requirements/base.txt b/apiserver/requirements/base.txt index 7887fa456b7..7dff0a765e2 100644 --- a/apiserver/requirements/base.txt +++ b/apiserver/requirements/base.txt @@ -6,7 +6,7 @@ django-taggit==2.1.0 psycopg2==2.9.3 django-oauth-toolkit==2.0.0 mistune==2.0.3 -djangorestframework==3.13.1 +djangorestframework==3.14.0 redis==4.2.2 django-nested-admin==3.4.0 django-cors-headers==3.11.0 From 3e34e56e3ee04208d0162f8b37108c5817a83c0d Mon Sep 17 00:00:00 2001 From: pablohashescobar Date: Sat, 14 Jan 2023 19:37:08 +0530 Subject: [PATCH 15/23] revert: drf version upgrade --- apiserver/requirements/base.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apiserver/requirements/base.txt b/apiserver/requirements/base.txt index 7dff0a765e2..7887fa456b7 100644 --- a/apiserver/requirements/base.txt +++ b/apiserver/requirements/base.txt @@ -6,7 +6,7 @@ django-taggit==2.1.0 psycopg2==2.9.3 django-oauth-toolkit==2.0.0 mistune==2.0.3 -djangorestframework==3.14.0 +djangorestframework==3.13.1 redis==4.2.2 django-nested-admin==3.4.0 django-cors-headers==3.11.0 From 528ab1ee208a226dff5565e4743b5cc2d42bc5a1 Mon Sep 17 00:00:00 2001 From: pablohashescobar Date: Sat, 14 Jan 2023 20:11:52 +0530 Subject: [PATCH 16/23] revert: read only fields update --- apiserver/plane/api/serializers/issue.py | 64 ++---------------------- 1 file changed, 4 insertions(+), 60 deletions(-) diff --git a/apiserver/plane/api/serializers/issue.py b/apiserver/plane/api/serializers/issue.py index b084f5ae4d2..64d563bd3b6 100644 --- a/apiserver/plane/api/serializers/issue.py +++ b/apiserver/plane/api/serializers/issue.py @@ -313,12 +313,10 @@ class LabelSerializer(BaseSerializer): class Meta: model = Label fields = "__all__" - - def get_fields(self): - fields = super().get_fields() - for field in fields.values(): - field.read_only = True - return fields + read_only_fields = [ + "workspace", + "project", + ] class BlockedIssueSerializer(BaseSerializer): @@ -329,13 +327,6 @@ class Meta: model = IssueBlocker fields = "__all__" - def get_fields(self): - fields = super().get_fields() - for field in fields.values(): - field.read_only = True - return fields - - class BlockerIssueSerializer(BaseSerializer): @@ -345,13 +336,6 @@ class Meta: model = IssueBlocker fields = "__all__" - def get_fields(self): - fields = super().get_fields() - for field in fields.values(): - field.read_only = True - return fields - - class IssueAssigneeSerializer(BaseSerializer): @@ -361,26 +345,12 @@ class Meta: model = IssueAssignee fields = "__all__" - def get_fields(self): - fields = super().get_fields() - for field in fields.values(): - field.read_only = True - return fields - - class CycleBaseSerializer(BaseSerializer): class Meta: model = Cycle fields = "__all__" - def get_fields(self): - fields = super().get_fields() - for field in fields.values(): - field.read_only = True - return fields - - class IssueCycleDetailSerializer(BaseSerializer): @@ -390,24 +360,12 @@ class Meta: model = CycleIssue fields = "__all__" - def get_fields(self): - fields = super().get_fields() - for field in fields.values(): - field.read_only = True - return fields - class ModuleBaseSerializer(BaseSerializer): class Meta: model = Module fields = "__all__" - def get_fields(self): - fields = super().get_fields() - for field in fields.values(): - field.read_only = True - return fields - class IssueModuleDetailSerializer(BaseSerializer): @@ -417,17 +375,9 @@ class Meta: model = ModuleIssue fields = "__all__" - def get_fields(self): - fields = super().get_fields() - for field in fields.values(): - field.read_only = True - return fields - - class IssueSerializer(BaseSerializer): - project_detail = ProjectSerializer(read_only=True, source="project") state_detail = StateSerializer(read_only=True, source="state") parent_detail = IssueFlatSerializer(read_only=True, source="parent") @@ -443,9 +393,3 @@ class IssueSerializer(BaseSerializer): class Meta: model = Issue fields = "__all__" - - def get_fields(self): - fields = super().get_fields() - for field in fields.values(): - field.read_only = True - return fields From 933f1bbbde85c66ead1819239f2601a69ef0fa6b Mon Sep 17 00:00:00 2001 From: pablohashescobar Date: Sat, 14 Jan 2023 20:41:46 +0530 Subject: [PATCH 17/23] revert: update serializer to old state --- apiserver/plane/api/serializers/issue.py | 55 +++++++++++++++++++++++- 1 file changed, 53 insertions(+), 2 deletions(-) diff --git a/apiserver/plane/api/serializers/issue.py b/apiserver/plane/api/serializers/issue.py index 64d563bd3b6..227d3b96ddd 100644 --- a/apiserver/plane/api/serializers/issue.py +++ b/apiserver/plane/api/serializers/issue.py @@ -41,7 +41,6 @@ class Meta: "target_date", "sequence_id", ] - read_only_fields = fields # Issue Serializer with state details @@ -319,6 +318,19 @@ class Meta: ] +class IssueLabelSerializer(BaseSerializer): + + # label_details = LabelSerializer(read_only=True, source="label") + + class Meta: + model = IssueLabel + fields = "__all__" + read_only_fields = [ + "workspace", + "project", + ] + + class BlockedIssueSerializer(BaseSerializer): blocked_issue_detail = IssueFlatSerializer(source="block", read_only=True) @@ -350,6 +362,14 @@ class CycleBaseSerializer(BaseSerializer): class Meta: model = Cycle fields = "__all__" + read_only_fields = [ + "workspace", + "project", + "created_by", + "updated_by", + "created_at", + "updated_at", + ] class IssueCycleDetailSerializer(BaseSerializer): @@ -359,12 +379,28 @@ class IssueCycleDetailSerializer(BaseSerializer): class Meta: model = CycleIssue fields = "__all__" + read_only_fields = [ + "workspace", + "project", + "created_by", + "updated_by", + "created_at", + "updated_at", + ] class ModuleBaseSerializer(BaseSerializer): class Meta: model = Module fields = "__all__" + read_only_fields = [ + "workspace", + "project", + "created_by", + "updated_by", + "created_at", + "updated_at", + ] class IssueModuleDetailSerializer(BaseSerializer): @@ -374,10 +410,17 @@ class IssueModuleDetailSerializer(BaseSerializer): class Meta: model = ModuleIssue fields = "__all__" + read_only_fields = [ + "workspace", + "project", + "created_by", + "updated_by", + "created_at", + "updated_at", + ] class IssueSerializer(BaseSerializer): - project_detail = ProjectSerializer(read_only=True, source="project") state_detail = StateSerializer(read_only=True, source="state") parent_detail = IssueFlatSerializer(read_only=True, source="parent") @@ -393,3 +436,11 @@ class IssueSerializer(BaseSerializer): class Meta: model = Issue fields = "__all__" + read_only_fields = [ + "workspace", + "project", + "created_by", + "updated_by", + "created_at", + "updated_at", + ] \ No newline at end of file From 810efb5cd5e9e6b206403ebf5affb90ec39676d0 Mon Sep 17 00:00:00 2001 From: pablohashescobar Date: Sat, 14 Jan 2023 20:45:50 +0530 Subject: [PATCH 18/23] chore: update drf to latest version --- apiserver/requirements/base.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apiserver/requirements/base.txt b/apiserver/requirements/base.txt index 7887fa456b7..7dff0a765e2 100644 --- a/apiserver/requirements/base.txt +++ b/apiserver/requirements/base.txt @@ -6,7 +6,7 @@ django-taggit==2.1.0 psycopg2==2.9.3 django-oauth-toolkit==2.0.0 mistune==2.0.3 -djangorestframework==3.13.1 +djangorestframework==3.14.0 redis==4.2.2 django-nested-admin==3.4.0 django-cors-headers==3.11.0 From f6467c596107aa31181f827236bc04a91218c4f8 Mon Sep 17 00:00:00 2001 From: pablohashescobar Date: Sat, 14 Jan 2023 22:10:12 +0530 Subject: [PATCH 19/23] refactor: update dispatch to display method as well --- apiserver/plane/api/views/base.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/apiserver/plane/api/views/base.py b/apiserver/plane/api/views/base.py index 36a362f0691..a4b9ac58476 100644 --- a/apiserver/plane/api/views/base.py +++ b/apiserver/plane/api/views/base.py @@ -48,7 +48,9 @@ def dispatch(self, request, *args, **kwargs): if settings.DEBUG: from django.db import connection - print(f"{request.get_full_path()} of Queries: {len(connection.queries)}") + print( + f"{request.method} - {request.get_full_path()} of Queries: {len(connection.queries)}" + ) return response @property @@ -91,7 +93,9 @@ def dispatch(self, request, *args, **kwargs): if settings.DEBUG: from django.db import connection - print(f"{request.get_full_path()} of Queries: {len(connection.queries)}") + print( + f"{request.method} - {request.get_full_path()} of Queries: {len(connection.queries)}" + ) return response @property From 2f256eeeb2615e17d3115dd96b4480ec992a215b Mon Sep 17 00:00:00 2001 From: pablohashescobar Date: Sun, 15 Jan 2023 11:44:17 +0530 Subject: [PATCH 20/23] refactor: optimize cycle and module issue queries --- apiserver/plane/api/views/cycle.py | 5 +++-- apiserver/plane/api/views/module.py | 2 ++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/apiserver/plane/api/views/cycle.py b/apiserver/plane/api/views/cycle.py index 56b6ee360e5..a4cfcb6d96c 100644 --- a/apiserver/plane/api/views/cycle.py +++ b/apiserver/plane/api/views/cycle.py @@ -63,8 +63,9 @@ def get_queryset(self): .select_related("project") .select_related("workspace") .select_related("cycle") - .select_related("issue") - .select_related("issue__state", "issue__project") + .select_related("issue", "issue__state", "issue__project") + .prefetch_related("issue__assignees") + .prefetch_related("issue__labels") .distinct() ) diff --git a/apiserver/plane/api/views/module.py b/apiserver/plane/api/views/module.py index d19c586b3c5..18aa5f9e5d5 100644 --- a/apiserver/plane/api/views/module.py +++ b/apiserver/plane/api/views/module.py @@ -120,6 +120,8 @@ def get_queryset(self): .select_related("workspace") .select_related("module") .select_related("issue", "issue__state", "issue__project") + .prefetch_related("issue__assignees") + .prefetch_related("issue__labels") .distinct() ) From 1360db973f96d509742799d4aaab186eb16fdfac Mon Sep 17 00:00:00 2001 From: pablohashescobar Date: Sun, 15 Jan 2023 13:11:11 +0530 Subject: [PATCH 21/23] refactor: optimize module endpoint and issue list endpoint --- apiserver/plane/api/views/issue.py | 4 +++- apiserver/plane/api/views/module.py | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/apiserver/plane/api/views/issue.py b/apiserver/plane/api/views/issue.py index 79743c0066f..56fc21afb3e 100644 --- a/apiserver/plane/api/views/issue.py +++ b/apiserver/plane/api/views/issue.py @@ -126,7 +126,9 @@ def get_queryset(self): .prefetch_related( Prefetch( "issue_module", - queryset=ModuleIssue.objects.select_related("module", "issue"), + queryset=ModuleIssue.objects.select_related( + "module", "issue" + ).prefetch_related("module__members"), ), ) ) diff --git a/apiserver/plane/api/views/module.py b/apiserver/plane/api/views/module.py index 18aa5f9e5d5..28f582c889d 100644 --- a/apiserver/plane/api/views/module.py +++ b/apiserver/plane/api/views/module.py @@ -47,7 +47,7 @@ def get_queryset(self): "issue_module", queryset=ModuleIssue.objects.select_related( "module", "issue", "issue__state", "issue__project" - ), + ).prefetch_related("issue__assignees", "issue__labels"), ) ) .prefetch_related( From c07ea9391cbc869ada5d18dd2d7a0e0dfbdd2671 Mon Sep 17 00:00:00 2001 From: pablohashescobar Date: Sun, 15 Jan 2023 13:36:47 +0530 Subject: [PATCH 22/23] refactor: update prefetch related in modules and cycles --- apiserver/plane/api/views/cycle.py | 3 +-- apiserver/plane/api/views/module.py | 12 +++++++++--- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/apiserver/plane/api/views/cycle.py b/apiserver/plane/api/views/cycle.py index a4cfcb6d96c..28125512f90 100644 --- a/apiserver/plane/api/views/cycle.py +++ b/apiserver/plane/api/views/cycle.py @@ -64,8 +64,7 @@ def get_queryset(self): .select_related("workspace") .select_related("cycle") .select_related("issue", "issue__state", "issue__project") - .prefetch_related("issue__assignees") - .prefetch_related("issue__labels") + .prefetch_related("issue__assignees", "issue__labels") .distinct() ) diff --git a/apiserver/plane/api/views/module.py b/apiserver/plane/api/views/module.py index 28f582c889d..4c121f23f24 100644 --- a/apiserver/plane/api/views/module.py +++ b/apiserver/plane/api/views/module.py @@ -15,7 +15,13 @@ ModuleIssueSerializer, ) from plane.api.permissions import ProjectEntityPermission -from plane.db.models import Module, ModuleIssue, Project, Issue, ModuleLink +from plane.db.models import ( + Module, + ModuleIssue, + Project, + Issue, + ModuleLink, +) class ModuleViewSet(BaseViewSet): @@ -120,8 +126,8 @@ def get_queryset(self): .select_related("workspace") .select_related("module") .select_related("issue", "issue__state", "issue__project") - .prefetch_related("issue__assignees") - .prefetch_related("issue__labels") + .prefetch_related("issue__assignees", "issue__labels") + .prefetch_related("module__members") .distinct() ) From 13405c9ae5386f22772bb046c06e8d3360b446c5 Mon Sep 17 00:00:00 2001 From: pablohashescobar Date: Tue, 17 Jan 2023 01:47:49 +0530 Subject: [PATCH 23/23] refactor: create permission mapping in permission file --- apiserver/plane/api/permissions/project.py | 16 +++++++++++----- apiserver/plane/api/permissions/workspace.py | 20 ++++++++++++++++---- 2 files changed, 27 insertions(+), 9 deletions(-) diff --git a/apiserver/plane/api/permissions/project.py b/apiserver/plane/api/permissions/project.py index 44659a671f8..f87aaf08e8b 100644 --- a/apiserver/plane/api/permissions/project.py +++ b/apiserver/plane/api/permissions/project.py @@ -4,6 +4,12 @@ # Module import from plane.db.models import WorkspaceMember, ProjectMember +# Permission Mappings +Admin = 20 +Member = 15 +Viewer = 10 +Guest = 5 + class ProjectBasePermission(BasePermission): def has_permission(self, request, view): @@ -22,14 +28,14 @@ def has_permission(self, request, view): return WorkspaceMember.objects.filter( workspace__slug=view.workspace_slug, member=request.user, - role__in=[15, 20], + role__in=[Admin, Member], ).exists() ## Only Project Admins can update project attributes return ProjectMember.objects.filter( workspace__slug=view.workspace_slug, member=request.user, - role=20, + role=Admin, project_id=view.project_id, ).exists() @@ -50,14 +56,14 @@ def has_permission(self, request, view): return WorkspaceMember.objects.filter( workspace__slug=view.workspace_slug, member=request.user, - role__in=[15, 20], + role__in=[Admin, Member], ).exists() ## Only Project Admins can update project attributes return ProjectMember.objects.filter( workspace__slug=view.workspace_slug, member=request.user, - role__in=[15, 20], + role__in=[Admin, Member], project_id=view.project_id, ).exists() @@ -80,6 +86,6 @@ def has_permission(self, request, view): return ProjectMember.objects.filter( workspace__slug=view.workspace_slug, member=request.user, - role__in=[15, 20], + role__in=[Admin, Member], project_id=view.project_id, ).exists() diff --git a/apiserver/plane/api/permissions/workspace.py b/apiserver/plane/api/permissions/workspace.py index 99745b7eb78..2a2e1d33915 100644 --- a/apiserver/plane/api/permissions/workspace.py +++ b/apiserver/plane/api/permissions/workspace.py @@ -2,7 +2,15 @@ from rest_framework.permissions import BasePermission, SAFE_METHODS # Module imports -from plane.db.models import WorkspaceMember, ProjectMember +from plane.db.models import WorkspaceMember + + + +# Permission Mappings +Owner = 20 +Admin = 15 +Member = 10 +Guest = 5 # TODO: Move the below logic to python match - python v3.10 @@ -22,13 +30,15 @@ def has_permission(self, request, view): # allow only admins and owners to update the workspace settings if request.method in ["PUT", "PATCH"]: return WorkspaceMember.objects.filter( - member=request.user, workspace__slug=view.workspace_slug, role__in=[15, 20] + member=request.user, + workspace__slug=view.workspace_slug, + role__in=[Owner, Admin], ).exists() # allow only owner to delete the workspace if request.method == "DELETE": return WorkspaceMember.objects.filter( - member=request.user, workspace__slug=view.workspace_slug, role=20 + member=request.user, workspace__slug=view.workspace_slug, role=Owner ).exists() @@ -39,5 +49,7 @@ def has_permission(self, request, view): return False return WorkspaceMember.objects.filter( - member=request.user, workspace__slug=view.workspace_slug, role__in=[15, 20] + member=request.user, + workspace__slug=view.workspace_slug, + role__in=[Owner, Admin], ).exists()