diff --git a/.github/workflows/pull-request-build-lint-api.yml b/.github/workflows/pull-request-build-lint-api.yml index 50d105ef56..11612207b1 100644 --- a/.github/workflows/pull-request-build-lint-api.yml +++ b/.github/workflows/pull-request-build-lint-api.yml @@ -31,7 +31,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v5 with: - python-version: "3.x" + python-version: "3.12.x" - name: Install Pylint run: python -m pip install ruff - name: Install API Dependencies diff --git a/apps/api/plane/api/urls/member.py b/apps/api/plane/api/urls/member.py index a33d8bbe35..83c9dfbe50 100644 --- a/apps/api/plane/api/urls/member.py +++ b/apps/api/plane/api/urls/member.py @@ -1,6 +1,10 @@ from django.urls import path -from plane.api.views import ProjectMemberListCreateAPIEndpoint, ProjectMemberDetailAPIEndpoint, WorkspaceMemberAPIEndpoint +from plane.api.views import ( + ProjectMemberListCreateAPIEndpoint, + ProjectMemberDetailAPIEndpoint, + WorkspaceMemberAPIEndpoint, +) urlpatterns = [ # Project members diff --git a/apps/api/plane/api/views/base.py b/apps/api/plane/api/views/base.py index f17ae2e328..2e65844301 100644 --- a/apps/api/plane/api/views/base.py +++ b/apps/api/plane/api/views/base.py @@ -13,8 +13,6 @@ from django.utils import timezone from rest_framework import status from rest_framework.permissions import IsAuthenticated from rest_framework.response import Response -from django_filters.rest_framework import DjangoFilterBackend -from rest_framework.filters import SearchFilter from rest_framework.viewsets import ModelViewSet from rest_framework.exceptions import APIException from rest_framework.generics import GenericAPIView diff --git a/apps/api/plane/api/views/module.py b/apps/api/plane/api/views/module.py index 7e47c99eb2..a4e0f3fe82 100644 --- a/apps/api/plane/api/views/module.py +++ b/apps/api/plane/api/views/module.py @@ -65,9 +65,7 @@ from plane.utils.openapi import ( ADMIN_ONLY_RESPONSE, REQUIRED_FIELDS_RESPONSE, MODULE_ISSUE_NOT_FOUND_RESPONSE, - ARCHIVED_RESPONSE, CANNOT_ARCHIVE_RESPONSE, - UNARCHIVED_RESPONSE, ) diff --git a/apps/api/plane/app/views/search/base.py b/apps/api/plane/app/views/search/base.py index 5309bff554..f1e6926532 100644 --- a/apps/api/plane/app/views/search/base.py +++ b/apps/api/plane/app/views/search/base.py @@ -129,9 +129,7 @@ class GlobalSearchEndpoint(BaseAPIView): return ( cycles.order_by("-created_at") .distinct() - .values( - "name", "id", "project_id", "project__identifier", "workspace__slug" - ) + .values("name", "id", "project_id", "project__identifier", "workspace__slug") ) def filter_modules(self, query, slug, project_id, workspace_search): @@ -155,9 +153,7 @@ class GlobalSearchEndpoint(BaseAPIView): return ( modules.order_by("-created_at") .distinct() - .values( - "name", "id", "project_id", "project__identifier", "workspace__slug" - ) + .values("name", "id", "project_id", "project__identifier", "workspace__slug") ) def filter_pages(self, query, slug, project_id, workspace_search): @@ -177,9 +173,7 @@ class GlobalSearchEndpoint(BaseAPIView): ) .annotate( project_ids=Coalesce( - ArrayAgg( - "projects__id", distinct=True, filter=~Q(projects__id=True) - ), + ArrayAgg("projects__id", distinct=True, filter=~Q(projects__id=True)), Value([], output_field=ArrayField(UUIDField())), ) ) @@ -196,20 +190,16 @@ class GlobalSearchEndpoint(BaseAPIView): ) if workspace_search == "false" and project_id: - project_subquery = ProjectPage.objects.filter( - page_id=OuterRef("id"), project_id=project_id - ).values_list("project_id", flat=True)[:1] + project_subquery = ProjectPage.objects.filter(page_id=OuterRef("id"), project_id=project_id).values_list( + "project_id", flat=True + )[:1] - pages = pages.annotate(project_id=Subquery(project_subquery)).filter( - project_id=project_id - ) + pages = pages.annotate(project_id=Subquery(project_subquery)).filter(project_id=project_id) return ( pages.order_by("-created_at") .distinct() - .values( - "name", "id", "project_ids", "project_identifiers", "workspace__slug" - ) + .values("name", "id", "project_ids", "project_identifiers", "workspace__slug") ) def filter_views(self, query, slug, project_id, workspace_search): @@ -233,9 +223,7 @@ class GlobalSearchEndpoint(BaseAPIView): return ( issue_views.order_by("-created_at") .distinct() - .values( - "name", "id", "project_id", "project__identifier", "workspace__slug" - ) + .values("name", "id", "project_id", "project__identifier", "workspace__slug") ) def filter_intakes(self, query, slug, project_id, workspace_search): @@ -294,9 +282,7 @@ class GlobalSearchEndpoint(BaseAPIView): # Determine which entities to search if entities_param: - requested_entities = [ - e.strip() for e in entities_param.split(",") if e.strip() - ] + requested_entities = [e.strip() for e in entities_param.split(",") if e.strip()] requested_entities = [e for e in requested_entities if e in MODELS_MAPPER] else: requested_entities = list(MODELS_MAPPER.keys()) @@ -306,9 +292,7 @@ class GlobalSearchEndpoint(BaseAPIView): for entity in requested_entities: func = MODELS_MAPPER.get(entity) if func: - results[entity] = func( - query or None, slug, project_id, workspace_search - ) + results[entity] = func(query or None, slug, project_id, workspace_search) return Response({"results": results}, status=status.HTTP_200_OK) @@ -320,7 +304,6 @@ class SearchEndpoint(BaseAPIView): query_types = [qt.strip() for qt in query_types] count = int(request.query_params.get("count", 5)) project_id = request.query_params.get("project_id", None) - issue_id = request.query_params.get("issue_id", None) response_data = {} @@ -367,14 +350,10 @@ class SearchEndpoint(BaseAPIView): .order_by("-created_at") ) - users = ( - users - .distinct() - .values( - "member__avatar_url", - "member__display_name", - "member__id", - ) + users = users.distinct().values( + "member__avatar_url", + "member__display_name", + "member__id", ) response_data["user_mention"] = list(users[:count]) @@ -389,15 +368,12 @@ class SearchEndpoint(BaseAPIView): projects = ( Project.objects.filter( q, - Q(project_projectmember__member=self.request.user) - | Q(network=2), + Q(project_projectmember__member=self.request.user) | Q(network=2), workspace__slug=slug, ) .order_by("-created_at") .distinct() - .values( - "name", "id", "identifier", "logo_props", "workspace__slug" - )[:count] + .values("name", "id", "identifier", "logo_props", "workspace__slug")[:count] ) response_data["project"] = list(projects) @@ -456,20 +432,16 @@ class SearchEndpoint(BaseAPIView): .annotate( status=Case( When( - Q(start_date__lte=timezone.now()) - & Q(end_date__gte=timezone.now()), + Q(start_date__lte=timezone.now()) & Q(end_date__gte=timezone.now()), then=Value("CURRENT"), ), When( start_date__gt=timezone.now(), then=Value("UPCOMING"), ), + When(end_date__lt=timezone.now(), then=Value("COMPLETED")), When( - end_date__lt=timezone.now(), then=Value("COMPLETED") - ), - When( - Q(start_date__isnull=True) - & Q(end_date__isnull=True), + Q(start_date__isnull=True) & Q(end_date__isnull=True), then=Value("DRAFT"), ), default=Value("DRAFT"), @@ -587,9 +559,7 @@ class SearchEndpoint(BaseAPIView): ) ) .order_by("-created_at") - .values( - "member__avatar_url", "member__display_name", "member__id" - )[:count] + .values("member__avatar_url", "member__display_name", "member__id")[:count] ) response_data["user_mention"] = list(users) @@ -603,15 +573,12 @@ class SearchEndpoint(BaseAPIView): projects = ( Project.objects.filter( q, - Q(project_projectmember__member=self.request.user) - | Q(network=2), + Q(project_projectmember__member=self.request.user) | Q(network=2), workspace__slug=slug, ) .order_by("-created_at") .distinct() - .values( - "name", "id", "identifier", "logo_props", "workspace__slug" - )[:count] + .values("name", "id", "identifier", "logo_props", "workspace__slug")[:count] ) response_data["project"] = list(projects) @@ -668,20 +635,16 @@ class SearchEndpoint(BaseAPIView): .annotate( status=Case( When( - Q(start_date__lte=timezone.now()) - & Q(end_date__gte=timezone.now()), + Q(start_date__lte=timezone.now()) & Q(end_date__gte=timezone.now()), then=Value("CURRENT"), ), When( start_date__gt=timezone.now(), then=Value("UPCOMING"), ), + When(end_date__lt=timezone.now(), then=Value("COMPLETED")), When( - end_date__lt=timezone.now(), then=Value("COMPLETED") - ), - When( - Q(start_date__isnull=True) - & Q(end_date__isnull=True), + Q(start_date__isnull=True) & Q(end_date__isnull=True), then=Value("DRAFT"), ), default=Value("DRAFT"), diff --git a/apps/api/plane/app/views/user/base.py b/apps/api/plane/app/views/user/base.py index 30b0391838..72d42010ce 100644 --- a/apps/api/plane/app/views/user/base.py +++ b/apps/api/plane/app/views/user/base.py @@ -210,7 +210,7 @@ class UserEndpoint(BaseViewSet): status=status.HTTP_400_BAD_REQUEST, ) - except Exception as e: + except Exception: return Response( {"error": "Failed to verify code. Please try again."}, status=status.HTTP_400_BAD_REQUEST, diff --git a/apps/api/plane/bgtasks/export_task.py b/apps/api/plane/bgtasks/export_task.py index d8aad5f69c..75b5f22659 100644 --- a/apps/api/plane/bgtasks/export_task.py +++ b/apps/api/plane/bgtasks/export_task.py @@ -2,7 +2,6 @@ import io import zipfile from typing import List -from collections import defaultdict import boto3 from botocore.client import Config from uuid import UUID diff --git a/apps/api/plane/tests/contract/api/test_cycles.py b/apps/api/plane/tests/contract/api/test_cycles.py index fb4ad3f335..644fe2bef9 100644 --- a/apps/api/plane/tests/contract/api/test_cycles.py +++ b/apps/api/plane/tests/contract/api/test_cycles.py @@ -1,8 +1,7 @@ import pytest from rest_framework import status -from django.db import IntegrityError from django.utils import timezone -from datetime import datetime, timedelta +from datetime import timedelta from uuid import uuid4 from plane.db.models import Cycle, Project, ProjectMember @@ -58,8 +57,6 @@ def create_cycle(db, project, create_user): ) - - @pytest.mark.contract class TestCycleListCreateAPIEndpoint: """Test Cycle List and Create API Endpoint""" @@ -85,7 +82,6 @@ class TestCycleListCreateAPIEndpoint: assert created_cycle.project == project assert created_cycle.owned_by_id is not None - @pytest.mark.django_db def test_create_cycle_invalid_data(self, api_key_client, workspace, project): """Test cycle creation with invalid data""" @@ -197,7 +193,7 @@ class TestCycleListCreateAPIEndpoint: # Create cycles in different states now = timezone.now() - + # Current cycle (started but not ended) Cycle.objects.create( name="Current Cycle", @@ -207,7 +203,7 @@ class TestCycleListCreateAPIEndpoint: end_date=now + timedelta(days=6), owned_by=create_user, ) - + # Upcoming cycle Cycle.objects.create( name="Upcoming Cycle", @@ -217,7 +213,7 @@ class TestCycleListCreateAPIEndpoint: end_date=now + timedelta(days=8), owned_by=create_user, ) - + # Completed cycle Cycle.objects.create( name="Completed Cycle", @@ -227,7 +223,7 @@ class TestCycleListCreateAPIEndpoint: end_date=now - timedelta(days=3), owned_by=create_user, ) - + # Draft cycle Cycle.objects.create( name="Draft Cycle", @@ -320,7 +316,9 @@ class TestCycleDetailAPIEndpoint: assert response.status_code in [status.HTTP_400_BAD_REQUEST, status.HTTP_200_OK] @pytest.mark.django_db - def test_update_cycle_with_external_id_conflict(self, api_key_client, workspace, project, create_cycle, create_user ): + def test_update_cycle_with_external_id_conflict( + self, api_key_client, workspace, project, create_cycle, create_user + ): """Test cycle update with conflicting external ID""" url = self.get_cycle_detail_url(workspace.slug, project.id, create_cycle.id) @@ -363,7 +361,7 @@ class TestCycleDetailAPIEndpoint: response = api_key_client.get(url) assert response.status_code == status.HTTP_200_OK - + # Check that metrics are included in response cycle_data = response.data assert "total_issues" in cycle_data @@ -372,11 +370,11 @@ class TestCycleDetailAPIEndpoint: assert "started_issues" in cycle_data assert "unstarted_issues" in cycle_data assert "backlog_issues" in cycle_data - + # All should be 0 for a new cycle assert cycle_data["total_issues"] == 0 assert cycle_data["completed_issues"] == 0 assert cycle_data["cancelled_issues"] == 0 assert cycle_data["started_issues"] == 0 assert cycle_data["unstarted_issues"] == 0 - assert cycle_data["backlog_issues"] == 0 \ No newline at end of file + assert cycle_data["backlog_issues"] == 0 diff --git a/apps/api/requirements/base.txt b/apps/api/requirements/base.txt index 8e880f088c..7f2c64be47 100644 --- a/apps/api/requirements/base.txt +++ b/apps/api/requirements/base.txt @@ -5,9 +5,9 @@ Django==4.2.27 # rest framework djangorestframework==3.15.2 # postgres -psycopg==3.2.9 -psycopg-binary==3.2.9 -psycopg-c==3.2.9 +psycopg==3.3.0 +psycopg-binary==3.3.0 +psycopg-c==3.3.0 dj-database-url==2.1.0 # mongo pymongo==4.6.3