From 736c4e2ff9f40a344b9fc161a8a9b5877fb1b849 Mon Sep 17 00:00:00 2001 From: sriramveeraghanta Date: Wed, 10 Dec 2025 15:59:23 +0530 Subject: [PATCH] chore: format files in API server --- apps/api/plane/api/serializers/__init__.py | 2 +- apps/api/plane/api/serializers/project.py | 2 +- apps/api/plane/api/urls/invite.py | 2 +- apps/api/plane/api/views/cycle.py | 97 ++++--------- apps/api/plane/api/views/member.py | 1 - apps/api/plane/app/urls/exporter.py | 2 +- apps/api/plane/app/views/asset/v2.py | 99 ++++--------- apps/api/plane/app/views/cycle/base.py | 130 +++++------------- apps/api/plane/app/views/issue/label.py | 4 +- apps/api/plane/app/views/page/base.py | 28 ++-- apps/api/plane/authentication/adapter/base.py | 6 +- .../authentication/provider/oauth/gitea.py | 6 +- .../plane/authentication/views/app/gitea.py | 12 +- .../plane/bgtasks/page_transaction_task.py | 6 +- apps/api/plane/bgtasks/workspace_seed_task.py | 100 +++++++------- apps/api/plane/license/api/views/admin.py | 6 +- .../tests/unit/serializers/test_label.py | 12 +- apps/api/plane/utils/content_validator.py | 4 +- apps/api/plane/utils/cycle_transfer_issues.py | 24 +--- apps/api/plane/utils/openapi/decorators.py | 3 +- 20 files changed, 173 insertions(+), 373 deletions(-) diff --git a/apps/api/plane/api/serializers/__init__.py b/apps/api/plane/api/serializers/__init__.py index 6525ddce633..550d8e87f49 100644 --- a/apps/api/plane/api/serializers/__init__.py +++ b/apps/api/plane/api/serializers/__init__.py @@ -55,4 +55,4 @@ ) from .invite import WorkspaceInviteSerializer from .member import ProjectMemberSerializer -from .sticky import StickySerializer \ No newline at end of file +from .sticky import StickySerializer diff --git a/apps/api/plane/api/serializers/project.py b/apps/api/plane/api/serializers/project.py index 770957e08c8..b22438ad513 100644 --- a/apps/api/plane/api/serializers/project.py +++ b/apps/api/plane/api/serializers/project.py @@ -17,7 +17,7 @@ from .base import BaseSerializer -class ProjectCreateSerializer(BaseSerializer): +class ProjectCreateSerializer(BaseSerializer): """ Serializer for creating projects with workspace validation. diff --git a/apps/api/plane/api/urls/invite.py b/apps/api/plane/api/urls/invite.py index 9d73cb6ef80..e4fddd5c507 100644 --- a/apps/api/plane/api/urls/invite.py +++ b/apps/api/plane/api/urls/invite.py @@ -15,4 +15,4 @@ # Wrap the router URLs with the workspace slug path urlpatterns = [ path("workspaces//", include(router.urls)), -] \ No newline at end of file +] diff --git a/apps/api/plane/api/views/cycle.py b/apps/api/plane/api/views/cycle.py index c92b27f5912..170c644f33b 100644 --- a/apps/api/plane/api/views/cycle.py +++ b/apps/api/plane/api/views/cycle.py @@ -195,9 +195,7 @@ def get(self, request, slug, project_id): # Current Cycle if cycle_view == "current": - queryset = queryset.filter( - start_date__lte=timezone.now(), end_date__gte=timezone.now() - ) + queryset = queryset.filter(start_date__lte=timezone.now(), end_date__gte=timezone.now()) data = CycleSerializer( queryset, many=True, @@ -254,9 +252,7 @@ def get(self, request, slug, project_id): # Incomplete Cycles if cycle_view == "incomplete": - queryset = queryset.filter( - Q(end_date__gte=timezone.now()) | Q(end_date__isnull=True) - ) + queryset = queryset.filter(Q(end_date__gte=timezone.now()) | Q(end_date__isnull=True)) return self.paginate( request=request, queryset=(queryset), @@ -302,17 +298,10 @@ def post(self, request, slug, project_id): Create a new development cycle with specified name, description, and date range. Supports external ID tracking for integration purposes. """ - if ( - request.data.get("start_date", None) is None - and request.data.get("end_date", None) is None - ) or ( - request.data.get("start_date", None) is not None - and request.data.get("end_date", None) is not None + if (request.data.get("start_date", None) is None and request.data.get("end_date", None) is None) or ( + request.data.get("start_date", None) is not None and request.data.get("end_date", None) is not None ): - - serializer = CycleCreateSerializer( - data=request.data, context={"request": request} - ) + serializer = CycleCreateSerializer(data=request.data, context={"request": request}) if serializer.is_valid(): if ( request.data.get("external_id") @@ -355,9 +344,7 @@ def post(self, request, slug, project_id): return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) else: return Response( - { - "error": "Both start date and end date are either required or are to be null" - }, + {"error": "Both start date and end date are either required or are to be null"}, status=status.HTTP_400_BAD_REQUEST, ) @@ -505,9 +492,7 @@ def patch(self, request, slug, project_id, pk): """ cycle = Cycle.objects.get(workspace__slug=slug, project_id=project_id, pk=pk) - current_instance = json.dumps( - CycleSerializer(cycle).data, cls=DjangoJSONEncoder - ) + current_instance = json.dumps(CycleSerializer(cycle).data, cls=DjangoJSONEncoder) if cycle.archived_at: return Response( @@ -520,20 +505,14 @@ def patch(self, request, slug, project_id, pk): if cycle.end_date is not None and cycle.end_date < timezone.now(): if "sort_order" in request_data: # Can only change sort order - request_data = { - "sort_order": request_data.get("sort_order", cycle.sort_order) - } + request_data = {"sort_order": request_data.get("sort_order", cycle.sort_order)} else: return Response( - { - "error": "The Cycle has already been completed so it cannot be edited" - }, + {"error": "The Cycle has already been completed so it cannot be edited"}, status=status.HTTP_400_BAD_REQUEST, ) - serializer = CycleUpdateSerializer( - cycle, data=request.data, partial=True, context={"request": request} - ) + serializer = CycleUpdateSerializer(cycle, data=request.data, partial=True, context={"request": request}) if serializer.is_valid(): if ( request.data.get("external_id") @@ -541,9 +520,7 @@ def patch(self, request, slug, project_id, pk): and Cycle.objects.filter( project_id=project_id, workspace__slug=slug, - external_source=request.data.get( - "external_source", cycle.external_source - ), + external_source=request.data.get("external_source", cycle.external_source), external_id=request.data.get("external_id"), ).exists() ): @@ -600,11 +577,7 @@ def delete(self, request, slug, project_id, pk): status=status.HTTP_403_FORBIDDEN, ) - cycle_issues = list( - CycleIssue.objects.filter(cycle_id=self.kwargs.get("pk")).values_list( - "issue", flat=True - ) - ) + cycle_issues = list(CycleIssue.objects.filter(cycle_id=self.kwargs.get("pk")).values_list("issue", flat=True)) issue_activity.delay( type="cycle.activity.deleted", @@ -624,9 +597,7 @@ def delete(self, request, slug, project_id, pk): # Delete the cycle cycle.delete() # Delete the user favorite cycle - UserFavorite.objects.filter( - entity_type="cycle", entity_identifier=pk, project_id=project_id - ).delete() + UserFavorite.objects.filter(entity_type="cycle", entity_identifier=pk, project_id=project_id).delete() return Response(status=status.HTTP_204_NO_CONTENT) @@ -764,9 +735,7 @@ def get(self, request, slug, project_id): return self.paginate( request=request, queryset=(self.get_queryset()), - on_results=lambda cycles: CycleSerializer( - cycles, many=True, fields=self.fields, expand=self.expand - ).data, + on_results=lambda cycles: CycleSerializer(cycles, many=True, fields=self.fields, expand=self.expand).data, ) @cycle_docs( @@ -785,9 +754,7 @@ def post(self, request, slug, project_id, cycle_id): Move a completed cycle to archived status for historical tracking. Only cycles that have ended can be archived. """ - cycle = Cycle.objects.get( - pk=cycle_id, project_id=project_id, workspace__slug=slug - ) + cycle = Cycle.objects.get(pk=cycle_id, project_id=project_id, workspace__slug=slug) if cycle.end_date >= timezone.now(): return Response( {"error": "Only completed cycles can be archived"}, @@ -818,9 +785,7 @@ def delete(self, request, slug, project_id, cycle_id): Restore an archived cycle to active status, making it available for regular use. The cycle will reappear in active cycle lists. """ - cycle = Cycle.objects.get( - pk=cycle_id, project_id=project_id, workspace__slug=slug - ) + cycle = Cycle.objects.get(pk=cycle_id, project_id=project_id, workspace__slug=slug) cycle.archived_at = None cycle.save() return Response(status=status.HTTP_204_NO_CONTENT) @@ -883,9 +848,7 @@ def get(self, request, slug, project_id, cycle_id): # List order_by = request.GET.get("order_by", "created_at") issues = ( - Issue.issue_objects.filter( - issue_cycle__cycle_id=cycle_id, issue_cycle__deleted_at__isnull=True - ) + Issue.issue_objects.filter(issue_cycle__cycle_id=cycle_id, issue_cycle__deleted_at__isnull=True) .annotate( sub_issues_count=Issue.issue_objects.filter(parent=OuterRef("id")) .order_by() @@ -922,9 +885,7 @@ def get(self, request, slug, project_id, cycle_id): return self.paginate( request=request, queryset=(issues), - on_results=lambda issues: IssueSerializer( - issues, many=True, fields=self.fields, expand=self.expand - ).data, + on_results=lambda issues: IssueSerializer(issues, many=True, fields=self.fields, expand=self.expand).data, ) @cycle_docs( @@ -958,9 +919,7 @@ def post(self, request, slug, project_id, cycle_id): status=status.HTTP_400_BAD_REQUEST, ) - cycle = Cycle.objects.get( - workspace__slug=slug, project_id=project_id, pk=cycle_id - ) + cycle = Cycle.objects.get(workspace__slug=slug, project_id=project_id, pk=cycle_id) if cycle.end_date is not None and cycle.end_date < timezone.now(): return Response( @@ -972,13 +931,9 @@ def post(self, request, slug, project_id, cycle_id): ) # Get all CycleWorkItems already created - cycle_issues = list( - CycleIssue.objects.filter(~Q(cycle_id=cycle_id), issue_id__in=issues) - ) + cycle_issues = list(CycleIssue.objects.filter(~Q(cycle_id=cycle_id), issue_id__in=issues)) existing_issues = [ - str(cycle_issue.issue_id) - for cycle_issue in cycle_issues - if str(cycle_issue.issue_id) in issues + str(cycle_issue.issue_id) for cycle_issue in cycle_issues if str(cycle_issue.issue_id) in issues ] new_issues = list(set(issues) - set(existing_issues)) @@ -1029,9 +984,7 @@ def post(self, request, slug, project_id, cycle_id): current_instance=json.dumps( { "updated_cycle_issues": update_cycle_issue_activity, - "created_cycle_issues": serializers.serialize( - "json", created_records - ), + "created_cycle_issues": serializers.serialize("json", created_records), } ), epoch=int(timezone.now().timestamp()), @@ -1107,9 +1060,7 @@ def get(self, request, slug, project_id, cycle_id, issue_id): cycle_id=cycle_id, issue_id=issue_id, ) - serializer = CycleIssueSerializer( - cycle_issue, fields=self.fields, expand=self.expand - ) + serializer = CycleIssueSerializer(cycle_issue, fields=self.fields, expand=self.expand) return Response(serializer.data, status=status.HTTP_200_OK) @cycle_docs( @@ -1214,7 +1165,7 @@ def post(self, request, slug, project_id, cycle_id): {"error": "New Cycle Id is required"}, status=status.HTTP_400_BAD_REQUEST, ) - + old_cycle = Cycle.objects.get( workspace__slug=slug, project_id=project_id, diff --git a/apps/api/plane/api/views/member.py b/apps/api/plane/api/views/member.py index 854bc7ae67e..a569a597699 100644 --- a/apps/api/plane/api/views/member.py +++ b/apps/api/plane/api/views/member.py @@ -154,7 +154,6 @@ def post(self, request, slug, project_id): # API endpoint to get and update a project member class ProjectMemberDetailAPIEndpoint(ProjectMemberListCreateAPIEndpoint): - @extend_schema( operation_id="get_project_member", summary="Get project member", diff --git a/apps/api/plane/app/urls/exporter.py b/apps/api/plane/app/urls/exporter.py index 0bcb4621b24..56c6bfb51a6 100644 --- a/apps/api/plane/app/urls/exporter.py +++ b/apps/api/plane/app/urls/exporter.py @@ -9,4 +9,4 @@ ExportIssuesEndpoint.as_view(), name="export-issues", ), -] \ No newline at end of file +] diff --git a/apps/api/plane/app/views/asset/v2.py b/apps/api/plane/app/views/asset/v2.py index b8b27eeae0a..4313fe3fff9 100644 --- a/apps/api/plane/app/views/asset/v2.py +++ b/apps/api/plane/app/views/asset/v2.py @@ -45,9 +45,7 @@ def entity_asset_save(self, asset_id, entity_type, asset, request): # Save the new avatar user.avatar_asset_id = asset_id user.save() - invalidate_cache_directly( - path="/api/users/me/", url_params=False, user=True, request=request - ) + invalidate_cache_directly(path="/api/users/me/", url_params=False, user=True, request=request) invalidate_cache_directly( path="/api/users/me/settings/", url_params=False, @@ -65,9 +63,7 @@ def entity_asset_save(self, asset_id, entity_type, asset, request): # Save the new cover image user.cover_image_asset_id = asset_id user.save() - invalidate_cache_directly( - path="/api/users/me/", url_params=False, user=True, request=request - ) + invalidate_cache_directly(path="/api/users/me/", url_params=False, user=True, request=request) invalidate_cache_directly( path="/api/users/me/settings/", url_params=False, @@ -83,9 +79,7 @@ def entity_asset_delete(self, entity_type, asset, request): user = User.objects.get(id=asset.user_id) user.avatar_asset_id = None user.save() - invalidate_cache_directly( - path="/api/users/me/", url_params=False, user=True, request=request - ) + invalidate_cache_directly(path="/api/users/me/", url_params=False, user=True, request=request) invalidate_cache_directly( path="/api/users/me/settings/", url_params=False, @@ -98,9 +92,7 @@ def entity_asset_delete(self, entity_type, asset, request): user = User.objects.get(id=asset.user_id) user.cover_image_asset_id = None user.save() - invalidate_cache_directly( - path="/api/users/me/", url_params=False, user=True, request=request - ) + invalidate_cache_directly(path="/api/users/me/", url_params=False, user=True, request=request) invalidate_cache_directly( path="/api/users/me/settings/", url_params=False, @@ -160,9 +152,7 @@ def post(self, request): # Get the presigned URL storage = S3Storage(request=request) # Generate a presigned URL to share an S3 object - presigned_url = storage.generate_presigned_post( - object_name=asset_key, file_type=type, file_size=size_limit - ) + presigned_url = storage.generate_presigned_post(object_name=asset_key, file_type=type, file_size=size_limit) # Return the presigned URL return Response( { @@ -199,9 +189,7 @@ def delete(self, request, asset_id): asset.is_deleted = True asset.deleted_at = timezone.now() # get the entity and save the asset id for the request field - self.entity_asset_delete( - entity_type=asset.entity_type, asset=asset, request=request - ) + self.entity_asset_delete(entity_type=asset.entity_type, asset=asset, request=request) asset.save(update_fields=["is_deleted", "deleted_at"]) return Response(status=status.HTTP_204_NO_CONTENT) @@ -265,18 +253,14 @@ def entity_asset_save(self, asset_id, entity_type, asset, request): workspace.logo = "" workspace.logo_asset_id = asset_id workspace.save() - invalidate_cache_directly( - path="/api/workspaces/", url_params=False, user=False, request=request - ) + invalidate_cache_directly(path="/api/workspaces/", url_params=False, user=False, request=request) invalidate_cache_directly( path="/api/users/me/workspaces/", url_params=False, user=True, request=request, ) - invalidate_cache_directly( - path="/api/instances/", url_params=False, user=False, request=request - ) + invalidate_cache_directly(path="/api/instances/", url_params=False, user=False, request=request) return # Project Cover @@ -303,18 +287,14 @@ def entity_asset_delete(self, entity_type, asset, request): return workspace.logo_asset_id = None workspace.save() - invalidate_cache_directly( - path="/api/workspaces/", url_params=False, user=False, request=request - ) + invalidate_cache_directly(path="/api/workspaces/", url_params=False, user=False, request=request) invalidate_cache_directly( path="/api/users/me/workspaces/", url_params=False, user=True, request=request, ) - invalidate_cache_directly( - path="/api/instances/", url_params=False, user=False, request=request - ) + invalidate_cache_directly(path="/api/instances/", url_params=False, user=False, request=request) return # Project Cover elif entity_type == FileAsset.EntityTypeContext.PROJECT_COVER: @@ -375,17 +355,13 @@ def post(self, request, slug): workspace=workspace, created_by=request.user, entity_type=entity_type, - **self.get_entity_id_field( - entity_type=entity_type, entity_id=entity_identifier - ), + **self.get_entity_id_field(entity_type=entity_type, entity_id=entity_identifier), ) # Get the presigned URL storage = S3Storage(request=request) # Generate a presigned URL to share an S3 object - presigned_url = storage.generate_presigned_post( - object_name=asset_key, file_type=type, file_size=size_limit - ) + presigned_url = storage.generate_presigned_post(object_name=asset_key, file_type=type, file_size=size_limit) # Return the presigned URL return Response( { @@ -422,9 +398,7 @@ def delete(self, request, slug, asset_id): asset.is_deleted = True asset.deleted_at = timezone.now() # get the entity and save the asset id for the request field - self.entity_asset_delete( - entity_type=asset.entity_type, asset=asset, request=request - ) + self.entity_asset_delete(entity_type=asset.entity_type, asset=asset, request=request) asset.save(update_fields=["is_deleted", "deleted_at"]) return Response(status=status.HTTP_204_NO_CONTENT) @@ -587,9 +561,7 @@ def post(self, request, slug, project_id): # Get the presigned URL storage = S3Storage(request=request) # Generate a presigned URL to share an S3 object - presigned_url = storage.generate_presigned_post( - object_name=asset_key, file_type=type, file_size=size_limit - ) + presigned_url = storage.generate_presigned_post(object_name=asset_key, file_type=type, file_size=size_limit) # Return the presigned URL return Response( { @@ -619,9 +591,7 @@ def patch(self, request, slug, project_id, pk): @allow_permission([ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST]) def delete(self, request, slug, project_id, pk): # Get the asset - asset = FileAsset.objects.get( - id=pk, workspace__slug=slug, project_id=project_id - ) + asset = FileAsset.objects.get(id=pk, workspace__slug=slug, project_id=project_id) # Check deleted assets asset.is_deleted = True asset.deleted_at = timezone.now() @@ -632,9 +602,7 @@ def delete(self, request, slug, project_id, pk): @allow_permission([ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST]) def get(self, request, slug, project_id, pk): # get the asset id - asset = FileAsset.objects.get( - workspace__slug=slug, project_id=project_id, pk=pk - ) + asset = FileAsset.objects.get(workspace__slug=slug, project_id=project_id, pk=pk) # Check if the asset is uploaded if not asset.is_uploaded: @@ -667,9 +635,7 @@ def post(self, request, slug, project_id, entity_id): # Check if the asset ids are provided if not asset_ids: - return Response( - {"error": "No asset ids provided."}, status=status.HTTP_400_BAD_REQUEST - ) + return Response({"error": "No asset ids provided."}, status=status.HTTP_400_BAD_REQUEST) # get the asset id assets = FileAsset.objects.filter(id__in=asset_ids, workspace__slug=slug) @@ -723,14 +689,11 @@ class AssetCheckEndpoint(BaseAPIView): @allow_permission([ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST], level="WORKSPACE") def get(self, request, slug, asset_id): - asset = FileAsset.all_objects.filter( - id=asset_id, workspace__slug=slug, deleted_at__isnull=True - ).exists() + asset = FileAsset.all_objects.filter(id=asset_id, workspace__slug=slug, deleted_at__isnull=True).exists() return Response({"exists": asset}, status=status.HTTP_200_OK) class DuplicateAssetEndpoint(BaseAPIView): - throttle_classes = [AssetRateThrottle] def get_entity_id_field(self, entity_type, entity_id): @@ -772,11 +735,7 @@ def post(self, request, slug, asset_id): entity_id = request.data.get("entity_id", None) entity_type = request.data.get("entity_type", None) - - if ( - not entity_type - or entity_type not in FileAsset.EntityTypeContext.values - ): + if not entity_type or entity_type not in FileAsset.EntityTypeContext.values: return Response( {"error": "Invalid entity type or entity id"}, status=status.HTTP_400_BAD_REQUEST, @@ -786,23 +745,15 @@ def post(self, request, slug, asset_id): if project_id: # check if project exists in the workspace if not Project.objects.filter(id=project_id, workspace=workspace).exists(): - return Response( - {"error": "Project not found"}, status=status.HTTP_404_NOT_FOUND - ) + return Response({"error": "Project not found"}, status=status.HTTP_404_NOT_FOUND) storage = S3Storage(request=request) - original_asset = FileAsset.objects.filter( - id=asset_id, is_uploaded=True - ).first() + original_asset = FileAsset.objects.filter(id=asset_id, is_uploaded=True).first() if not original_asset: - return Response( - {"error": "Asset not found"}, status=status.HTTP_404_NOT_FOUND - ) + return Response({"error": "Asset not found"}, status=status.HTTP_404_NOT_FOUND) - destination_key = ( - f"{workspace.id}/{uuid.uuid4().hex}-{original_asset.attributes.get('name')}" - ) + destination_key = f"{workspace.id}/{uuid.uuid4().hex}-{original_asset.attributes.get('name')}" duplicated_asset = FileAsset.objects.create( attributes={ "name": original_asset.attributes.get("name"), @@ -822,9 +773,7 @@ def post(self, request, slug, asset_id): # Update the is_uploaded field for all newly created assets FileAsset.objects.filter(id=duplicated_asset.id).update(is_uploaded=True) - return Response( - {"asset_id": str(duplicated_asset.id)}, status=status.HTTP_200_OK - ) + return Response({"asset_id": str(duplicated_asset.id)}, status=status.HTTP_200_OK) class WorkspaceAssetDownloadEndpoint(BaseAPIView): diff --git a/apps/api/plane/app/views/cycle/base.py b/apps/api/plane/app/views/cycle/base.py index 712d71754e5..d61f1587b3f 100644 --- a/apps/api/plane/app/views/cycle/base.py +++ b/apps/api/plane/app/views/cycle/base.py @@ -97,9 +97,7 @@ def get_queryset(self): .prefetch_related( Prefetch( "issue_cycle__issue__assignees", - queryset=User.objects.only( - "avatar_asset", "first_name", "id" - ).distinct(), + queryset=User.objects.only("avatar_asset", "first_name", "id").distinct(), ) ) .prefetch_related( @@ -150,8 +148,7 @@ def get_queryset(self): .annotate( status=Case( When( - Q(start_date__lte=current_time_in_utc) - & Q(end_date__gte=current_time_in_utc), + Q(start_date__lte=current_time_in_utc) & Q(end_date__gte=current_time_in_utc), then=Value("CURRENT"), ), When(start_date__gt=current_time_in_utc, then=Value("UPCOMING")), @@ -170,11 +167,7 @@ def get_queryset(self): "issue_cycle__issue__assignees__id", distinct=True, filter=~Q(issue_cycle__issue__assignees__id__isnull=True) - & ( - Q( - issue_cycle__issue__issue_assignee__deleted_at__isnull=True - ) - ), + & (Q(issue_cycle__issue__issue_assignee__deleted_at__isnull=True)), ), Value([], output_field=ArrayField(UUIDField())), ) @@ -205,9 +198,7 @@ def list(self, request, slug, project_id): # Current Cycle if cycle_view == "current": - queryset = queryset.filter( - start_date__lte=current_time_in_utc, end_date__gte=current_time_in_utc - ) + queryset = queryset.filter(start_date__lte=current_time_in_utc, end_date__gte=current_time_in_utc) data = queryset.values( # necessary fields @@ -274,16 +265,10 @@ def list(self, request, slug, project_id): @allow_permission([ROLE.ADMIN, ROLE.MEMBER]) def create(self, request, slug, project_id): - if ( - request.data.get("start_date", None) is None - and request.data.get("end_date", None) is None - ) or ( - request.data.get("start_date", None) is not None - and request.data.get("end_date", None) is not None + if (request.data.get("start_date", None) is None and request.data.get("end_date", None) is None) or ( + request.data.get("start_date", None) is not None and request.data.get("end_date", None) is not None ): - serializer = CycleWriteSerializer( - data=request.data, context={"project_id": project_id} - ) + serializer = CycleWriteSerializer(data=request.data, context={"project_id": project_id}) if serializer.is_valid(): serializer.save(project_id=project_id, owned_by=request.user) cycle = ( @@ -323,9 +308,7 @@ def create(self, request, slug, project_id): project_timezone = project.timezone datetime_fields = ["start_date", "end_date"] - cycle = user_timezone_converter( - cycle, datetime_fields, project_timezone - ) + cycle = user_timezone_converter(cycle, datetime_fields, project_timezone) # Send the model activity model_activity.delay( @@ -341,17 +324,13 @@ def create(self, request, slug, project_id): return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) else: return Response( - { - "error": "Both start date and end date are either required or are to be null" - }, + {"error": "Both start date and end date are either required or are to be null"}, status=status.HTTP_400_BAD_REQUEST, ) @allow_permission([ROLE.ADMIN, ROLE.MEMBER]) def partial_update(self, request, slug, project_id, pk): - queryset = self.get_queryset().filter( - workspace__slug=slug, project_id=project_id, pk=pk - ) + queryset = self.get_queryset().filter(workspace__slug=slug, project_id=project_id, pk=pk) cycle = queryset.first() if cycle.archived_at: return Response( @@ -359,29 +338,21 @@ def partial_update(self, request, slug, project_id, pk): status=status.HTTP_400_BAD_REQUEST, ) - current_instance = json.dumps( - CycleSerializer(cycle).data, cls=DjangoJSONEncoder - ) + current_instance = json.dumps(CycleSerializer(cycle).data, cls=DjangoJSONEncoder) request_data = request.data if cycle.end_date is not None and cycle.end_date < timezone.now(): if "sort_order" in request_data: # Can only change sort order for a completed cycle`` - request_data = { - "sort_order": request_data.get("sort_order", cycle.sort_order) - } + request_data = {"sort_order": request_data.get("sort_order", cycle.sort_order)} else: return Response( - { - "error": "The Cycle has already been completed so it cannot be edited" - }, + {"error": "The Cycle has already been completed so it cannot be edited"}, status=status.HTTP_400_BAD_REQUEST, ) - serializer = CycleWriteSerializer( - cycle, data=request.data, partial=True, context={"project_id": project_id} - ) + serializer = CycleWriteSerializer(cycle, data=request.data, partial=True, context={"project_id": project_id}) if serializer.is_valid(): serializer.save() cycle = queryset.values( @@ -481,9 +452,7 @@ def retrieve(self, request, slug, project_id, pk): ) if data is None: - return Response( - {"error": "Cycle not found"}, status=status.HTTP_404_NOT_FOUND - ) + return Response({"error": "Cycle not found"}, status=status.HTTP_404_NOT_FOUND) queryset = queryset.first() # Fetch the project timezone @@ -505,11 +474,7 @@ def retrieve(self, request, slug, project_id, pk): def destroy(self, request, slug, project_id, pk): cycle = Cycle.objects.get(workspace__slug=slug, project_id=project_id, pk=pk) - cycle_issues = list( - CycleIssue.objects.filter(cycle_id=self.kwargs.get("pk")).values_list( - "issue", flat=True - ) - ) + cycle_issues = list(CycleIssue.objects.filter(cycle_id=self.kwargs.get("pk")).values_list("issue", flat=True)) issue_activity.delay( type="cycle.activity.deleted", @@ -560,9 +525,7 @@ def post(self, request, slug, project_id): status=status.HTTP_400_BAD_REQUEST, ) - start_date = convert_to_utc( - date=str(start_date), project_id=project_id, is_start_date=True - ) + start_date = convert_to_utc(date=str(start_date), project_id=project_id, is_start_date=True) end_date = convert_to_utc( date=str(end_date), project_id=project_id, @@ -666,12 +629,8 @@ def patch(self, request, slug, project_id, cycle_id): ) cycle_properties.filters = request.data.get("filters", cycle_properties.filters) - cycle_properties.rich_filters = request.data.get( - "rich_filters", cycle_properties.rich_filters - ) - cycle_properties.display_filters = request.data.get( - "display_filters", cycle_properties.display_filters - ) + cycle_properties.rich_filters = request.data.get("rich_filters", cycle_properties.rich_filters) + cycle_properties.display_filters = request.data.get("display_filters", cycle_properties.display_filters) cycle_properties.display_properties = request.data.get( "display_properties", cycle_properties.display_properties ) @@ -695,13 +654,9 @@ def get(self, request, slug, project_id, cycle_id): class CycleProgressEndpoint(BaseAPIView): @allow_permission([ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST]) def get(self, request, slug, project_id, cycle_id): - cycle = Cycle.objects.filter( - workspace__slug=slug, project_id=project_id, id=cycle_id - ).first() + cycle = Cycle.objects.filter(workspace__slug=slug, project_id=project_id, id=cycle_id).first() if not cycle: - return Response( - {"error": "Cycle not found"}, status=status.HTTP_404_NOT_FOUND - ) + return Response({"error": "Cycle not found"}, status=status.HTTP_404_NOT_FOUND) aggregate_estimates = ( Issue.issue_objects.filter( estimate_point__estimate__type="points", @@ -747,9 +702,7 @@ def get(self, request, slug, project_id, cycle_id): output_field=FloatField(), ) ), - total_estimate_points=Sum( - "value_as_float", default=Value(0), output_field=FloatField() - ), + total_estimate_points=Sum("value_as_float", default=Value(0), output_field=FloatField()), ) ) if cycle.progress_snapshot: @@ -809,22 +762,11 @@ def get(self, request, slug, project_id, cycle_id): return Response( { - "backlog_estimate_points": aggregate_estimates["backlog_estimate_point"] - or 0, - "unstarted_estimate_points": aggregate_estimates[ - "unstarted_estimate_point" - ] - or 0, - "started_estimate_points": aggregate_estimates["started_estimate_point"] - or 0, - "cancelled_estimate_points": aggregate_estimates[ - "cancelled_estimate_point" - ] - or 0, - "completed_estimate_points": aggregate_estimates[ - "completed_estimate_points" - ] - or 0, + "backlog_estimate_points": aggregate_estimates["backlog_estimate_point"] or 0, + "unstarted_estimate_points": aggregate_estimates["unstarted_estimate_point"] or 0, + "started_estimate_points": aggregate_estimates["started_estimate_point"] or 0, + "cancelled_estimate_points": aggregate_estimates["cancelled_estimate_point"] or 0, + "completed_estimate_points": aggregate_estimates["completed_estimate_points"] or 0, "total_estimate_points": aggregate_estimates["total_estimate_points"], "backlog_issues": backlog_issues, "total_issues": total_issues, @@ -842,9 +784,7 @@ class CycleAnalyticsEndpoint(BaseAPIView): def get(self, request, slug, project_id, cycle_id): analytic_type = request.GET.get("type", "issues") cycle = ( - Cycle.objects.filter( - workspace__slug=slug, project_id=project_id, id=cycle_id - ) + Cycle.objects.filter(workspace__slug=slug, project_id=project_id, id=cycle_id) .annotate( total_issues=Count( "issue_cycle__issue__id", @@ -927,9 +867,7 @@ def get(self, request, slug, project_id, cycle_id): ) ) .values("display_name", "assignee_id", "avatar_url") - .annotate( - total_estimates=Sum(Cast("estimate_point__value", FloatField())) - ) + .annotate(total_estimates=Sum(Cast("estimate_point__value", FloatField()))) .annotate( completed_estimates=Sum( Cast("estimate_point__value", FloatField()), @@ -964,9 +902,7 @@ def get(self, request, slug, project_id, cycle_id): .annotate(color=F("labels__color")) .annotate(label_id=F("labels__id")) .values("label_name", "color", "label_id") - .annotate( - total_estimates=Sum(Cast("estimate_point__value", FloatField())) - ) + .annotate(total_estimates=Sum(Cast("estimate_point__value", FloatField()))) .annotate( completed_estimates=Sum( Cast("estimate_point__value", FloatField()), @@ -1068,11 +1004,7 @@ def get(self, request, slug, project_id, cycle_id): .annotate(color=F("labels__color")) .annotate(label_id=F("labels__id")) .values("label_name", "color", "label_id") - .annotate( - total_issues=Count( - "label_id", filter=Q(archived_at__isnull=True, is_draft=False) - ) - ) + .annotate(total_issues=Count("label_id", filter=Q(archived_at__isnull=True, is_draft=False))) .annotate( completed_issues=Count( "label_id", diff --git a/apps/api/plane/app/views/issue/label.py b/apps/api/plane/app/views/issue/label.py index ad0a290801b..6e46b5abb0d 100644 --- a/apps/api/plane/app/views/issue/label.py +++ b/apps/api/plane/app/views/issue/label.py @@ -39,9 +39,7 @@ def get_queryset(self): @allow_permission([ROLE.ADMIN]) def create(self, request, slug, project_id): try: - serializer = LabelSerializer( - data=request.data, context={"project_id": project_id} - ) + serializer = LabelSerializer(data=request.data, context={"project_id": project_id}) if serializer.is_valid(): serializer.save(project_id=project_id) return Response(serializer.data, status=status.HTTP_201_CREATED) diff --git a/apps/api/plane/app/views/page/base.py b/apps/api/plane/app/views/page/base.py index 50daf440adc..d3ad49b5fda 100644 --- a/apps/api/plane/app/views/page/base.py +++ b/apps/api/plane/app/views/page/base.py @@ -495,14 +495,12 @@ class PagesDescriptionViewSet(BaseViewSet): permission_classes = [ProjectPagePermission] def retrieve(self, request, slug, project_id, page_id): - page = ( - Page.objects.get( - Q(owned_by=self.request.user) | Q(access=0), - pk=page_id, - workspace__slug=slug, - projects__id=project_id, - project_pages__deleted_at__isnull=True, - ) + page = Page.objects.get( + Q(owned_by=self.request.user) | Q(access=0), + pk=page_id, + workspace__slug=slug, + projects__id=project_id, + project_pages__deleted_at__isnull=True, ) binary_data = page.description_binary @@ -517,14 +515,12 @@ def stream_data(): return response def partial_update(self, request, slug, project_id, page_id): - page = ( - Page.objects.get( - Q(owned_by=self.request.user) | Q(access=0), - pk=page_id, - workspace__slug=slug, - projects__id=project_id, - project_pages__deleted_at__isnull=True, - ) + page = Page.objects.get( + Q(owned_by=self.request.user) | Q(access=0), + pk=page_id, + workspace__slug=slug, + projects__id=project_id, + project_pages__deleted_at__isnull=True, ) if page.is_locked: diff --git a/apps/api/plane/authentication/adapter/base.py b/apps/api/plane/authentication/adapter/base.py index b80555fe16e..d01f3f10b2b 100644 --- a/apps/api/plane/authentication/adapter/base.py +++ b/apps/api/plane/authentication/adapter/base.py @@ -90,9 +90,9 @@ def __check_signup(self, email): """Check if sign up is enabled or not and raise exception if not enabled""" # Get configuration value - (ENABLE_SIGNUP,) = get_configuration_value([ - {"key": "ENABLE_SIGNUP", "default": os.environ.get("ENABLE_SIGNUP", "1")} - ]) + (ENABLE_SIGNUP,) = get_configuration_value( + [{"key": "ENABLE_SIGNUP", "default": os.environ.get("ENABLE_SIGNUP", "1")}] + ) # Check if sign up is disabled and invite is present or not if ENABLE_SIGNUP == "0" and not WorkspaceMemberInvite.objects.filter(email=email).exists(): diff --git a/apps/api/plane/authentication/provider/oauth/gitea.py b/apps/api/plane/authentication/provider/oauth/gitea.py index ba7d3d16ba3..4d8de8e1af3 100644 --- a/apps/api/plane/authentication/provider/oauth/gitea.py +++ b/apps/api/plane/authentication/provider/oauth/gitea.py @@ -101,9 +101,7 @@ def set_token_data(self): else None ), "refresh_token_expired_at": ( - datetime.fromtimestamp( - token_response.get("refresh_token_expired_at"), tz=pytz.utc - ) + datetime.fromtimestamp(token_response.get("refresh_token_expired_at"), tz=pytz.utc) if token_response.get("refresh_token_expired_at") else None ), @@ -168,4 +166,4 @@ def set_user_data(self): "is_password_autoset": True, }, } - ) \ No newline at end of file + ) diff --git a/apps/api/plane/authentication/views/app/gitea.py b/apps/api/plane/authentication/views/app/gitea.py index fd12f8b3363..e43a35c3c89 100644 --- a/apps/api/plane/authentication/views/app/gitea.py +++ b/apps/api/plane/authentication/views/app/gitea.py @@ -37,9 +37,7 @@ def get(self, request): params = exc.get_error_dict() if next_path: params["next_path"] = str(validate_next_path(next_path)) - url = urljoin( - base_host(request=request, is_app=True), "?" + urlencode(params) - ) + url = urljoin(base_host(request=request, is_app=True), "?" + urlencode(params)) return HttpResponseRedirect(url) try: state = uuid.uuid4().hex @@ -51,9 +49,7 @@ def get(self, request): params = e.get_error_dict() if next_path: params["next_path"] = str(validate_next_path(next_path)) - url = urljoin( - base_host(request=request, is_app=True), "?" + urlencode(params) - ) + url = urljoin(base_host(request=request, is_app=True), "?" + urlencode(params)) return HttpResponseRedirect(url) @@ -87,9 +83,7 @@ def get(self, request): return HttpResponseRedirect(url) try: - provider = GiteaOAuthProvider( - request=request, code=code, callback=post_user_auth_workflow - ) + provider = GiteaOAuthProvider(request=request, code=code, callback=post_user_auth_workflow) user = provider.authenticate() # Login the user and record his device info user_login(request=request, user=user, is_app=True) diff --git a/apps/api/plane/bgtasks/page_transaction_task.py b/apps/api/plane/bgtasks/page_transaction_task.py index 402d0a3ee02..9c0caccf068 100644 --- a/apps/api/plane/bgtasks/page_transaction_task.py +++ b/apps/api/plane/bgtasks/page_transaction_task.py @@ -88,7 +88,6 @@ def page_transaction(new_description_html, old_description_html, page_id): has_existing_logs = PageLog.objects.filter(page_id=page_id).exists() - # Extract all components in a single pass (optimized) old_components = extract_all_components(old_description_html) new_components = extract_all_components(new_description_html) @@ -125,12 +124,9 @@ def page_transaction(new_description_html, old_description_html, page_id): ) ) - # Bulk insert and cleanup if new_transactions: - PageLog.objects.bulk_create( - new_transactions, batch_size=50, ignore_conflicts=True - ) + PageLog.objects.bulk_create(new_transactions, batch_size=50, ignore_conflicts=True) if deleted_transaction_ids: PageLog.objects.filter(transaction__in=deleted_transaction_ids).delete() diff --git a/apps/api/plane/bgtasks/workspace_seed_task.py b/apps/api/plane/bgtasks/workspace_seed_task.py index 57ac02ec127..6df6b49663b 100644 --- a/apps/api/plane/bgtasks/workspace_seed_task.py +++ b/apps/api/plane/bgtasks/workspace_seed_task.py @@ -107,56 +107,60 @@ def create_project_and_member(workspace: Workspace, bot_user: User) -> Dict[int, ) # Create project members - ProjectMember.objects.bulk_create([ - ProjectMember( - project=project, - member_id=workspace_member["member_id"], - role=workspace_member["role"], - workspace_id=workspace.id, - created_by_id=bot_user.id, - ) - for workspace_member in workspace_members - ]) + ProjectMember.objects.bulk_create( + [ + ProjectMember( + project=project, + member_id=workspace_member["member_id"], + role=workspace_member["role"], + workspace_id=workspace.id, + created_by_id=bot_user.id, + ) + for workspace_member in workspace_members + ] + ) # Create issue user properties - IssueUserProperty.objects.bulk_create([ - IssueUserProperty( - project=project, - user_id=workspace_member["member_id"], - workspace_id=workspace.id, - display_filters={ - "layout": "list", - "calendar": {"layout": "month", "show_weekends": False}, - "group_by": "state", - "order_by": "sort_order", - "sub_issue": True, - "sub_group_by": None, - "show_empty_groups": True, - }, - display_properties={ - "key": True, - "link": True, - "cycle": False, - "state": True, - "labels": False, - "modules": False, - "assignee": True, - "due_date": False, - "estimate": True, - "priority": True, - "created_on": True, - "issue_type": True, - "start_date": False, - "updated_on": True, - "customer_count": True, - "sub_issue_count": False, - "attachment_count": False, - "customer_request_count": True, - }, - created_by_id=bot_user.id, - ) - for workspace_member in workspace_members - ]) + IssueUserProperty.objects.bulk_create( + [ + IssueUserProperty( + project=project, + user_id=workspace_member["member_id"], + workspace_id=workspace.id, + display_filters={ + "layout": "list", + "calendar": {"layout": "month", "show_weekends": False}, + "group_by": "state", + "order_by": "sort_order", + "sub_issue": True, + "sub_group_by": None, + "show_empty_groups": True, + }, + display_properties={ + "key": True, + "link": True, + "cycle": False, + "state": True, + "labels": False, + "modules": False, + "assignee": True, + "due_date": False, + "estimate": True, + "priority": True, + "created_on": True, + "issue_type": True, + "start_date": False, + "updated_on": True, + "customer_count": True, + "sub_issue_count": False, + "attachment_count": False, + "customer_request_count": True, + }, + created_by_id=bot_user.id, + ) + for workspace_member in workspace_members + ] + ) # update map projects_map[project_id] = project.id logger.info(f"Task: workspace_seed_task -> Project {project_id} created") diff --git a/apps/api/plane/license/api/views/admin.py b/apps/api/plane/license/api/views/admin.py index 5b70beab9d1..0d37f4fdc0e 100644 --- a/apps/api/plane/license/api/views/admin.py +++ b/apps/api/plane/license/api/views/admin.py @@ -134,8 +134,10 @@ def post(self, request): }, ) url = urljoin( - base_host(request=request, is_admin=True, ), - + base_host( + request=request, + is_admin=True, + ), "?" + urlencode(exc.get_error_dict()), ) return HttpResponseRedirect(url) diff --git a/apps/api/plane/tests/unit/serializers/test_label.py b/apps/api/plane/tests/unit/serializers/test_label.py index 91cde1c4ad8..4775ef49ad1 100644 --- a/apps/api/plane/tests/unit/serializers/test_label.py +++ b/apps/api/plane/tests/unit/serializers/test_label.py @@ -10,9 +10,7 @@ class TestLabelSerializer: @pytest.mark.django_db def test_label_serializer_create_valid_data(self, db, workspace): """Test creating a label with valid data""" - project = Project.objects.create( - name="Test Project", identifier="TEST", workspace=workspace - ) + project = Project.objects.create(name="Test Project", identifier="TEST", workspace=workspace) serializer = LabelSerializer( data={"name": "Test Label"}, @@ -30,14 +28,10 @@ def test_label_serializer_create_valid_data(self, db, workspace): @pytest.mark.django_db def test_label_serializer_create_duplicate_name(self, db, workspace): """Test creating a label with a duplicate name""" - project = Project.objects.create( - name="Test Project", identifier="TEST", workspace=workspace - ) + project = Project.objects.create(name="Test Project", identifier="TEST", workspace=workspace) Label.objects.create(name="Test Label", project=project) - serializer = LabelSerializer( - data={"name": "Test Label"}, context={"project_id": project.id} - ) + serializer = LabelSerializer(data={"name": "Test Label"}, context={"project_id": project.id}) assert not serializer.is_valid() assert serializer.errors == {"name": ["LABEL_NAME_ALREADY_EXISTS"]} diff --git a/apps/api/plane/utils/content_validator.py b/apps/api/plane/utils/content_validator.py index 10e83b85dab..00e6c0c6605 100644 --- a/apps/api/plane/utils/content_validator.py +++ b/apps/api/plane/utils/content_validator.py @@ -56,9 +56,7 @@ def validate_binary_data(data): # Check for suspicious text patterns (HTML/JS) try: decoded_text = binary_data.decode("utf-8", errors="ignore")[:200] - if any( - pattern in decoded_text.lower() for pattern in SUSPICIOUS_BINARY_PATTERNS - ): + if any(pattern in decoded_text.lower() for pattern in SUSPICIOUS_BINARY_PATTERNS): return False, "Binary data contains suspicious content patterns" except Exception: pass # Binary data might not be decodable as text, which is fine diff --git a/apps/api/plane/utils/cycle_transfer_issues.py b/apps/api/plane/utils/cycle_transfer_issues.py index ec934e8892d..fda9f39b95c 100644 --- a/apps/api/plane/utils/cycle_transfer_issues.py +++ b/apps/api/plane/utils/cycle_transfer_issues.py @@ -51,9 +51,7 @@ def transfer_cycle_issues( dict: Response data with success or error message """ # Get the new cycle - new_cycle = Cycle.objects.filter( - workspace__slug=slug, project_id=project_id, pk=new_cycle_id - ).first() + new_cycle = Cycle.objects.filter(workspace__slug=slug, project_id=project_id, pk=new_cycle_id).first() # Check if new cycle is already completed if new_cycle.end_date is not None and new_cycle.end_date < timezone.now(): @@ -216,9 +214,7 @@ def transfer_cycle_issues( assignee_estimate_distribution = [ { "display_name": item["display_name"], - "assignee_id": ( - str(item["assignee_id"]) if item["assignee_id"] else None - ), + "assignee_id": (str(item["assignee_id"]) if item["assignee_id"] else None), "avatar_url": item.get("avatar_url"), "total_estimates": item["total_estimates"], "completed_estimates": item["completed_estimates"], @@ -310,9 +306,7 @@ def transfer_cycle_issues( ) ) .values("display_name", "assignee_id", "avatar_url") - .annotate( - total_issues=Count("id", filter=Q(archived_at__isnull=True, is_draft=False)) - ) + .annotate(total_issues=Count("id", filter=Q(archived_at__isnull=True, is_draft=False))) .annotate( completed_issues=Count( "id", @@ -360,9 +354,7 @@ def transfer_cycle_issues( .annotate(color=F("labels__color")) .annotate(label_id=F("labels__id")) .values("label_name", "color", "label_id") - .annotate( - total_issues=Count("id", filter=Q(archived_at__isnull=True, is_draft=False)) - ) + .annotate(total_issues=Count("id", filter=Q(archived_at__isnull=True, is_draft=False))) .annotate( completed_issues=Count( "id", @@ -409,9 +401,7 @@ def transfer_cycle_issues( ) # Get the current cycle and save progress snapshot - current_cycle = Cycle.objects.filter( - workspace__slug=slug, project_id=project_id, pk=cycle_id - ).first() + current_cycle = Cycle.objects.filter(workspace__slug=slug, project_id=project_id, pk=cycle_id).first() current_cycle.progress_snapshot = { "total_issues": old_cycle.total_issues, @@ -461,9 +451,7 @@ def transfer_cycle_issues( ) # Bulk update cycle issues - cycle_issues = CycleIssue.objects.bulk_update( - updated_cycles, ["cycle_id"], batch_size=100 - ) + cycle_issues = CycleIssue.objects.bulk_update(updated_cycles, ["cycle_id"], batch_size=100) # Capture Issue Activity issue_activity.delay( diff --git a/apps/api/plane/utils/openapi/decorators.py b/apps/api/plane/utils/openapi/decorators.py index c1ba9612e5c..b11926889c2 100644 --- a/apps/api/plane/utils/openapi/decorators.py +++ b/apps/api/plane/utils/openapi/decorators.py @@ -263,6 +263,7 @@ def state_docs(**kwargs): return extend_schema(**_merge_schema_options(defaults, kwargs)) + def sticky_docs(**kwargs): """Decorator for sticky management endpoints""" defaults = { @@ -276,4 +277,4 @@ def sticky_docs(**kwargs): }, } - return extend_schema(**_merge_schema_options(defaults, kwargs)) \ No newline at end of file + return extend_schema(**_merge_schema_options(defaults, kwargs))