From 08734753bc6b8f6dd6210c5a78b84ce55c0c3990 Mon Sep 17 00:00:00 2001 From: pablohashescobar Date: Tue, 2 Dec 2025 19:28:04 +0530 Subject: [PATCH 1/3] chore(deps): upgrade psycopg packages to version 3.3.0 --- apps/api/requirements/base.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/api/requirements/base.txt b/apps/api/requirements/base.txt index 8454ad09ecb..07afa1bb9f3 100644 --- a/apps/api/requirements/base.txt +++ b/apps/api/requirements/base.txt @@ -5,9 +5,9 @@ Django==4.2.26 # 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 From 03c0918f91ab61f9c36b8f1ed19219a22c712b21 Mon Sep 17 00:00:00 2001 From: pablohashescobar Date: Tue, 2 Dec 2025 19:33:55 +0530 Subject: [PATCH 2/3] chore: update Python version to 3.12.x in CI workflow --- .github/workflows/pull-request-build-lint-api.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pull-request-build-lint-api.yml b/.github/workflows/pull-request-build-lint-api.yml index 50d105ef56d..11612207b1a 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 From dac5b7cac717d9922ea424ff3705cdd1fc00781c Mon Sep 17 00:00:00 2001 From: pablohashescobar Date: Tue, 2 Dec 2025 19:38:15 +0530 Subject: [PATCH 3/3] refactor: clean up imports and improve code formatting across multiple files --- apps/api/plane/api/urls/member.py | 6 +- apps/api/plane/api/views/base.py | 2 - apps/api/plane/api/views/module.py | 2 - apps/api/plane/app/views/search/base.py | 89 ++++++------------- apps/api/plane/app/views/user/base.py | 2 +- apps/api/plane/bgtasks/export_task.py | 1 - .../plane/tests/contract/api/test_cycles.py | 24 +++-- 7 files changed, 43 insertions(+), 83 deletions(-) diff --git a/apps/api/plane/api/urls/member.py b/apps/api/plane/api/urls/member.py index a33d8bbe353..83c9dfbe507 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 f17ae2e3281..2e658443018 100644 --- a/apps/api/plane/api/views/base.py +++ b/apps/api/plane/api/views/base.py @@ -13,8 +13,6 @@ 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 7e47c99eb25..a4e0f3fe820 100644 --- a/apps/api/plane/api/views/module.py +++ b/apps/api/plane/api/views/module.py @@ -65,9 +65,7 @@ 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 5309bff554e..f1e69265323 100644 --- a/apps/api/plane/app/views/search/base.py +++ b/apps/api/plane/app/views/search/base.py @@ -129,9 +129,7 @@ def filter_cycles(self, query, slug, project_id, workspace_search): 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 @@ def filter_modules(self, query, slug, project_id, workspace_search): 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 @@ def filter_pages(self, query, slug, project_id, workspace_search): ) .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 @@ def filter_pages(self, query, slug, project_id, workspace_search): ) 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 @@ def filter_views(self, query, slug, project_id, workspace_search): 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 @@ def get(self, request, slug): # 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 @@ def get(self, request, slug): 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 @@ def get(self, request, slug): 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 @@ def get(self, request, slug): .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 @@ def get(self, request, slug): 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 @@ def get(self, request, slug): .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 @@ def get(self, request, slug): ) ) .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 @@ def get(self, request, slug): 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 @@ def get(self, request, slug): .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 30b0391838e..72d42010ced 100644 --- a/apps/api/plane/app/views/user/base.py +++ b/apps/api/plane/app/views/user/base.py @@ -210,7 +210,7 @@ def update_email(self, request): 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 d8aad5f69c8..75b5f22659e 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 fb4ad3f3356..644fe2bef9b 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 @@ def test_create_cycle_success(self, api_key_client, workspace, project, cycle_da 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 @@ def test_list_cycles_with_view_filter(self, api_key_client, workspace, project, # Create cycles in different states now = timezone.now() - + # Current cycle (started but not ended) Cycle.objects.create( name="Current Cycle", @@ -207,7 +203,7 @@ def test_list_cycles_with_view_filter(self, api_key_client, workspace, project, end_date=now + timedelta(days=6), owned_by=create_user, ) - + # Upcoming cycle Cycle.objects.create( name="Upcoming Cycle", @@ -217,7 +213,7 @@ def test_list_cycles_with_view_filter(self, api_key_client, workspace, project, end_date=now + timedelta(days=8), owned_by=create_user, ) - + # Completed cycle Cycle.objects.create( name="Completed Cycle", @@ -227,7 +223,7 @@ def test_list_cycles_with_view_filter(self, api_key_client, workspace, project, end_date=now - timedelta(days=3), owned_by=create_user, ) - + # Draft cycle Cycle.objects.create( name="Draft Cycle", @@ -320,7 +316,9 @@ def test_update_cycle_invalid_data(self, api_key_client, workspace, project, cre 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 @@ def test_cycle_metrics_annotation(self, api_key_client, workspace, project, crea 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 @@ def test_cycle_metrics_annotation(self, api_key_client, workspace, project, crea 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