From 7f3e9c5d1980dab3646a64e71d1f92155106fd53 Mon Sep 17 00:00:00 2001 From: Mardone Date: Thu, 14 Dec 2023 15:12:37 -0300 Subject: [PATCH 01/11] Add: Pagination ordering options for organizationviewset --- connect/api/v2/organizations/views.py | 22 ++++++++++++++++++---- connect/api/v2/paginations.py | 6 ++++++ connect/api/v2/projects/views.py | 3 +++ 3 files changed, 27 insertions(+), 4 deletions(-) diff --git a/connect/api/v2/organizations/views.py b/connect/api/v2/organizations/views.py index c5cd75a5..1ea4a5ec 100644 --- a/connect/api/v2/organizations/views.py +++ b/connect/api/v2/organizations/views.py @@ -1,8 +1,10 @@ +import pendulum from rest_framework import mixins, status from rest_framework.decorators import action from rest_framework.viewsets import GenericViewSet from rest_framework.response import Response from rest_framework.permissions import IsAuthenticated +from rest_framework.exceptions import ValidationError from drf_yasg2.utils import swagger_auto_schema from django.utils.translation import ugettext_lazy as _ @@ -17,7 +19,6 @@ OrganizationHasPermission, ) from connect.api.v2.paginations import CustomCursorPagination - from connect.api.v2.organizations.serializers import ( OrganizationSeralizer, NestedAuthorizationOrganizationSerializer @@ -26,9 +27,7 @@ from connect.api.v2.organizations.api_schemas import ( create_organization_schema, ) -from rest_framework.exceptions import ValidationError -from connect.api.v2.paginations import CustomCursorPagination -import pendulum + class OrganizationViewSet( mixins.RetrieveModelMixin, @@ -57,6 +56,21 @@ def get_queryset(self, *args, **kwargs): return self.queryset.filter(pk__in=auth) + def get_ordering(self): + valid_ordering_params = [ + "created_at", + "-created_at", + "name", + "-name", + ] + ordering = self.request.query_params.get("ordering", "created_at") + if ordering in valid_ordering_params: + return ordering + return Response( + _('Invalid ordering parameter'), + status=status.HTTP_400_BAD_REQUEST + ) + @swagger_auto_schema(request_body=create_organization_schema) def create(self, request, *args, **kwargs): org_data = request.data.get("organization") diff --git a/connect/api/v2/paginations.py b/connect/api/v2/paginations.py index b805636d..6876baf8 100644 --- a/connect/api/v2/paginations.py +++ b/connect/api/v2/paginations.py @@ -5,3 +5,9 @@ class CustomCursorPagination(CursorPagination): page_size_query_param = 'page_size' page_size = 20 ordering = "created_at" + + def paginate_queryset(self, queryset, request, view=None): + ordering = view.get_ordering() + if ordering: + queryset = queryset.order_by(ordering) + return super().paginate_queryset(queryset, request, view) diff --git a/connect/api/v2/projects/views.py b/connect/api/v2/projects/views.py index c08af3ff..1fc81e03 100644 --- a/connect/api/v2/projects/views.py +++ b/connect/api/v2/projects/views.py @@ -41,6 +41,9 @@ def get_queryset(self, **kwargs): return super().get_queryset().filter(organization__uuid=self.kwargs["organization_uuid"]) + + + @action( detail=True, methods=["GET"], From ef0f5feec01d54349dc089114bd4485693d3ba80 Mon Sep 17 00:00:00 2001 From: Mardone Date: Thu, 14 Dec 2023 16:24:54 -0300 Subject: [PATCH 02/11] Add: Project Viewset ordering params --- connect/api/v2/paginations.py | 1 - connect/api/v2/projects/views.py | 18 +++++++++++++++++- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/connect/api/v2/paginations.py b/connect/api/v2/paginations.py index 6876baf8..9a26b632 100644 --- a/connect/api/v2/paginations.py +++ b/connect/api/v2/paginations.py @@ -4,7 +4,6 @@ class CustomCursorPagination(CursorPagination): page_size_query_param = 'page_size' page_size = 20 - ordering = "created_at" def paginate_queryset(self, queryset, request, view=None): ordering = view.get_ordering() diff --git a/connect/api/v2/projects/views.py b/connect/api/v2/projects/views.py index 1fc81e03..e50fdb25 100644 --- a/connect/api/v2/projects/views.py +++ b/connect/api/v2/projects/views.py @@ -41,7 +41,23 @@ def get_queryset(self, **kwargs): return super().get_queryset().filter(organization__uuid=self.kwargs["organization_uuid"]) - + def get_ordering(self): + valid_ordering_params = [ + 'last_opened_on', + 'created_at', + 'name', + '-last_opened_on', + '-created_at', + '-name' + ] + ordering = self.request.query_params.get("ordering", 'created_at') + if ordering in valid_ordering_params: + ordering = 'created_at' + else: + return Response( + 'Invalid ordering parameter', + status=status.HTTP_400_BAD_REQUEST + ) @action( From 20037133043e7855687303a8687fb0509e3ca21d Mon Sep 17 00:00:00 2001 From: Mardone Date: Fri, 15 Dec 2023 12:49:45 -0300 Subject: [PATCH 03/11] Fix: Ordering by other fields --- connect/api/v2/organizations/views.py | 23 ++++++++++------------- connect/api/v2/paginations.py | 4 +--- 2 files changed, 11 insertions(+), 16 deletions(-) diff --git a/connect/api/v2/organizations/views.py b/connect/api/v2/organizations/views.py index 1ea4a5ec..5ccc7d84 100644 --- a/connect/api/v2/organizations/views.py +++ b/connect/api/v2/organizations/views.py @@ -57,19 +57,16 @@ def get_queryset(self, *args, **kwargs): return self.queryset.filter(pk__in=auth) def get_ordering(self): - valid_ordering_params = [ - "created_at", - "-created_at", - "name", - "-name", - ] - ordering = self.request.query_params.get("ordering", "created_at") - if ordering in valid_ordering_params: - return ordering - return Response( - _('Invalid ordering parameter'), - status=status.HTTP_400_BAD_REQUEST - ) + valid_fields = (org_fields.name for org_fields in Organization._meta.get_fields()) + ordering = [] + for param in self.request.query_params.getlist('ordering'): + if param.startswith('-'): + field = param[1:] + else: + field = param + if field in valid_fields: + ordering.append(param) + return ordering if ordering else None @swagger_auto_schema(request_body=create_organization_schema) def create(self, request, *args, **kwargs): diff --git a/connect/api/v2/paginations.py b/connect/api/v2/paginations.py index 9a26b632..2a6f4c25 100644 --- a/connect/api/v2/paginations.py +++ b/connect/api/v2/paginations.py @@ -6,7 +6,5 @@ class CustomCursorPagination(CursorPagination): page_size = 20 def paginate_queryset(self, queryset, request, view=None): - ordering = view.get_ordering() - if ordering: - queryset = queryset.order_by(ordering) + self.ordering = view.get_ordering() return super().paginate_queryset(queryset, request, view) From 0e8fc242ba40e1dfb7594b74591f0f7838069c04 Mon Sep 17 00:00:00 2001 From: Mardone Date: Fri, 15 Dec 2023 14:13:08 -0300 Subject: [PATCH 04/11] Add: Get ordering from any field on project list --- connect/api/v2/projects/views.py | 27 ++++++++++----------------- 1 file changed, 10 insertions(+), 17 deletions(-) diff --git a/connect/api/v2/projects/views.py b/connect/api/v2/projects/views.py index e50fdb25..0476b68d 100644 --- a/connect/api/v2/projects/views.py +++ b/connect/api/v2/projects/views.py @@ -42,23 +42,16 @@ def get_queryset(self, **kwargs): return super().get_queryset().filter(organization__uuid=self.kwargs["organization_uuid"]) def get_ordering(self): - valid_ordering_params = [ - 'last_opened_on', - 'created_at', - 'name', - '-last_opened_on', - '-created_at', - '-name' - ] - ordering = self.request.query_params.get("ordering", 'created_at') - if ordering in valid_ordering_params: - ordering = 'created_at' - else: - return Response( - 'Invalid ordering parameter', - status=status.HTTP_400_BAD_REQUEST - ) - + valid_fields = (org_fields.name for org_fields in Project._meta.get_fields()) + ordering = [] + for param in self.request.query_params.getlist('ordering'): + if param.startswith('-'): + field = param[1:] + else: + field = param + if field in valid_fields: + ordering.append(param) + return ordering if ordering else None @action( detail=True, From 187cadb964776cf72c8fc5d59b5214156b7b3dbe Mon Sep 17 00:00:00 2001 From: Mardone Date: Fri, 15 Dec 2023 14:32:30 -0300 Subject: [PATCH 05/11] Fix: No default ordering --- connect/api/v2/organizations/views.py | 4 +++- connect/api/v2/projects/views.py | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/connect/api/v2/organizations/views.py b/connect/api/v2/organizations/views.py index 5ccc7d84..658d55fc 100644 --- a/connect/api/v2/organizations/views.py +++ b/connect/api/v2/organizations/views.py @@ -66,7 +66,9 @@ def get_ordering(self): field = param if field in valid_fields: ordering.append(param) - return ordering if ordering else None + if not ordering: + ordering.append("created_at") + return ordering @swagger_auto_schema(request_body=create_organization_schema) def create(self, request, *args, **kwargs): diff --git a/connect/api/v2/projects/views.py b/connect/api/v2/projects/views.py index 0476b68d..995f8820 100644 --- a/connect/api/v2/projects/views.py +++ b/connect/api/v2/projects/views.py @@ -51,7 +51,9 @@ def get_ordering(self): field = param if field in valid_fields: ordering.append(param) - return ordering if ordering else None + if not ordering: + ordering.append("created_at") + return ordering @action( detail=True, From da13ef893103d047e92ff5c1a28f5c4771985645 Mon Sep 17 00:00:00 2001 From: Mardone Date: Fri, 15 Dec 2023 15:15:09 -0300 Subject: [PATCH 06/11] Fix: improve the syntax --- connect/api/v2/organizations/views.py | 5 ++--- connect/api/v2/projects/views.py | 4 +--- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/connect/api/v2/organizations/views.py b/connect/api/v2/organizations/views.py index 658d55fc..2580a819 100644 --- a/connect/api/v2/organizations/views.py +++ b/connect/api/v2/organizations/views.py @@ -66,9 +66,8 @@ def get_ordering(self): field = param if field in valid_fields: ordering.append(param) - if not ordering: - ordering.append("created_at") - return ordering + return ordering or ["created_at"] + @swagger_auto_schema(request_body=create_organization_schema) def create(self, request, *args, **kwargs): diff --git a/connect/api/v2/projects/views.py b/connect/api/v2/projects/views.py index 995f8820..3e0115b7 100644 --- a/connect/api/v2/projects/views.py +++ b/connect/api/v2/projects/views.py @@ -51,9 +51,7 @@ def get_ordering(self): field = param if field in valid_fields: ordering.append(param) - if not ordering: - ordering.append("created_at") - return ordering + return ordering or ["created_at"] @action( detail=True, From e5b6f4bcac8fc7ec917f3bd20c88c48f7a0047d7 Mon Sep 17 00:00:00 2001 From: Mardone Date: Fri, 15 Dec 2023 16:48:35 -0300 Subject: [PATCH 07/11] Add: Last opened on filter option --- connect/api/v2/projects/views.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/connect/api/v2/projects/views.py b/connect/api/v2/projects/views.py index 3e0115b7..4bbe4647 100644 --- a/connect/api/v2/projects/views.py +++ b/connect/api/v2/projects/views.py @@ -42,7 +42,9 @@ def get_queryset(self, **kwargs): return super().get_queryset().filter(organization__uuid=self.kwargs["organization_uuid"]) def get_ordering(self): - valid_fields = (org_fields.name for org_fields in Project._meta.get_fields()) + valid_fields = ( + org_fields.name for org_fields in Project._meta.get_fields() + {'last_opened_on'} + ) ordering = [] for param in self.request.query_params.getlist('ordering'): if param.startswith('-'): From 85088a19a17ce6c115572e015466ff7c177068ac Mon Sep 17 00:00:00 2001 From: Mardone Date: Mon, 18 Dec 2023 10:57:52 -0300 Subject: [PATCH 08/11] Add: Opened_project api --- connect/api/v2/projects/serializers.py | 23 ++++++++++++++++ connect/api/v2/projects/views.py | 38 +++++++++++++++++++++++++- connect/api/v2/routers.py | 3 ++ 3 files changed, 63 insertions(+), 1 deletion(-) diff --git a/connect/api/v2/projects/serializers.py b/connect/api/v2/projects/serializers.py index 23eb5deb..6eb96056 100644 --- a/connect/api/v2/projects/serializers.py +++ b/connect/api/v2/projects/serializers.py @@ -620,3 +620,26 @@ def get_pending_rocketchat_role(self, email): if rocket_authorization: return rocket_authorization.role return None + + +class OpenedProjectSerializer(serializers.ModelSerializer): + + class Meta: + model = OpenedProject + fields = [ + "day", + "project", + "user", + ] + + user = serializers.PrimaryKeyRelatedField( + queryset=User.objects, + required=True, + style={"show": False}, + ) + day = serializers.DateTimeField(required=False) + project = serializers.SerializerMethodField() + + def get_project(self, obj): + data = ProjectSerializer(obj.project).data + return data diff --git a/connect/api/v2/projects/views.py b/connect/api/v2/projects/views.py index 4bbe4647..63b5633e 100644 --- a/connect/api/v2/projects/views.py +++ b/connect/api/v2/projects/views.py @@ -15,6 +15,7 @@ ProjectSerializer, ProjectUpdateSerializer, ProjectListAuthorizationSerializer, + OpenedProjectSerializer ) from django.utils import timezone @@ -43,7 +44,7 @@ def get_queryset(self, **kwargs): def get_ordering(self): valid_fields = ( - org_fields.name for org_fields in Project._meta.get_fields() + {'last_opened_on'} + org_fields.name for org_fields in Project._meta.get_fields() ) ordering = [] for param in self.request.query_params.getlist('ordering'): @@ -118,3 +119,38 @@ class ProjectAuthorizationViewSet( serializer_class = ProjectListAuthorizationSerializer permission_classes = [IsAuthenticated, ProjectHasPermission, Has2FA] lookup_field = "uuid" + + +class OpenedProjectViewSet( + mixins.ListModelMixin, + GenericViewSet +): + + queryset = OpenedProject.objects.select_related("project", "user") + serializer_class = OpenedProjectSerializer + permission_classes = [IsAuthenticated, ProjectHasPermission, Has2FA] + lookup_field = "uuid" + pagination_class = CustomCursorPagination + + def get_queryset(self, **kwargs): + if getattr(self, "swagger_fake_view", False): + return OpenedProject.objects.none() # pragma: no cover + + organization__uuid = self.kwargs["organization_uuid"] + projects = Project.objects.filter(organization__uuid=organization__uuid) + + return super().get_queryset().filter(project__in=projects) + + def get_ordering(self): + valid_fields = ( + opened_project.name for opened_project in OpenedProject._meta.get_fields() + ) + ordering = [] + for param in self.request.query_params.getlist('ordering'): + if param.startswith('-'): + field = param[1:] + else: + field = param + if field in valid_fields: + ordering.append(param) + return ordering or ["day"] diff --git a/connect/api/v2/routers.py b/connect/api/v2/routers.py index 5986ae6a..97f11d21 100644 --- a/connect/api/v2/routers.py +++ b/connect/api/v2/routers.py @@ -31,6 +31,9 @@ projects_router.register( "projects", project_views.ProjectViewSet, basename="organization-projects" ) +projects_router.register( + "opened-projects", project_views.OpenedProjectViewSet, basename="opened-projects" +) urlpatterns = [ path("", include(router.urls)), From cd4a5e16566928b948323f69ab2d3a141dd25ae1 Mon Sep 17 00:00:00 2001 From: Mardone Date: Mon, 18 Dec 2023 14:45:41 -0300 Subject: [PATCH 09/11] Add: Distinct openedproject viewset --- connect/api/v2/projects/views.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/connect/api/v2/projects/views.py b/connect/api/v2/projects/views.py index 63b5633e..722c7f6a 100644 --- a/connect/api/v2/projects/views.py +++ b/connect/api/v2/projects/views.py @@ -138,8 +138,8 @@ def get_queryset(self, **kwargs): organization__uuid = self.kwargs["organization_uuid"] projects = Project.objects.filter(organization__uuid=organization__uuid) - - return super().get_queryset().filter(project__in=projects) + opened_projects = super().get_queryset().filter(project__in=projects).order_by('day').distinct('project') + return opened_projects def get_ordering(self): valid_fields = ( From 7812ce6bd717995e2a1c999350e69b117bab3fd4 Mon Sep 17 00:00:00 2001 From: Mardone Date: Mon, 18 Dec 2023 15:32:23 -0300 Subject: [PATCH 10/11] Fix: Distinct missing parameter --- connect/api/v2/projects/views.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/connect/api/v2/projects/views.py b/connect/api/v2/projects/views.py index 722c7f6a..b4570b7c 100644 --- a/connect/api/v2/projects/views.py +++ b/connect/api/v2/projects/views.py @@ -138,8 +138,9 @@ def get_queryset(self, **kwargs): organization__uuid = self.kwargs["organization_uuid"] projects = Project.objects.filter(organization__uuid=organization__uuid) - opened_projects = super().get_queryset().filter(project__in=projects).order_by('day').distinct('project') - return opened_projects + opened_projects = super().get_queryset().filter(project__in=projects).order_by('project__uuid', 'day') + filtered_projects = opened_projects.distinct('project__uuid', 'day') + return filtered_projects def get_ordering(self): valid_fields = ( From b3190e5f8cb065d51bd51d3a3f4912fc176ebd55 Mon Sep 17 00:00:00 2001 From: Mardone Date: Tue, 19 Dec 2023 12:04:46 -0300 Subject: [PATCH 11/11] Fix: Distinct issues on postgres --- connect/api/v2/paginations.py | 7 +++++++ connect/api/v2/projects/serializers.py | 6 +++++- connect/api/v2/projects/views.py | 13 ++++++------- 3 files changed, 18 insertions(+), 8 deletions(-) diff --git a/connect/api/v2/paginations.py b/connect/api/v2/paginations.py index 2a6f4c25..5d214a64 100644 --- a/connect/api/v2/paginations.py +++ b/connect/api/v2/paginations.py @@ -8,3 +8,10 @@ class CustomCursorPagination(CursorPagination): def paginate_queryset(self, queryset, request, view=None): self.ordering = view.get_ordering() return super().paginate_queryset(queryset, request, view) + + +class OpenedProjectCustomCursorPagination(CursorPagination): + def paginate_queryset(self, queryset, request, view=None): + self.ordering = view.get_ordering() + self.queryset = queryset.order_by(*self.ordering).distinct('project') + return super().paginate_queryset(queryset, request, view) diff --git a/connect/api/v2/projects/serializers.py b/connect/api/v2/projects/serializers.py index 6eb96056..79c62da9 100644 --- a/connect/api/v2/projects/serializers.py +++ b/connect/api/v2/projects/serializers.py @@ -641,5 +641,9 @@ class Meta: project = serializers.SerializerMethodField() def get_project(self, obj): - data = ProjectSerializer(obj.project).data + data = ProjectSerializer( + obj.project, + context=self.context + ).data + return data diff --git a/connect/api/v2/projects/views.py b/connect/api/v2/projects/views.py index b4570b7c..5a2b595a 100644 --- a/connect/api/v2/projects/views.py +++ b/connect/api/v2/projects/views.py @@ -19,7 +19,7 @@ ) from django.utils import timezone -from connect.api.v2.paginations import CustomCursorPagination +from connect.api.v2.paginations import CustomCursorPagination, OpenedProjectCustomCursorPagination class ProjectViewSet( @@ -130,7 +130,7 @@ class OpenedProjectViewSet( serializer_class = OpenedProjectSerializer permission_classes = [IsAuthenticated, ProjectHasPermission, Has2FA] lookup_field = "uuid" - pagination_class = CustomCursorPagination + pagination_class = OpenedProjectCustomCursorPagination def get_queryset(self, **kwargs): if getattr(self, "swagger_fake_view", False): @@ -138,15 +138,14 @@ def get_queryset(self, **kwargs): organization__uuid = self.kwargs["organization_uuid"] projects = Project.objects.filter(organization__uuid=organization__uuid) - opened_projects = super().get_queryset().filter(project__in=projects).order_by('project__uuid', 'day') - filtered_projects = opened_projects.distinct('project__uuid', 'day') - return filtered_projects + opened_projects = super().get_queryset().filter(project__in=projects) + return opened_projects def get_ordering(self): valid_fields = ( opened_project.name for opened_project in OpenedProject._meta.get_fields() ) - ordering = [] + ordering = ["project"] for param in self.request.query_params.getlist('ordering'): if param.startswith('-'): field = param[1:] @@ -154,4 +153,4 @@ def get_ordering(self): field = param if field in valid_fields: ordering.append(param) - return ordering or ["day"] + return ordering or ["project", "day"]