diff --git a/apps/api/plane/api/apps.py b/apps/api/plane/api/apps.py index b48a9a949..f1f531118 100644 --- a/apps/api/plane/api/apps.py +++ b/apps/api/plane/api/apps.py @@ -9,4 +9,4 @@ class ApiConfig(AppConfig): try: import plane.utils.openapi.auth # noqa except ImportError: - pass \ No newline at end of file + pass diff --git a/apps/api/plane/api/serializers/asset.py b/apps/api/plane/api/serializers/asset.py index b63dc7ebb..6b74b3757 100644 --- a/apps/api/plane/api/serializers/asset.py +++ b/apps/api/plane/api/serializers/asset.py @@ -46,9 +46,7 @@ class AssetUpdateSerializer(serializers.Serializer): and upload confirmation for S3-based file storage workflows. """ - attributes = serializers.JSONField( - required=False, help_text="Additional attributes to update for the asset" - ) + attributes = serializers.JSONField(required=False, help_text="Additional attributes to update for the asset") class GenericAssetUploadSerializer(serializers.Serializer): @@ -85,9 +83,7 @@ class GenericAssetUpdateSerializer(serializers.Serializer): upload completion marking and metadata finalization. """ - is_uploaded = serializers.BooleanField( - default=True, help_text="Whether the asset has been successfully uploaded" - ) + is_uploaded = serializers.BooleanField(default=True, help_text="Whether the asset has been successfully uploaded") class FileAssetSerializer(BaseSerializer): diff --git a/apps/api/plane/api/serializers/base.py b/apps/api/plane/api/serializers/base.py index 6ef2b1582..bc790f2cd 100644 --- a/apps/api/plane/api/serializers/base.py +++ b/apps/api/plane/api/serializers/base.py @@ -103,13 +103,9 @@ class BaseSerializer(serializers.ModelSerializer): # Check if field in expansion then expand the field if expand in expansion: if isinstance(response.get(expand), list): - exp_serializer = expansion[expand]( - getattr(instance, expand), many=True - ) + exp_serializer = expansion[expand](getattr(instance, expand), many=True) else: - exp_serializer = expansion[expand]( - getattr(instance, expand) - ) + exp_serializer = expansion[expand](getattr(instance, expand)) response[expand] = exp_serializer.data else: # You might need to handle this case differently diff --git a/apps/api/plane/api/serializers/cycle.py b/apps/api/plane/api/serializers/cycle.py index cf057d842..726715db7 100644 --- a/apps/api/plane/api/serializers/cycle.py +++ b/apps/api/plane/api/serializers/cycle.py @@ -55,14 +55,9 @@ class CycleCreateSerializer(BaseSerializer): ): raise serializers.ValidationError("Start date cannot exceed end date") - if ( - data.get("start_date", None) is not None - and data.get("end_date", None) is not None - ): + if data.get("start_date", None) is not None and data.get("end_date", None) is not None: project_id = self.initial_data.get("project_id") or ( - self.instance.project_id - if self.instance and hasattr(self.instance, "project_id") - else None + self.instance.project_id if self.instance and hasattr(self.instance, "project_id") else None ) if not project_id: @@ -166,9 +161,7 @@ class CycleIssueRequestSerializer(serializers.Serializer): cycle assignment and sprint planning workflows. """ - issues = serializers.ListField( - child=serializers.UUIDField(), help_text="List of issue IDs to add to the cycle" - ) + issues = serializers.ListField(child=serializers.UUIDField(), help_text="List of issue IDs to add to the cycle") class TransferCycleIssueRequestSerializer(serializers.Serializer): @@ -179,6 +172,4 @@ class TransferCycleIssueRequestSerializer(serializers.Serializer): and relationship updates for sprint reallocation workflows. """ - new_cycle_id = serializers.UUIDField( - help_text="ID of the target cycle to transfer issues to" - ) + new_cycle_id = serializers.UUIDField(help_text="ID of the target cycle to transfer issues to") diff --git a/apps/api/plane/api/serializers/intake.py b/apps/api/plane/api/serializers/intake.py index 32f8bf2da..5af4fd54b 100644 --- a/apps/api/plane/api/serializers/intake.py +++ b/apps/api/plane/api/serializers/intake.py @@ -98,9 +98,7 @@ class IntakeIssueUpdateSerializer(BaseSerializer): and embedded issue updates for issue queue processing workflows. """ - issue = IssueForIntakeSerializer( - required=False, help_text="Issue data to update in the intake issue" - ) + issue = IssueForIntakeSerializer(required=False, help_text="Issue data to update in the intake issue") class Meta: model = IntakeIssue @@ -132,9 +130,5 @@ class IssueDataSerializer(serializers.Serializer): """ name = serializers.CharField(max_length=255, help_text="Issue name") - description_html = serializers.CharField( - required=False, allow_null=True, help_text="Issue description HTML" - ) - priority = serializers.ChoiceField( - choices=Issue.PRIORITY_CHOICES, default="none", help_text="Issue priority" - ) + description_html = serializers.CharField(required=False, allow_null=True, help_text="Issue description HTML") + priority = serializers.ChoiceField(choices=Issue.PRIORITY_CHOICES, default="none", help_text="Issue priority") diff --git a/apps/api/plane/api/serializers/issue.py b/apps/api/plane/api/serializers/issue.py index 2bd0d884b..d7fc3e911 100644 --- a/apps/api/plane/api/serializers/issue.py +++ b/apps/api/plane/api/serializers/issue.py @@ -48,17 +48,13 @@ class IssueSerializer(BaseSerializer): """ assignees = serializers.ListField( - child=serializers.PrimaryKeyRelatedField( - queryset=User.objects.values_list("id", flat=True) - ), + child=serializers.PrimaryKeyRelatedField(queryset=User.objects.values_list("id", flat=True)), write_only=True, required=False, ) labels = serializers.ListField( - child=serializers.PrimaryKeyRelatedField( - queryset=Label.objects.values_list("id", flat=True) - ), + child=serializers.PrimaryKeyRelatedField(queryset=Label.objects.values_list("id", flat=True)), write_only=True, required=False, ) @@ -90,13 +86,9 @@ class IssueSerializer(BaseSerializer): # Validate description content for security if data.get("description_html"): - is_valid, error_msg, sanitized_html = validate_html_content( - data["description_html"] - ) + is_valid, error_msg, sanitized_html = validate_html_content(data["description_html"]) if not is_valid: - raise serializers.ValidationError( - {"error": "html content is not valid"} - ) + raise serializers.ValidationError({"error": "html content is not valid"}) # Update the data with sanitized HTML if available if sanitized_html is not None: data["description_html"] = sanitized_html @@ -104,9 +96,7 @@ class IssueSerializer(BaseSerializer): if data.get("description_binary"): is_valid, error_msg = validate_binary_data(data["description_binary"]) if not is_valid: - raise serializers.ValidationError( - {"description_binary": "Invalid binary data"} - ) + raise serializers.ValidationError({"description_binary": "Invalid binary data"}) # Validate assignees are from project if data.get("assignees", []): @@ -126,13 +116,9 @@ class IssueSerializer(BaseSerializer): # Check state is from the project only else raise validation error if ( data.get("state") - and not State.objects.filter( - project_id=self.context.get("project_id"), pk=data.get("state").id - ).exists() + and not State.objects.filter(project_id=self.context.get("project_id"), pk=data.get("state").id).exists() ): - raise serializers.ValidationError( - "State is not valid please pass a valid state_id" - ) + raise serializers.ValidationError("State is not valid please pass a valid state_id") # Check parent issue is from workspace as it can be cross workspace if ( @@ -143,9 +129,7 @@ class IssueSerializer(BaseSerializer): pk=data.get("parent").id, ).exists() ): - raise serializers.ValidationError( - "Parent is not valid issue_id please pass a valid issue_id" - ) + raise serializers.ValidationError("Parent is not valid issue_id please pass a valid issue_id") if ( data.get("estimate_point") @@ -155,9 +139,7 @@ class IssueSerializer(BaseSerializer): pk=data.get("estimate_point").id, ).exists() ): - raise serializers.ValidationError( - "Estimate point is not valid please pass a valid estimate_point_id" - ) + raise serializers.ValidationError("Estimate point is not valid please pass a valid estimate_point_id") return data @@ -173,14 +155,10 @@ class IssueSerializer(BaseSerializer): if not issue_type: # Get default issue type - issue_type = IssueType.objects.filter( - project_issue_types__project_id=project_id, is_default=True - ).first() + issue_type = IssueType.objects.filter(project_issue_types__project_id=project_id, is_default=True).first() issue_type = issue_type - issue = Issue.objects.create( - **validated_data, project_id=project_id, type=issue_type - ) + issue = Issue.objects.create(**validated_data, project_id=project_id, type=issue_type) # Issue Audit Users created_by_id = issue.created_by_id @@ -312,35 +290,26 @@ class IssueSerializer(BaseSerializer): data["assignees"] = UserLiteSerializer( User.objects.filter( - pk__in=IssueAssignee.objects.filter(issue=instance).values_list( - "assignee_id", flat=True - ) + pk__in=IssueAssignee.objects.filter(issue=instance).values_list("assignee_id", flat=True) ), many=True, ).data else: data["assignees"] = [ str(assignee) - for assignee in IssueAssignee.objects.filter( - issue=instance - ).values_list("assignee_id", flat=True) + for assignee in IssueAssignee.objects.filter(issue=instance).values_list("assignee_id", flat=True) ] if "labels" in self.fields: if "labels" in self.expand: data["labels"] = LabelSerializer( Label.objects.filter( - pk__in=IssueLabel.objects.filter(issue=instance).values_list( - "label_id", flat=True - ) + pk__in=IssueLabel.objects.filter(issue=instance).values_list("label_id", flat=True) ), many=True, ).data else: data["labels"] = [ - str(label) - for label in IssueLabel.objects.filter(issue=instance).values_list( - "label_id", flat=True - ) + str(label) for label in IssueLabel.objects.filter(issue=instance).values_list("label_id", flat=True) ] return data @@ -452,12 +421,8 @@ class IssueLinkCreateSerializer(BaseSerializer): # Validation if url already exists def create(self, validated_data): - if IssueLink.objects.filter( - url=validated_data.get("url"), issue_id=validated_data.get("issue_id") - ).exists(): - raise serializers.ValidationError( - {"error": "URL already exists for this Issue"} - ) + if IssueLink.objects.filter(url=validated_data.get("url"), issue_id=validated_data.get("issue_id")).exists(): + raise serializers.ValidationError({"error": "URL already exists for this Issue"}) return IssueLink.objects.create(**validated_data) @@ -478,15 +443,11 @@ class IssueLinkUpdateSerializer(IssueLinkCreateSerializer): def update(self, instance, validated_data): if ( - IssueLink.objects.filter( - url=validated_data.get("url"), issue_id=instance.issue_id - ) + IssueLink.objects.filter(url=validated_data.get("url"), issue_id=instance.issue_id) .exclude(pk=instance.id) .exists() ): - raise serializers.ValidationError( - {"error": "URL already exists for this Issue"} - ) + raise serializers.ValidationError({"error": "URL already exists for this Issue"}) return super().update(instance, validated_data) @@ -677,17 +638,13 @@ class IssueExpandSerializer(BaseSerializer): expand = self.context.get("expand", []) if "labels" in expand: # Use prefetched data - return LabelLiteSerializer( - [il.label for il in obj.label_issue.all()], many=True - ).data + return LabelLiteSerializer([il.label for il in obj.label_issue.all()], many=True).data return [il.label_id for il in obj.label_issue.all()] def get_assignees(self, obj): expand = self.context.get("expand", []) if "assignees" in expand: - return UserLiteSerializer( - [ia.assignee for ia in obj.issue_assignee.all()], many=True - ).data + return UserLiteSerializer([ia.assignee for ia in obj.issue_assignee.all()], many=True).data return [ia.assignee_id for ia in obj.issue_assignee.all()] class Meta: @@ -735,8 +692,6 @@ class IssueSearchSerializer(serializers.Serializer): id = serializers.CharField(required=True, help_text="Issue ID") name = serializers.CharField(required=True, help_text="Issue name") sequence_id = serializers.CharField(required=True, help_text="Issue sequence ID") - project__identifier = serializers.CharField( - required=True, help_text="Project identifier" - ) + project__identifier = serializers.CharField(required=True, help_text="Project identifier") project_id = serializers.CharField(required=True, help_text="Project ID") workspace__slug = serializers.CharField(required=True, help_text="Workspace slug") diff --git a/apps/api/plane/api/serializers/module.py b/apps/api/plane/api/serializers/module.py index 2cdd4c78f..77be453c8 100644 --- a/apps/api/plane/api/serializers/module.py +++ b/apps/api/plane/api/serializers/module.py @@ -76,9 +76,15 @@ class ModuleCreateSerializer(BaseSerializer): module_name = validated_data.get("name") if module_name: # Lookup for the module name in the module table for that project - if Module.objects.filter(name=module_name, project_id=project_id).exists(): + module = Module.objects.filter(name=module_name, project_id=project_id).first() + if module: raise serializers.ValidationError( - {"error": "Module with this name already exists"} + { + "id": str(module.id), + "code": "MODULE_NAME_ALREADY_EXISTS", + "error": "Module with this name already exists", + "message": "Module with this name already exists", + } ) module = Module.objects.create(**validated_data, project_id=project_id) @@ -123,14 +129,8 @@ class ModuleUpdateSerializer(ModuleCreateSerializer): module_name = validated_data.get("name") if module_name: # Lookup for the module name in the module table for that project - if ( - Module.objects.filter(name=module_name, project=instance.project) - .exclude(id=instance.id) - .exists() - ): - raise serializers.ValidationError( - {"error": "Module with this name already exists"} - ) + if Module.objects.filter(name=module_name, project=instance.project).exclude(id=instance.id).exists(): + raise serializers.ValidationError({"error": "Module with this name already exists"}) if members is not None: ModuleMember.objects.filter(module=instance).delete() @@ -240,12 +240,8 @@ class ModuleLinkSerializer(BaseSerializer): # Validation if url already exists def create(self, validated_data): - if ModuleLink.objects.filter( - url=validated_data.get("url"), module_id=validated_data.get("module_id") - ).exists(): - raise serializers.ValidationError( - {"error": "URL already exists for this Issue"} - ) + if ModuleLink.objects.filter(url=validated_data.get("url"), module_id=validated_data.get("module_id")).exists(): + raise serializers.ValidationError({"error": "URL already exists for this Issue"}) return ModuleLink.objects.create(**validated_data) diff --git a/apps/api/plane/api/serializers/project.py b/apps/api/plane/api/serializers/project.py index d860c46b2..3228c5ad9 100644 --- a/apps/api/plane/api/serializers/project.py +++ b/apps/api/plane/api/serializers/project.py @@ -66,9 +66,7 @@ class ProjectCreateSerializer(BaseSerializer): workspace_id=self.context["workspace_id"], member_id=data.get("project_lead"), ).exists(): - raise serializers.ValidationError( - "Project lead should be a user in the workspace" - ) + raise serializers.ValidationError("Project lead should be a user in the workspace") if data.get("default_assignee", None) is not None: # Check if the default assignee is a member of the workspace @@ -76,9 +74,7 @@ class ProjectCreateSerializer(BaseSerializer): workspace_id=self.context["workspace_id"], member_id=data.get("default_assignee"), ).exists(): - raise serializers.ValidationError( - "Default assignee should be a user in the workspace" - ) + raise serializers.ValidationError("Default assignee should be a user in the workspace") return data @@ -87,14 +83,10 @@ class ProjectCreateSerializer(BaseSerializer): if identifier == "": raise serializers.ValidationError(detail="Project Identifier is required") - if ProjectIdentifier.objects.filter( - name=identifier, workspace_id=self.context["workspace_id"] - ).exists(): + if ProjectIdentifier.objects.filter(name=identifier, workspace_id=self.context["workspace_id"]).exists(): raise serializers.ValidationError(detail="Project Identifier is taken") - project = Project.objects.create( - **validated_data, workspace_id=self.context["workspace_id"] - ) + project = Project.objects.create(**validated_data, workspace_id=self.context["workspace_id"]) return project @@ -119,25 +111,17 @@ class ProjectUpdateSerializer(ProjectCreateSerializer): """Update a project""" if ( validated_data.get("default_state", None) is not None - and not State.objects.filter( - project=instance, id=validated_data.get("default_state") - ).exists() + and not State.objects.filter(project=instance, id=validated_data.get("default_state")).exists() ): # Check if the default state is a state in the project - raise serializers.ValidationError( - "Default state should be a state in the project" - ) + raise serializers.ValidationError("Default state should be a state in the project") if ( validated_data.get("estimate", None) is not None - and not Estimate.objects.filter( - project=instance, id=validated_data.get("estimate") - ).exists() + and not Estimate.objects.filter(project=instance, id=validated_data.get("estimate")).exists() ): # Check if the estimate is a estimate in the project - raise serializers.ValidationError( - "Estimate should be a estimate in the project" - ) + raise serializers.ValidationError("Estimate should be a estimate in the project") return super().update(instance, validated_data) @@ -182,9 +166,7 @@ class ProjectSerializer(BaseSerializer): member_id=data.get("project_lead"), ).exists() ): - raise serializers.ValidationError( - "Project lead should be a user in the workspace" - ) + raise serializers.ValidationError("Project lead should be a user in the workspace") # Check default assignee should be a member of the workspace if ( @@ -194,23 +176,17 @@ class ProjectSerializer(BaseSerializer): member_id=data.get("default_assignee"), ).exists() ): - raise serializers.ValidationError( - "Default assignee should be a user in the workspace" - ) + raise serializers.ValidationError("Default assignee should be a user in the workspace") # Validate description content for security if "description_html" in data and data["description_html"]: if isinstance(data["description_html"], dict): - is_valid, error_msg, sanitized_html = validate_html_content( - str(data["description_html"]) - ) + is_valid, error_msg, sanitized_html = validate_html_content(str(data["description_html"])) # Update the data with sanitized HTML if available if sanitized_html is not None: data["description_html"] = sanitized_html if not is_valid: - raise serializers.ValidationError( - {"error": "html content is not valid"} - ) + raise serializers.ValidationError({"error": "html content is not valid"}) return data @@ -219,14 +195,10 @@ class ProjectSerializer(BaseSerializer): if identifier == "": raise serializers.ValidationError(detail="Project Identifier is required") - if ProjectIdentifier.objects.filter( - name=identifier, workspace_id=self.context["workspace_id"] - ).exists(): + if ProjectIdentifier.objects.filter(name=identifier, workspace_id=self.context["workspace_id"]).exists(): raise serializers.ValidationError(detail="Project Identifier is taken") - project = Project.objects.create( - **validated_data, workspace_id=self.context["workspace_id"] - ) + project = Project.objects.create(**validated_data, workspace_id=self.context["workspace_id"]) _ = ProjectIdentifier.objects.create( name=project.identifier, project=project, diff --git a/apps/api/plane/api/serializers/state.py b/apps/api/plane/api/serializers/state.py index 150c238fc..fc6aac15e 100644 --- a/apps/api/plane/api/serializers/state.py +++ b/apps/api/plane/api/serializers/state.py @@ -14,9 +14,7 @@ class StateSerializer(BaseSerializer): def validate(self, data): # If the default is being provided then make all other states default False if data.get("default", False): - State.objects.filter(project_id=self.context.get("project_id")).update( - default=False - ) + State.objects.filter(project_id=self.context.get("project_id")).update(default=False) return data class Meta: diff --git a/apps/api/plane/api/urls/intake.py b/apps/api/plane/api/urls/intake.py index 6af4aa4a8..5538467aa 100644 --- a/apps/api/plane/api/urls/intake.py +++ b/apps/api/plane/api/urls/intake.py @@ -14,9 +14,7 @@ urlpatterns = [ ), path( "workspaces//projects//intake-issues//", - IntakeIssueDetailAPIEndpoint.as_view( - http_method_names=["get", "patch", "delete"] - ), + IntakeIssueDetailAPIEndpoint.as_view(http_method_names=["get", "patch", "delete"]), name="intake-issue", ), ] diff --git a/apps/api/plane/api/urls/issue.py b/apps/api/plane/api/urls/issue.py index df64684de..8306a1315 100644 --- a/apps/api/plane/api/urls/issue.py +++ b/apps/api/plane/api/urls/issue.py @@ -55,9 +55,7 @@ urlpatterns = [ ), path( "workspaces//projects//issues//links//", - IssueLinkDetailAPIEndpoint.as_view( - http_method_names=["get", "patch", "delete"] - ), + IssueLinkDetailAPIEndpoint.as_view(http_method_names=["get", "patch", "delete"]), name="link", ), path( @@ -67,9 +65,7 @@ urlpatterns = [ ), path( "workspaces//projects//issues//comments//", - IssueCommentDetailAPIEndpoint.as_view( - http_method_names=["get", "patch", "delete"] - ), + IssueCommentDetailAPIEndpoint.as_view(http_method_names=["get", "patch", "delete"]), name="comment", ), path( @@ -89,9 +85,7 @@ urlpatterns = [ ), path( "workspaces//projects//issues//issue-attachments//", - IssueAttachmentDetailAPIEndpoint.as_view( - http_method_names=["get", "patch", "delete"] - ), + IssueAttachmentDetailAPIEndpoint.as_view(http_method_names=["get", "patch", "delete"]), name="issue-attachment", ), ] diff --git a/apps/api/plane/api/urls/project.py b/apps/api/plane/api/urls/project.py index 4cfc5a198..9cf9291aa 100644 --- a/apps/api/plane/api/urls/project.py +++ b/apps/api/plane/api/urls/project.py @@ -19,9 +19,7 @@ urlpatterns = [ ), path( "workspaces//projects//archive/", - ProjectArchiveUnarchiveAPIEndpoint.as_view( - http_method_names=["post", "delete"] - ), + ProjectArchiveUnarchiveAPIEndpoint.as_view(http_method_names=["post", "delete"]), name="project-archive-unarchive", ), ] diff --git a/apps/api/plane/api/views/asset.py b/apps/api/plane/api/views/asset.py index 815aa4503..a91ebc883 100644 --- a/apps/api/plane/api/views/asset.py +++ b/apps/api/plane/api/views/asset.py @@ -158,9 +158,7 @@ class UserAssetEndpoint(BaseAPIView): # 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( { @@ -236,9 +234,7 @@ class UserAssetEndpoint(BaseAPIView): 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) @@ -335,9 +331,7 @@ class UserServerAssetEndpoint(BaseAPIView): # Get the presigned URL storage = S3Storage(request=request, is_server=True) # 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( { @@ -389,16 +383,15 @@ class UserServerAssetEndpoint(BaseAPIView): def delete(self, request, asset_id): """Delete user server asset. - Delete a user profile asset (avatar or cover image) using server credentials and remove its reference from the user profile. - This performs a soft delete by marking the asset as deleted and updating the user's profile. + Delete a user profile asset (avatar or cover image) using server credentials and + remove its reference from the user profile. This performs a soft delete by marking the + asset as deleted and updating the user's profile. """ asset = FileAsset.objects.get(id=asset_id, user_id=request.user.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) @@ -430,9 +423,7 @@ class GenericAssetEndpoint(BaseAPIView): workspace = Workspace.objects.get(slug=slug) # Get the asset - asset = FileAsset.objects.get( - id=asset_id, workspace_id=workspace.id, is_deleted=False - ) + asset = FileAsset.objects.get(id=asset_id, workspace_id=workspace.id, is_deleted=False) # Check if the asset exists and is uploaded if not asset.is_uploaded: @@ -458,13 +449,9 @@ class GenericAssetEndpoint(BaseAPIView): ) except Workspace.DoesNotExist: - return Response( - {"error": "Workspace not found"}, status=status.HTTP_404_NOT_FOUND - ) + return Response({"error": "Workspace not found"}, status=status.HTTP_404_NOT_FOUND) except FileAsset.DoesNotExist: - return Response( - {"error": "Asset not found"}, status=status.HTTP_404_NOT_FOUND - ) + return Response({"error": "Asset not found"}, status=status.HTTP_404_NOT_FOUND) except Exception as e: log_exception(e) return Response( @@ -566,14 +553,12 @@ class GenericAssetEndpoint(BaseAPIView): created_by=request.user, external_id=external_id, external_source=external_source, - entity_type=FileAsset.EntityTypeContext.ISSUE_ATTACHMENT, # Using ISSUE_ATTACHMENT since we'll bind it to issues + entity_type=FileAsset.EntityTypeContext.ISSUE_ATTACHMENT, # Using ISSUE_ATTACHMENT since we'll bind it to issues # noqa: E501 ) # Get the presigned URL storage = S3Storage(request=request, is_server=True) - 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 Response( { @@ -612,9 +597,7 @@ class GenericAssetEndpoint(BaseAPIView): and trigger metadata extraction. """ try: - asset = FileAsset.objects.get( - id=asset_id, workspace__slug=slug, is_deleted=False - ) + asset = FileAsset.objects.get(id=asset_id, workspace__slug=slug, is_deleted=False) # Update is_uploaded status asset.is_uploaded = request.data.get("is_uploaded", asset.is_uploaded) @@ -627,6 +610,4 @@ class GenericAssetEndpoint(BaseAPIView): return Response(status=status.HTTP_204_NO_CONTENT) except FileAsset.DoesNotExist: - return Response( - {"error": "Asset not found"}, status=status.HTTP_404_NOT_FOUND - ) + return Response({"error": "Asset not found"}, status=status.HTTP_404_NOT_FOUND) diff --git a/apps/api/plane/api/views/base.py b/apps/api/plane/api/views/base.py index ea5bcba02..b3acbab36 100644 --- a/apps/api/plane/api/views/base.py +++ b/apps/api/plane/api/views/base.py @@ -37,9 +37,7 @@ class TimezoneMixin: timezone.deactivate() -class BaseAPIView( - TimezoneMixin, GenericAPIView, ReadReplicaControlMixin, BasePaginator -): +class BaseAPIView(TimezoneMixin, GenericAPIView, ReadReplicaControlMixin, BasePaginator): authentication_classes = [APIKeyAuthentication] permission_classes = [IsAuthenticated] @@ -56,9 +54,7 @@ class BaseAPIView( api_key = self.request.headers.get("X-Api-Key") if api_key: - service_token = APIToken.objects.filter( - token=api_key, is_service=True - ).first() + service_token = APIToken.objects.filter(token=api_key, is_service=True).first() if service_token: throttle_classes.append(ServiceTokenRateThrottle()) @@ -113,9 +109,7 @@ class BaseAPIView( if settings.DEBUG: from django.db import connection - print( - f"{request.method} - {request.get_full_path()} of Queries: {len(connection.queries)}" - ) + print(f"{request.method} - {request.get_full_path()} of Queries: {len(connection.queries)}") return response except Exception as exc: response = self.handle_exception(exc) @@ -151,14 +145,10 @@ class BaseAPIView( @property def fields(self): - fields = [ - field for field in self.request.GET.get("fields", "").split(",") if field - ] + fields = [field for field in self.request.GET.get("fields", "").split(",") if field] return fields if fields else None @property def expand(self): - expand = [ - expand for expand in self.request.GET.get("expand", "").split(",") if expand - ] + expand = [expand for expand in self.request.GET.get("expand", "").split(",") if expand] return expand if expand else None diff --git a/apps/api/plane/api/views/cycle.py b/apps/api/plane/api/views/cycle.py index e10d3d16e..17e8b6e66 100644 --- a/apps/api/plane/api/views/cycle.py +++ b/apps/api/plane/api/views/cycle.py @@ -171,7 +171,7 @@ class CycleListCreateAPIEndpoint(BaseAPIView): @cycle_docs( operation_id="list_cycles", summary="List cycles", - description="Retrieve all cycles in a project. Supports filtering by cycle status like current, upcoming, completed, or draft.", + description="Retrieve all cycles in a project. Supports filtering by cycle status like current, upcoming, completed, or draft.", # noqa: E501 parameters=[ CURSOR_PARAMETER, PER_PAGE_PARAMETER, @@ -201,9 +201,7 @@ class CycleListCreateAPIEndpoint(BaseAPIView): # 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, @@ -260,9 +258,7 @@ class CycleListCreateAPIEndpoint(BaseAPIView): # 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), @@ -289,7 +285,7 @@ class CycleListCreateAPIEndpoint(BaseAPIView): @cycle_docs( operation_id="create_cycle", summary="Create cycle", - description="Create a new development cycle with specified name, description, and date range. Supports external ID tracking for integration purposes.", + description="Create a new development cycle with specified name, description, and date range. Supports external ID tracking for integration purposes.", # noqa: E501 request=OpenApiRequest( request=CycleCreateSerializer, examples=[CYCLE_CREATE_EXAMPLE], @@ -308,12 +304,8 @@ class CycleListCreateAPIEndpoint(BaseAPIView): 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) if serializer.is_valid(): @@ -358,9 +350,7 @@ class CycleListCreateAPIEndpoint(BaseAPIView): 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, ) @@ -487,7 +477,7 @@ class CycleDetailAPIEndpoint(BaseAPIView): @cycle_docs( operation_id="update_cycle", summary="Update cycle", - description="Modify an existing cycle's properties like name, description, or date range. Completed cycles can only have their sort order changed.", + description="Modify an existing cycle's properties like name, description, or date range. Completed cycles can only have their sort order changed.", # noqa: E501 request=OpenApiRequest( request=CycleUpdateSerializer, examples=[CYCLE_UPDATE_EXAMPLE], @@ -508,9 +498,7 @@ class CycleDetailAPIEndpoint(BaseAPIView): """ 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( @@ -523,14 +511,10 @@ class CycleDetailAPIEndpoint(BaseAPIView): 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, ) @@ -542,9 +526,7 @@ class CycleDetailAPIEndpoint(BaseAPIView): 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() ): @@ -601,11 +583,7 @@ class CycleDetailAPIEndpoint(BaseAPIView): 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", @@ -625,9 +603,7 @@ class CycleDetailAPIEndpoint(BaseAPIView): # 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) @@ -765,15 +741,13 @@ class CycleArchiveUnarchiveAPIEndpoint(BaseAPIView): 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( operation_id="archive_cycle", summary="Archive cycle", - description="Move a completed cycle to archived status for historical tracking. Only cycles that have ended can be archived.", + description="Move a completed cycle to archived status for historical tracking. Only cycles that have ended can be archived.", # noqa: E501 request={}, responses={ 204: ARCHIVED_RESPONSE, @@ -786,9 +760,7 @@ class CycleArchiveUnarchiveAPIEndpoint(BaseAPIView): 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"}, @@ -819,9 +791,7 @@ class CycleArchiveUnarchiveAPIEndpoint(BaseAPIView): 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) @@ -884,9 +854,7 @@ class CycleIssueListCreateAPIEndpoint(BaseAPIView): # 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() @@ -923,15 +891,13 @@ class CycleIssueListCreateAPIEndpoint(BaseAPIView): 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( operation_id="add_cycle_work_items", summary="Add Work Items to Cycle", - description="Assign multiple work items to a cycle. Automatically handles bulk creation and updates with activity tracking.", + description="Assign multiple work items to a cycle. Automatically handles bulk creation and updates with activity tracking.", # noqa: E501 request=OpenApiRequest( request=CycleIssueRequestSerializer, examples=[CYCLE_ISSUE_REQUEST_EXAMPLE], @@ -955,22 +921,24 @@ class CycleIssueListCreateAPIEndpoint(BaseAPIView): if not issues: return Response( - {"error": "Work items are required"}, status=status.HTTP_400_BAD_REQUEST + {"error": "Work items are required", "code": "MISSING_WORK_ITEMS"}, 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( + { + "code": "CYCLE_COMPLETED", + "message": "The Cycle has already been completed so no new issues can be added", + }, + status=status.HTTP_400_BAD_REQUEST, + ) # 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)) @@ -1021,9 +989,7 @@ class CycleIssueListCreateAPIEndpoint(BaseAPIView): 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()), @@ -1099,9 +1065,7 @@ class CycleIssueDetailAPIEndpoint(BaseAPIView): 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( @@ -1154,7 +1118,7 @@ class TransferCycleIssueAPIEndpoint(BaseAPIView): @cycle_docs( operation_id="transfer_cycle_work_items", summary="Transfer cycle work items", - description="Move incomplete work items from the current cycle to a new target cycle. Captures progress snapshot and transfers only unfinished work items.", + description="Move incomplete work items from the current cycle to a new target cycle. Captures progress snapshot and transfers only unfinished work items.", # noqa: E501 request=OpenApiRequest( request=TransferCycleIssueRequestSerializer, examples=[TRANSFER_CYCLE_ISSUE_EXAMPLE], @@ -1207,14 +1171,10 @@ class TransferCycleIssueAPIEndpoint(BaseAPIView): status=status.HTTP_400_BAD_REQUEST, ) - 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() old_cycle = ( - Cycle.objects.filter( - workspace__slug=slug, project_id=project_id, pk=cycle_id - ) + Cycle.objects.filter(workspace__slug=slug, project_id=project_id, pk=cycle_id) .annotate( total_issues=Count( "issue_cycle", @@ -1324,9 +1284,7 @@ class TransferCycleIssueAPIEndpoint(BaseAPIView): ) ) .values("display_name", "assignee_id", "avatar", "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()), @@ -1353,9 +1311,7 @@ class TransferCycleIssueAPIEndpoint(BaseAPIView): 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": item.get("avatar", None), "avatar_url": item.get("avatar_url", None), "total_estimates": item["total_estimates"], @@ -1376,9 +1332,7 @@ class TransferCycleIssueAPIEndpoint(BaseAPIView): .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()), @@ -1445,19 +1399,13 @@ class TransferCycleIssueAPIEndpoint(BaseAPIView): ), ), # If `avatar_asset` is None, fall back to using `avatar` field directly - When( - assignees__avatar_asset__isnull=True, then="assignees__avatar" - ), + When(assignees__avatar_asset__isnull=True, then="assignees__avatar"), default=Value(None), output_field=models.CharField(), ) ) .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", @@ -1484,9 +1432,7 @@ class TransferCycleIssueAPIEndpoint(BaseAPIView): assignee_distribution_data = [ { "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": item.get("avatar", None), "avatar_url": item.get("avatar_url", None), "total_issues": item["total_issues"], @@ -1508,11 +1454,7 @@ class TransferCycleIssueAPIEndpoint(BaseAPIView): .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", @@ -1558,9 +1500,7 @@ class TransferCycleIssueAPIEndpoint(BaseAPIView): cycle_id=cycle_id, ) - 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, @@ -1588,9 +1528,7 @@ class TransferCycleIssueAPIEndpoint(BaseAPIView): if new_cycle.end_date is not None and new_cycle.end_date < timezone.now(): return Response( - { - "error": "The cycle where the issues are transferred is already completed" - }, + {"error": "The cycle where the issues are transferred is already completed"}, status=status.HTTP_400_BAD_REQUEST, ) @@ -1614,9 +1552,7 @@ class TransferCycleIssueAPIEndpoint(BaseAPIView): } ) - 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/api/views/intake.py b/apps/api/plane/api/views/intake.py index 6d82bd453..7a00fa431 100644 --- a/apps/api/plane/api/views/intake.py +++ b/apps/api/plane/api/views/intake.py @@ -62,9 +62,7 @@ class IntakeIssueListCreateAPIEndpoint(BaseAPIView): project_id=self.kwargs.get("project_id"), ).first() - project = Project.objects.get( - workspace__slug=self.kwargs.get("slug"), pk=self.kwargs.get("project_id") - ) + project = Project.objects.get(workspace__slug=self.kwargs.get("slug"), pk=self.kwargs.get("project_id")) if intake is None or not project.intake_view: return IntakeIssue.objects.none() @@ -83,7 +81,7 @@ class IntakeIssueListCreateAPIEndpoint(BaseAPIView): @intake_docs( operation_id="get_intake_work_items_list", summary="List intake work items", - description="Retrieve all work items in the project's intake queue. Returns paginated results when listing all intake work items.", + description="Retrieve all work items in the project's intake queue. Returns paginated results when listing all intake work items.", # noqa: E501 parameters=[ WORKSPACE_SLUG_PARAMETER, PROJECT_ID_PARAMETER, @@ -119,7 +117,7 @@ class IntakeIssueListCreateAPIEndpoint(BaseAPIView): @intake_docs( operation_id="create_intake_work_item", summary="Create intake work item", - description="Submit a new work item to the project's intake queue for review and triage. Automatically creates the work item with default triage state and tracks activity.", + description="Submit a new work item to the project's intake queue for review and triage. Automatically creates the work item with default triage state and tracks activity.", # noqa: E501 parameters=[ WORKSPACE_SLUG_PARAMETER, PROJECT_ID_PARAMETER, @@ -144,22 +142,16 @@ class IntakeIssueListCreateAPIEndpoint(BaseAPIView): Automatically creates the work item with default triage state and tracks activity. """ if not request.data.get("issue", {}).get("name", False): - return Response( - {"error": "Name is required"}, status=status.HTTP_400_BAD_REQUEST - ) + return Response({"error": "Name is required"}, status=status.HTTP_400_BAD_REQUEST) - intake = Intake.objects.filter( - workspace__slug=slug, project_id=project_id - ).first() + intake = Intake.objects.filter(workspace__slug=slug, project_id=project_id).first() project = Project.objects.get(workspace__slug=slug, pk=project_id) # Intake view if intake is None and not project.intake_view: return Response( - { - "error": "Intake is not enabled for this project enable it through the project's api" - }, + {"error": "Intake is not enabled for this project enable it through the project's api"}, status=status.HTTP_400_BAD_REQUEST, ) @@ -171,17 +163,13 @@ class IntakeIssueListCreateAPIEndpoint(BaseAPIView): "urgent", "none", ]: - return Response( - {"error": "Invalid priority"}, status=status.HTTP_400_BAD_REQUEST - ) + return Response({"error": "Invalid priority"}, status=status.HTTP_400_BAD_REQUEST) # create an issue issue = Issue.objects.create( name=request.data.get("issue", {}).get("name"), description=request.data.get("issue", {}).get("description", {}), - description_html=request.data.get("issue", {}).get( - "description_html", "

" - ), + description_html=request.data.get("issue", {}).get("description_html", "

"), priority=request.data.get("issue", {}).get("priority", "none"), project_id=project_id, ) @@ -226,9 +214,7 @@ class IntakeIssueDetailAPIEndpoint(BaseAPIView): project_id=self.kwargs.get("project_id"), ).first() - project = Project.objects.get( - workspace__slug=self.kwargs.get("slug"), pk=self.kwargs.get("project_id") - ) + project = Project.objects.get(workspace__slug=self.kwargs.get("slug"), pk=self.kwargs.get("project_id")) if intake is None or not project.intake_view: return IntakeIssue.objects.none() @@ -267,15 +253,13 @@ class IntakeIssueDetailAPIEndpoint(BaseAPIView): Retrieve details of a specific intake work item. """ intake_issue_queryset = self.get_queryset().get(issue_id=issue_id) - intake_issue_data = IntakeIssueSerializer( - intake_issue_queryset, fields=self.fields, expand=self.expand - ).data + intake_issue_data = IntakeIssueSerializer(intake_issue_queryset, fields=self.fields, expand=self.expand).data return Response(intake_issue_data, status=status.HTTP_200_OK) @intake_docs( operation_id="update_intake_work_item", summary="Update intake work item", - description="Modify an existing intake work item's properties or status for triage processing. Supports status changes like accept, reject, or mark as duplicate.", + description="Modify an existing intake work item's properties or status for triage processing. Supports status changes like accept, reject, or mark as duplicate.", # noqa: E501 parameters=[ WORKSPACE_SLUG_PARAMETER, PROJECT_ID_PARAMETER, @@ -300,18 +284,14 @@ class IntakeIssueDetailAPIEndpoint(BaseAPIView): Modify an existing intake work item's properties or status for triage processing. Supports status changes like accept, reject, or mark as duplicate. """ - intake = Intake.objects.filter( - workspace__slug=slug, project_id=project_id - ).first() + intake = Intake.objects.filter(workspace__slug=slug, project_id=project_id).first() project = Project.objects.get(workspace__slug=slug, pk=project_id) # Intake view if intake is None and not project.intake_view: return Response( - { - "error": "Intake is not enabled for this project enable it through the project's api" - }, + {"error": "Intake is not enabled for this project enable it through the project's api"}, status=status.HTTP_400_BAD_REQUEST, ) @@ -332,9 +312,7 @@ class IntakeIssueDetailAPIEndpoint(BaseAPIView): ) # Only project members admins and created_by users can access this endpoint - if project_member.role <= 5 and str(intake_issue.created_by_id) != str( - request.user.id - ): + if project_member.role <= 5 and str(intake_issue.created_by_id) != str(request.user.id): return Response( {"error": "You cannot edit intake work items"}, status=status.HTTP_400_BAD_REQUEST, @@ -349,10 +327,7 @@ class IntakeIssueDetailAPIEndpoint(BaseAPIView): ArrayAgg( "labels__id", distinct=True, - filter=Q( - ~Q(labels__id__isnull=True) - & Q(label_issue__deleted_at__isnull=True) - ), + filter=Q(~Q(labels__id__isnull=True) & Q(label_issue__deleted_at__isnull=True)), ), Value([], output_field=ArrayField(UUIDField())), ), @@ -373,9 +348,7 @@ class IntakeIssueDetailAPIEndpoint(BaseAPIView): if project_member.role <= 5: issue_data = { "name": issue_data.get("name", issue.name), - "description_html": issue_data.get( - "description_html", issue.description_html - ), + "description_html": issue_data.get("description_html", issue.description_html), "description": issue_data.get("description", issue.description), } @@ -401,45 +374,31 @@ class IntakeIssueDetailAPIEndpoint(BaseAPIView): ) issue_serializer.save() else: - return Response( - issue_serializer.errors, status=status.HTTP_400_BAD_REQUEST - ) + return Response(issue_serializer.errors, status=status.HTTP_400_BAD_REQUEST) # Only project admins and members can edit intake issue attributes if project_member.role > 15: - serializer = IntakeIssueUpdateSerializer( - intake_issue, data=request.data, partial=True - ) - current_instance = json.dumps( - IntakeIssueSerializer(intake_issue).data, cls=DjangoJSONEncoder - ) + serializer = IntakeIssueUpdateSerializer(intake_issue, data=request.data, partial=True) + current_instance = json.dumps(IntakeIssueSerializer(intake_issue).data, cls=DjangoJSONEncoder) if serializer.is_valid(): serializer.save() # Update the issue state if the issue is rejected or marked as duplicate if serializer.data["status"] in [-1, 2]: - issue = Issue.objects.get( - pk=issue_id, workspace__slug=slug, project_id=project_id - ) - state = State.objects.filter( - group="cancelled", workspace__slug=slug, project_id=project_id - ).first() + issue = Issue.objects.get(pk=issue_id, workspace__slug=slug, project_id=project_id) + state = State.objects.filter(group="cancelled", workspace__slug=slug, project_id=project_id).first() if state is not None: issue.state = state issue.save() # Update the issue state if it is accepted if serializer.data["status"] in [1]: - issue = Issue.objects.get( - pk=issue_id, workspace__slug=slug, project_id=project_id - ) + issue = Issue.objects.get(pk=issue_id, workspace__slug=slug, project_id=project_id) # Update the issue state only if it is in triage state if issue.state.is_triage: # Move to default state - state = State.objects.filter( - workspace__slug=slug, project_id=project_id, default=True - ).first() + state = State.objects.filter(workspace__slug=slug, project_id=project_id, default=True).first() if state is not None: issue.state = state issue.save() @@ -461,14 +420,12 @@ class IntakeIssueDetailAPIEndpoint(BaseAPIView): return Response(serializer.data, status=status.HTTP_200_OK) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) else: - return Response( - IntakeIssueSerializer(intake_issue).data, status=status.HTTP_200_OK - ) + return Response(IntakeIssueSerializer(intake_issue).data, status=status.HTTP_200_OK) @intake_docs( operation_id="delete_intake_work_item", summary="Delete intake work item", - description="Permanently remove an intake work item from the triage queue. Also deletes the underlying work item if it hasn't been accepted yet.", + description="Permanently remove an intake work item from the triage queue. Also deletes the underlying work item if it hasn't been accepted yet.", # noqa: E501 parameters=[ WORKSPACE_SLUG_PARAMETER, PROJECT_ID_PARAMETER, @@ -484,18 +441,14 @@ class IntakeIssueDetailAPIEndpoint(BaseAPIView): Permanently remove an intake work item from the triage queue. Also deletes the underlying work item if it hasn't been accepted yet. """ - intake = Intake.objects.filter( - workspace__slug=slug, project_id=project_id - ).first() + intake = Intake.objects.filter(workspace__slug=slug, project_id=project_id).first() project = Project.objects.get(workspace__slug=slug, pk=project_id) # Intake view if intake is None and not project.intake_view: return Response( - { - "error": "Intake is not enabled for this project enable it through the project's api" - }, + {"error": "Intake is not enabled for this project enable it through the project's api"}, status=status.HTTP_400_BAD_REQUEST, ) @@ -510,9 +463,7 @@ class IntakeIssueDetailAPIEndpoint(BaseAPIView): # Check the issue status if intake_issue.status in [-2, -1, 0, 2]: # Delete the issue also - issue = Issue.objects.filter( - workspace__slug=slug, project_id=project_id, pk=issue_id - ).first() + issue = Issue.objects.filter(workspace__slug=slug, project_id=project_id, pk=issue_id).first() if issue.created_by_id != request.user.id and ( not ProjectMember.objects.filter( workspace__slug=slug, diff --git a/apps/api/plane/api/views/issue.py b/apps/api/plane/api/views/issue.py index 6b7bd7a90..d3686ceea 100644 --- a/apps/api/plane/api/views/issue.py +++ b/apps/api/plane/api/views/issue.py @@ -142,9 +142,8 @@ from plane.utils.openapi import ( ) from plane.bgtasks.work_item_link_task import crawl_work_item_link_title -def user_has_issue_permission( - user_id, project_id, issue=None, allowed_roles=None, allow_creator=True -): + +def user_has_issue_permission(user_id, project_id, issue=None, allowed_roles=None, allow_creator=True): if allow_creator and issue is not None and user_id == issue.created_by_id: return True @@ -269,7 +268,7 @@ class IssueListCreateAPIEndpoint(BaseAPIView): @work_item_docs( operation_id="list_work_items", summary="List work items", - description="Retrieve a paginated list of all work items in a project. Supports filtering, ordering, and field selection through query parameters.", + description="Retrieve a paginated list of all work items in a project. Supports filtering, ordering, and field selection through query parameters.", # noqa: E501 parameters=[ CURSOR_PARAMETER, PER_PAGE_PARAMETER, @@ -322,9 +321,7 @@ class IssueListCreateAPIEndpoint(BaseAPIView): self.get_queryset() .annotate( cycle_id=Subquery( - CycleIssue.objects.filter( - issue=OuterRef("id"), deleted_at__isnull=True - ).values("cycle_id")[:1] + CycleIssue.objects.filter(issue=OuterRef("id"), deleted_at__isnull=True).values("cycle_id")[:1] ) ) .annotate( @@ -344,21 +341,14 @@ class IssueListCreateAPIEndpoint(BaseAPIView): ) ) - total_issue_queryset = Issue.issue_objects.filter( - project_id=project_id, workspace__slug=slug - ) + total_issue_queryset = Issue.issue_objects.filter(project_id=project_id, workspace__slug=slug) # Priority Ordering if order_by_param == "priority" or order_by_param == "-priority": - priority_order = ( - priority_order if order_by_param == "priority" else priority_order[::-1] - ) + priority_order = priority_order if order_by_param == "priority" else priority_order[::-1] issue_queryset = issue_queryset.annotate( priority_order=Case( - *[ - When(priority=p, then=Value(i)) - for i, p in enumerate(priority_order) - ], + *[When(priority=p, then=Value(i)) for i, p in enumerate(priority_order)], output_field=CharField(), ) ).order_by("priority_order") @@ -370,17 +360,10 @@ class IssueListCreateAPIEndpoint(BaseAPIView): "-state__name", "-state__group", ]: - state_order = ( - state_order - if order_by_param in ["state__name", "state__group"] - else state_order[::-1] - ) + state_order = state_order if order_by_param in ["state__name", "state__group"] else state_order[::-1] issue_queryset = issue_queryset.annotate( state_order=Case( - *[ - When(state__group=state_group, then=Value(i)) - for i, state_group in enumerate(state_order) - ], + *[When(state__group=state_group, then=Value(i)) for i, state_group in enumerate(state_order)], default=Value(len(state_order)), output_field=CharField(), ) @@ -393,14 +376,8 @@ class IssueListCreateAPIEndpoint(BaseAPIView): "-assignees__first_name", ]: issue_queryset = issue_queryset.annotate( - max_values=Max( - order_by_param[1::] - if order_by_param.startswith("-") - else order_by_param - ) - ).order_by( - "-max_values" if order_by_param.startswith("-") else "max_values" - ) + max_values=Max(order_by_param[1::] if order_by_param.startswith("-") else order_by_param) + ).order_by("-max_values" if order_by_param.startswith("-") else "max_values") else: issue_queryset = issue_queryset.order_by(order_by_param) @@ -408,9 +385,7 @@ class IssueListCreateAPIEndpoint(BaseAPIView): request=request, queryset=(issue_queryset), total_count_queryset=total_issue_queryset, - 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, ) @work_item_docs( @@ -476,9 +451,7 @@ class IssueListCreateAPIEndpoint(BaseAPIView): serializer.save() # Refetch the issue - issue = Issue.objects.filter( - workspace__slug=slug, project_id=project_id, pk=serializer.data["id"] - ).first() + issue = Issue.objects.filter(workspace__slug=slug, project_id=project_id, pk=serializer.data["id"]).first() issue.created_at = request.data.get("created_at", timezone.now()) issue.created_by_id = request.data.get("created_by", request.user.id) issue.save(update_fields=["created_at", "created_by"]) @@ -579,7 +552,7 @@ class IssueDetailAPIEndpoint(BaseAPIView): @work_item_docs( operation_id="put_work_item", summary="Update or create work item", - description="Update an existing work item identified by external ID and source, or create a new one if it doesn't exist. Requires external_id and external_source parameters for identification.", + description="Update an existing work item identified by external ID and source, or create a new one if it doesn't exist. Requires external_id and external_source parameters for identification.", # noqa: E501 request=OpenApiRequest( request=IssueSerializer, examples=[ISSUE_UPSERT_EXAMPLE], @@ -625,9 +598,7 @@ class IssueDetailAPIEndpoint(BaseAPIView): # Get the current instance of the issue in order to track # changes and dispatch the issue activity - current_instance = json.dumps( - IssueSerializer(issue).data, cls=DjangoJSONEncoder - ) + current_instance = json.dumps(IssueSerializer(issue).data, cls=DjangoJSONEncoder) # Get the requested data, encode it as django object and pass it # to serializer to validation @@ -690,16 +661,12 @@ class IssueDetailAPIEndpoint(BaseAPIView): # the issue with the provided data, else return with the # default states given. issue.created_at = request.data.get("created_at", timezone.now()) - issue.created_by_id = request.data.get( - "created_by", request.user.id - ) + issue.created_by_id = request.data.get("created_by", request.user.id) issue.save(update_fields=["created_at", "created_by"]) issue_activity.delay( type="issue.activity.created", - requested_data=json.dumps( - self.request.data, cls=DjangoJSONEncoder - ), + requested_data=json.dumps(self.request.data, cls=DjangoJSONEncoder), actor_id=str(request.user.id), issue_id=str(serializer.data.get("id", None)), project_id=str(project_id), @@ -717,7 +684,7 @@ class IssueDetailAPIEndpoint(BaseAPIView): @work_item_docs( operation_id="update_work_item", summary="Partially update work item", - description="Partially update an existing work item with the provided fields. Supports external ID validation to prevent conflicts.", + description="Partially update an existing work item with the provided fields. Supports external ID validation to prevent conflicts.", # noqa: E501 parameters=[ PROJECT_ID_PARAMETER, ], @@ -744,9 +711,7 @@ class IssueDetailAPIEndpoint(BaseAPIView): """ issue = Issue.objects.get(workspace__slug=slug, project_id=project_id, pk=pk) project = Project.objects.get(pk=project_id) - current_instance = json.dumps( - IssueSerializer(issue).data, cls=DjangoJSONEncoder - ) + current_instance = json.dumps(IssueSerializer(issue).data, cls=DjangoJSONEncoder) requested_data = json.dumps(self.request.data, cls=DjangoJSONEncoder) serializer = IssueSerializer( issue, @@ -761,9 +726,7 @@ class IssueDetailAPIEndpoint(BaseAPIView): and Issue.objects.filter( project_id=project_id, workspace__slug=slug, - external_source=request.data.get( - "external_source", issue.external_source - ), + external_source=request.data.get("external_source", issue.external_source), external_id=request.data.get("external_id"), ).exists() ): @@ -791,7 +754,7 @@ class IssueDetailAPIEndpoint(BaseAPIView): @work_item_docs( operation_id="delete_work_item", summary="Delete work item", - description="Permanently delete an existing work item from the project. Only admins or the item creator can perform this action.", + description="Permanently delete an existing work item from the project. Only admins or the item creator can perform this action.", # noqa: E501 parameters=[ PROJECT_ID_PARAMETER, ], @@ -821,9 +784,7 @@ class IssueDetailAPIEndpoint(BaseAPIView): {"error": "Only admin or creator can delete the work item"}, status=status.HTTP_403_FORBIDDEN, ) - current_instance = json.dumps( - IssueSerializer(issue).data, cls=DjangoJSONEncoder - ) + current_instance = json.dumps(IssueSerializer(issue).data, cls=DjangoJSONEncoder) issue.delete() issue_activity.delay( type="issue.activity.deleted", @@ -959,9 +920,7 @@ class LabelListCreateAPIEndpoint(BaseAPIView): return self.paginate( request=request, queryset=(self.get_queryset()), - on_results=lambda labels: LabelSerializer( - labels, many=True, fields=self.fields, expand=self.expand - ).data, + on_results=lambda labels: LabelSerializer(labels, many=True, fields=self.fields, expand=self.expand).data, ) @@ -1033,9 +992,7 @@ class LabelDetailAPIEndpoint(LabelListCreateAPIEndpoint): and Label.objects.filter( project_id=project_id, workspace__slug=slug, - external_source=request.data.get( - "external_source", label.external_source - ), + external_source=request.data.get("external_source", label.external_source), external_id=request.data.get("external_id"), ) .exclude(id=pk) @@ -1162,9 +1119,7 @@ class IssueLinkListCreateAPIEndpoint(BaseAPIView): serializer = IssueLinkCreateSerializer(data=request.data) if serializer.is_valid(): serializer.save(project_id=project_id, issue_id=issue_id) - crawl_work_item_link_title.delay( - serializer.instance.id, serializer.instance.url - ) + crawl_work_item_link_title.delay(serializer.instance.id, serializer.instance.url) link = IssueLink.objects.get(pk=serializer.instance.id) link.created_by_id = request.data.get("created_by", request.user.id) link.save(update_fields=["created_by"]) @@ -1233,9 +1188,7 @@ class IssueLinkDetailAPIEndpoint(BaseAPIView): """ if pk is None: issue_links = self.get_queryset() - serializer = IssueLinkSerializer( - issue_links, fields=self.fields, expand=self.expand - ) + serializer = IssueLinkSerializer(issue_links, fields=self.fields, expand=self.expand) return self.paginate( request=request, queryset=(self.get_queryset()), @@ -1244,9 +1197,7 @@ class IssueLinkDetailAPIEndpoint(BaseAPIView): ).data, ) issue_link = self.get_queryset().get(pk=pk) - serializer = IssueLinkSerializer( - issue_link, fields=self.fields, expand=self.expand - ) + serializer = IssueLinkSerializer(issue_link, fields=self.fields, expand=self.expand) return Response(serializer.data, status=status.HTTP_200_OK) @issue_link_docs( @@ -1276,19 +1227,13 @@ class IssueLinkDetailAPIEndpoint(BaseAPIView): Modify the URL, title, or metadata of an existing issue link. Tracks all changes in issue activity logs. """ - issue_link = IssueLink.objects.get( - workspace__slug=slug, project_id=project_id, issue_id=issue_id, pk=pk - ) + issue_link = IssueLink.objects.get(workspace__slug=slug, project_id=project_id, issue_id=issue_id, pk=pk) requested_data = json.dumps(request.data, cls=DjangoJSONEncoder) - current_instance = json.dumps( - IssueLinkSerializer(issue_link).data, cls=DjangoJSONEncoder - ) + current_instance = json.dumps(IssueLinkSerializer(issue_link).data, cls=DjangoJSONEncoder) serializer = IssueLinkSerializer(issue_link, data=request.data, partial=True) if serializer.is_valid(): serializer.save() - crawl_work_item_link_title.delay( - serializer.data.get("id"), serializer.data.get("url") - ) + crawl_work_item_link_title.delay(serializer.data.get("id"), serializer.data.get("url")) issue_activity.delay( type="link.activity.updated", requested_data=requested_data, @@ -1320,12 +1265,8 @@ class IssueLinkDetailAPIEndpoint(BaseAPIView): Permanently remove an external link from a work item. Records deletion activity for audit purposes. """ - issue_link = IssueLink.objects.get( - workspace__slug=slug, project_id=project_id, issue_id=issue_id, pk=pk - ) - current_instance = json.dumps( - IssueLinkSerializer(issue_link).data, cls=DjangoJSONEncoder - ) + issue_link = IssueLink.objects.get(workspace__slug=slug, project_id=project_id, issue_id=issue_id, pk=pk) + current_instance = json.dumps(IssueLinkSerializer(issue_link).data, cls=DjangoJSONEncoder) issue_activity.delay( type="link.activity.deleted", requested_data=json.dumps({"link_id": str(pk)}), @@ -1461,15 +1402,12 @@ class IssueCommentListCreateAPIEndpoint(BaseAPIView): serializer = IssueCommentCreateSerializer(data=request.data) if serializer.is_valid(): - serializer.save( - project_id=project_id, issue_id=issue_id, actor=request.user - ) + serializer.save(project_id=project_id, issue_id=issue_id, actor=request.user) issue_comment = IssueComment.objects.get(pk=serializer.instance.id) # Update the created_at and the created_by and save the comment issue_comment.created_at = request.data.get("created_at", timezone.now()) - issue_comment.created_by_id = request.data.get( - "created_by", request.user.id - ) + issue_comment.created_by_id = request.data.get("created_by", request.user.id) + issue_comment.actor_id = request.data.get("created_by", request.user.id) issue_comment.save(update_fields=["created_at", "created_by"]) issue_activity.delay( @@ -1555,9 +1493,7 @@ class IssueCommentDetailAPIEndpoint(BaseAPIView): Retrieve details of a specific comment. """ issue_comment = self.get_queryset().get(pk=pk) - serializer = IssueCommentSerializer( - issue_comment, fields=self.fields, expand=self.expand - ) + serializer = IssueCommentSerializer(issue_comment, fields=self.fields, expand=self.expand) return Response(serializer.data, status=status.HTTP_200_OK) @issue_comment_docs( @@ -1588,13 +1524,9 @@ class IssueCommentDetailAPIEndpoint(BaseAPIView): Modify the content of an existing comment on a work item. Validates external ID uniqueness if provided. """ - issue_comment = IssueComment.objects.get( - workspace__slug=slug, project_id=project_id, issue_id=issue_id, pk=pk - ) + issue_comment = IssueComment.objects.get(workspace__slug=slug, project_id=project_id, issue_id=issue_id, pk=pk) requested_data = json.dumps(self.request.data, cls=DjangoJSONEncoder) - current_instance = json.dumps( - IssueCommentSerializer(issue_comment).data, cls=DjangoJSONEncoder - ) + current_instance = json.dumps(IssueCommentSerializer(issue_comment).data, cls=DjangoJSONEncoder) # Validation check if the issue already exists if ( @@ -1603,9 +1535,7 @@ class IssueCommentDetailAPIEndpoint(BaseAPIView): and IssueComment.objects.filter( project_id=project_id, workspace__slug=slug, - external_source=request.data.get( - "external_source", issue_comment.external_source - ), + external_source=request.data.get("external_source", issue_comment.external_source), external_id=request.data.get("external_id"), ).exists() ): @@ -1617,9 +1547,7 @@ class IssueCommentDetailAPIEndpoint(BaseAPIView): status=status.HTTP_409_CONFLICT, ) - serializer = IssueCommentCreateSerializer( - issue_comment, data=request.data, partial=True - ) + serializer = IssueCommentCreateSerializer(issue_comment, data=request.data, partial=True) if serializer.is_valid(): serializer.save() issue_activity.delay( @@ -1665,12 +1593,8 @@ class IssueCommentDetailAPIEndpoint(BaseAPIView): Permanently remove a comment from a work item. Records deletion activity for audit purposes. """ - issue_comment = IssueComment.objects.get( - workspace__slug=slug, project_id=project_id, issue_id=issue_id, pk=pk - ) - current_instance = json.dumps( - IssueCommentSerializer(issue_comment).data, cls=DjangoJSONEncoder - ) + issue_comment = IssueComment.objects.get(workspace__slug=slug, project_id=project_id, issue_id=issue_id, pk=pk) + current_instance = json.dumps(IssueCommentSerializer(issue_comment).data, cls=DjangoJSONEncoder) issue_comment.delete() issue_activity.delay( type="comment.activity.deleted", @@ -1717,9 +1641,7 @@ class IssueActivityListAPIEndpoint(BaseAPIView): Excludes comment, vote, reaction, and draft activities. """ issue_activities = ( - IssueActivity.objects.filter( - issue_id=issue_id, workspace__slug=slug, project_id=project_id - ) + IssueActivity.objects.filter(issue_id=issue_id, workspace__slug=slug, project_id=project_id) .filter( ~Q(field__in=["comment", "vote", "reaction", "draft"]), project__project_projectmember__member=self.request.user, @@ -1774,9 +1696,7 @@ class IssueActivityDetailAPIEndpoint(BaseAPIView): Excludes comment, vote, reaction, and draft activities. """ issue_activities = ( - IssueActivity.objects.filter( - issue_id=issue_id, workspace__slug=slug, project_id=project_id - ) + IssueActivity.objects.filter(issue_id=issue_id, workspace__slug=slug, project_id=project_id) .filter( ~Q(field__in=["comment", "vote", "reaction", "draft"]), project__project_projectmember__member=self.request.user, @@ -1866,12 +1786,8 @@ class IssueAttachmentListCreateAPIEndpoint(BaseAPIView): name="Workspace not found", value={"error": "Workspace not found"}, ), - OpenApiExample( - name="Project not found", value={"error": "Project not found"} - ), - OpenApiExample( - name="Issue not found", value={"error": "Issue not found"} - ), + OpenApiExample(name="Project not found", value={"error": "Project not found"}), + OpenApiExample(name="Issue not found", value={"error": "Issue not found"}), ], ), }, @@ -1882,9 +1798,7 @@ class IssueAttachmentListCreateAPIEndpoint(BaseAPIView): Generate presigned URL for uploading file attachments to a work item. Validates file type and size before creating the attachment record. """ - issue = Issue.objects.get( - pk=issue_id, workspace__slug=slug, project_id=project_id - ) + issue = Issue.objects.get(pk=issue_id, workspace__slug=slug, project_id=project_id) # if the user is creator or admin,member then allow the upload if not user_has_issue_permission( request.user.id, @@ -1970,9 +1884,7 @@ class IssueAttachmentListCreateAPIEndpoint(BaseAPIView): # 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( { @@ -2032,9 +1944,7 @@ class IssueAttachmentDetailAPIEndpoint(BaseAPIView): ATTACHMENT_ID_PARAMETER, ], responses={ - 204: OpenApiResponse( - description="Work item attachment deleted successfully" - ), + 204: OpenApiResponse(description="Work item attachment deleted successfully"), 404: ATTACHMENT_NOT_FOUND_RESPONSE, }, ) @@ -2044,9 +1954,7 @@ class IssueAttachmentDetailAPIEndpoint(BaseAPIView): Soft delete an attachment from a work item by marking it as deleted. Records deletion activity and triggers metadata cleanup. """ - issue = Issue.objects.get( - pk=issue_id, workspace__slug=slug, project_id=project_id - ) + issue = Issue.objects.get(pk=issue_id, workspace__slug=slug, project_id=project_id) # if the request user is creator or admin then delete the attachment if not user_has_issue_permission( request.user, @@ -2060,9 +1968,7 @@ class IssueAttachmentDetailAPIEndpoint(BaseAPIView): status=status.HTTP_403_FORBIDDEN, ) - issue_attachment = FileAsset.objects.get( - pk=pk, workspace__slug=slug, project_id=project_id - ) + issue_attachment = FileAsset.objects.get(pk=pk, workspace__slug=slug, project_id=project_id) issue_attachment.is_deleted = True issue_attachment.deleted_at = timezone.now() issue_attachment.save() @@ -2136,9 +2042,7 @@ class IssueAttachmentDetailAPIEndpoint(BaseAPIView): ) # 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 if the asset is uploaded if not asset.is_uploaded: @@ -2176,9 +2080,7 @@ class IssueAttachmentDetailAPIEndpoint(BaseAPIView): examples=[ATTACHMENT_UPLOAD_CONFIRM_EXAMPLE], ), responses={ - 204: OpenApiResponse( - description="Work item attachment uploaded successfully" - ), + 204: OpenApiResponse(description="Work item attachment uploaded successfully"), 400: INVALID_REQUEST_RESPONSE, 404: ATTACHMENT_NOT_FOUND_RESPONSE, }, @@ -2190,9 +2092,7 @@ class IssueAttachmentDetailAPIEndpoint(BaseAPIView): Triggers activity logging and metadata extraction. """ - issue = Issue.objects.get( - pk=issue_id, workspace__slug=slug, project_id=project_id - ) + issue = Issue.objects.get(pk=issue_id, workspace__slug=slug, project_id=project_id) # if the user is creator or admin then allow the upload if not user_has_issue_permission( request.user, @@ -2206,9 +2106,7 @@ class IssueAttachmentDetailAPIEndpoint(BaseAPIView): status=status.HTTP_403_FORBIDDEN, ) - issue_attachment = FileAsset.objects.get( - pk=pk, workspace__slug=slug, project_id=project_id - ) + issue_attachment = FileAsset.objects.get(pk=pk, workspace__slug=slug, project_id=project_id) serializer = IssueAttachmentSerializer(issue_attachment) # Send this activity only if the attachment is not uploaded before diff --git a/apps/api/plane/api/views/member.py b/apps/api/plane/api/views/member.py index 8ae662520..f761d5c91 100644 --- a/apps/api/plane/api/views/member.py +++ b/apps/api/plane/api/views/member.py @@ -74,9 +74,7 @@ class WorkspaceMemberAPIEndpoint(BaseAPIView): status=status.HTTP_400_BAD_REQUEST, ) - workspace_members = WorkspaceMember.objects.filter( - workspace__slug=slug - ).select_related("member") + workspace_members = WorkspaceMember.objects.filter(workspace__slug=slug).select_related("member") # Get all the users with their roles users_with_roles = [] @@ -125,13 +123,11 @@ class ProjectMemberAPIEndpoint(BaseAPIView): ) # Get the workspace members that are present inside the workspace - project_members = ProjectMember.objects.filter( - project_id=project_id, workspace__slug=slug - ).values_list("member_id", flat=True) + project_members = ProjectMember.objects.filter(project_id=project_id, workspace__slug=slug).values_list( + "member_id", flat=True + ) # Get all the users that are present inside the workspace - users = UserLiteSerializer( - User.objects.filter(id__in=project_members), many=True - ).data + users = UserLiteSerializer(User.objects.filter(id__in=project_members), many=True).data return Response(users, status=status.HTTP_200_OK) diff --git a/apps/api/plane/api/views/module.py b/apps/api/plane/api/views/module.py index e00a624c7..d79b94084 100644 --- a/apps/api/plane/api/views/module.py +++ b/apps/api/plane/api/views/module.py @@ -394,9 +394,7 @@ class ModuleDetailAPIEndpoint(BaseAPIView): examples=[MODULE_UPDATE_EXAMPLE], ), 404: OpenApiResponse(description="Module not found"), - 409: OpenApiResponse( - description="Module with same external ID already exists" - ), + 409: OpenApiResponse(description="Module with same external ID already exists"), }, ) def patch(self, request, slug, project_id, pk): @@ -407,18 +405,14 @@ class ModuleDetailAPIEndpoint(BaseAPIView): """ module = Module.objects.get(pk=pk, project_id=project_id, workspace__slug=slug) - current_instance = json.dumps( - ModuleSerializer(module).data, cls=DjangoJSONEncoder - ) + current_instance = json.dumps(ModuleSerializer(module).data, cls=DjangoJSONEncoder) if module.archived_at: return Response( {"error": "Archived module cannot be edited"}, status=status.HTTP_400_BAD_REQUEST, ) - serializer = ModuleSerializer( - module, data=request.data, context={"project_id": project_id}, partial=True - ) + serializer = ModuleSerializer(module, data=request.data, context={"project_id": project_id}, partial=True) if serializer.is_valid(): if ( request.data.get("external_id") @@ -426,9 +420,7 @@ class ModuleDetailAPIEndpoint(BaseAPIView): and Module.objects.filter( project_id=project_id, workspace__slug=slug, - external_source=request.data.get( - "external_source", module.external_source - ), + external_source=request.data.get("external_source", module.external_source), external_id=request.data.get("external_id"), ).exists() ): @@ -514,9 +506,7 @@ class ModuleDetailAPIEndpoint(BaseAPIView): status=status.HTTP_403_FORBIDDEN, ) - module_issues = list( - ModuleIssue.objects.filter(module_id=pk).values_list("issue", flat=True) - ) + module_issues = list(ModuleIssue.objects.filter(module_id=pk).values_list("issue", flat=True)) issue_activity.delay( type="module.activity.deleted", requested_data=json.dumps( @@ -537,9 +527,7 @@ class ModuleDetailAPIEndpoint(BaseAPIView): # Delete the module issues ModuleIssue.objects.filter(module=pk, project_id=project_id).delete() # Delete the user favorite module - UserFavorite.objects.filter( - entity_type="module", entity_identifier=pk, project_id=project_id - ).delete() + UserFavorite.objects.filter(entity_type="module", entity_identifier=pk, project_id=project_id).delete() return Response(status=status.HTTP_204_NO_CONTENT) @@ -609,9 +597,7 @@ class ModuleIssueListCreateAPIEndpoint(BaseAPIView): """ order_by = request.GET.get("order_by", "created_at") issues = ( - Issue.issue_objects.filter( - issue_module__module_id=module_id, issue_module__deleted_at__isnull=True - ) + Issue.issue_objects.filter(issue_module__module_id=module_id, issue_module__deleted_at__isnull=True) .annotate( sub_issues_count=Issue.issue_objects.filter(parent=OuterRef("id")) .order_by() @@ -647,15 +633,13 @@ class ModuleIssueListCreateAPIEndpoint(BaseAPIView): 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, ) @module_issue_docs( operation_id="add_module_work_items", summary="Add Work Items to Module", - description="Assign multiple work items to a module or move them from another module. Automatically handles bulk creation and updates with activity tracking.", + description="Assign multiple work items to a module or move them from another module. Automatically handles bulk creation and updates with activity tracking.", # noqa: E501 parameters=[ MODULE_ID_PARAMETER, ], @@ -681,16 +665,12 @@ class ModuleIssueListCreateAPIEndpoint(BaseAPIView): """ issues = request.data.get("issues", []) if not len(issues): - return Response( - {"error": "Issues are required"}, status=status.HTTP_400_BAD_REQUEST - ) - module = Module.objects.get( - workspace__slug=slug, project_id=project_id, pk=module_id - ) + return Response({"error": "Issues are required"}, status=status.HTTP_400_BAD_REQUEST) + module = Module.objects.get(workspace__slug=slug, project_id=project_id, pk=module_id) - issues = Issue.objects.filter( - workspace__slug=slug, project_id=project_id, pk__in=issues - ).values_list("id", flat=True) + issues = Issue.objects.filter(workspace__slug=slug, project_id=project_id, pk__in=issues).values_list( + "id", flat=True + ) module_issues = list(ModuleIssue.objects.filter(issue_id__in=issues)) @@ -699,11 +679,7 @@ class ModuleIssueListCreateAPIEndpoint(BaseAPIView): record_to_create = [] for issue in issues: - module_issue = [ - module_issue - for module_issue in module_issues - if str(module_issue.issue_id) in issues - ] + module_issue = [module_issue for module_issue in module_issues if str(module_issue.issue_id) in issues] if len(module_issue): if module_issue[0].module_id != module_id: @@ -728,9 +704,7 @@ class ModuleIssueListCreateAPIEndpoint(BaseAPIView): ) ) - ModuleIssue.objects.bulk_create( - record_to_create, batch_size=10, ignore_conflicts=True - ) + ModuleIssue.objects.bulk_create(record_to_create, batch_size=10, ignore_conflicts=True) ModuleIssue.objects.bulk_update(records_to_update, ["module"], batch_size=10) @@ -744,9 +718,7 @@ class ModuleIssueListCreateAPIEndpoint(BaseAPIView): current_instance=json.dumps( { "updated_module_issues": update_module_issue_activity, - "created_module_issues": serializers.serialize( - "json", record_to_create - ), + "created_module_issues": serializers.serialize("json", record_to_create), } ), epoch=int(timezone.now().timestamp()), @@ -871,9 +843,7 @@ class ModuleIssueDetailAPIEndpoint(BaseAPIView): 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, ) @module_issue_docs( @@ -904,9 +874,7 @@ class ModuleIssueDetailAPIEndpoint(BaseAPIView): module_issue.delete() issue_activity.delay( type="module.activity.deleted", - requested_data=json.dumps( - {"module_id": str(module_id), "issues": [str(module_issue.issue_id)]} - ), + requested_data=json.dumps({"module_id": str(module_id), "issues": [str(module_issue.issue_id)]}), actor_id=str(request.user.id), issue_id=str(issue_id), project_id=str(project_id), diff --git a/apps/api/plane/api/views/project.py b/apps/api/plane/api/views/project.py index 4508aaaee..ed4d7518f 100644 --- a/apps/api/plane/api/views/project.py +++ b/apps/api/plane/api/views/project.py @@ -79,9 +79,7 @@ class ProjectListCreateAPIEndpoint(BaseAPIView): ) | Q(network=2) ) - .select_related( - "workspace", "workspace__owner", "default_assignee", "project_lead" - ) + .select_related("workspace", "workspace__owner", "default_assignee", "project_lead") .annotate( is_member=Exists( ProjectMember.objects.filter( @@ -170,9 +168,9 @@ class ProjectListCreateAPIEndpoint(BaseAPIView): .prefetch_related( Prefetch( "project_projectmember", - queryset=ProjectMember.objects.filter( - workspace__slug=slug, is_active=True - ).select_related("member"), + queryset=ProjectMember.objects.filter(workspace__slug=slug, is_active=True).select_related( + "member" + ), ) ) .order_by(request.GET.get("order_by", "sort_order")) @@ -211,24 +209,18 @@ class ProjectListCreateAPIEndpoint(BaseAPIView): """ try: workspace = Workspace.objects.get(slug=slug) - serializer = ProjectCreateSerializer( - data={**request.data}, context={"workspace_id": workspace.id} - ) + serializer = ProjectCreateSerializer(data={**request.data}, context={"workspace_id": workspace.id}) if serializer.is_valid(): serializer.save() # Add the user as Administrator to the project - _ = ProjectMember.objects.create( - project_id=serializer.instance.id, member=request.user, role=20 - ) + _ = ProjectMember.objects.create(project_id=serializer.instance.id, member=request.user, role=20) # Also create the issue property for the user - _ = IssueUserProperty.objects.create( - project_id=serializer.instance.id, user=request.user - ) + _ = IssueUserProperty.objects.create(project_id=serializer.instance.id, user=request.user) - if serializer.instance.project_lead is not None and str( - serializer.instance.project_lead - ) != str(request.user.id): + if serializer.instance.project_lead is not None and str(serializer.instance.project_lead) != str( + request.user.id + ): ProjectMember.objects.create( project_id=serializer.instance.id, member_id=serializer.instance.project_lead, @@ -314,9 +306,7 @@ class ProjectListCreateAPIEndpoint(BaseAPIView): status=status.HTTP_409_CONFLICT, ) except Workspace.DoesNotExist: - return Response( - {"error": "Workspace does not exist"}, status=status.HTTP_404_NOT_FOUND - ) + return Response({"error": "Workspace does not exist"}, status=status.HTTP_404_NOT_FOUND) except ValidationError: return Response( {"identifier": "The project identifier is already taken"}, @@ -344,9 +334,7 @@ class ProjectDetailAPIEndpoint(BaseAPIView): ) | Q(network=2) ) - .select_related( - "workspace", "workspace__owner", "default_assignee", "project_lead" - ) + .select_related("workspace", "workspace__owner", "default_assignee", "project_lead") .annotate( is_member=Exists( ProjectMember.objects.filter( @@ -451,9 +439,7 @@ class ProjectDetailAPIEndpoint(BaseAPIView): try: workspace = Workspace.objects.get(slug=slug) project = Project.objects.get(pk=pk) - current_instance = json.dumps( - ProjectSerializer(project).data, cls=DjangoJSONEncoder - ) + current_instance = json.dumps(ProjectSerializer(project).data, cls=DjangoJSONEncoder) intake_view = request.data.get("intake_view", project.intake_view) @@ -473,9 +459,7 @@ class ProjectDetailAPIEndpoint(BaseAPIView): if serializer.is_valid(): serializer.save() if serializer.data["intake_view"]: - intake = Intake.objects.filter( - project=project, is_default=True - ).first() + intake = Intake.objects.filter(project=project, is_default=True).first() if not intake: Intake.objects.create( name=f"{project.name} Intake", @@ -505,9 +489,7 @@ class ProjectDetailAPIEndpoint(BaseAPIView): status=status.HTTP_409_CONFLICT, ) except (Project.DoesNotExist, Workspace.DoesNotExist): - return Response( - {"error": "Project does not exist"}, status=status.HTTP_404_NOT_FOUND - ) + return Response({"error": "Project does not exist"}, status=status.HTTP_404_NOT_FOUND) except ValidationError: return Response( {"identifier": "The project identifier is already taken"}, @@ -533,9 +515,7 @@ class ProjectDetailAPIEndpoint(BaseAPIView): """ project = Project.objects.get(pk=pk, workspace__slug=slug) # Delete the user favorite cycle - UserFavorite.objects.filter( - entity_type="project", entity_identifier=pk, project_id=pk - ).delete() + UserFavorite.objects.filter(entity_type="project", entity_identifier=pk, project_id=pk).delete() project.delete() webhook_activity.delay( event="project", diff --git a/apps/api/plane/api/views/state.py b/apps/api/plane/api/views/state.py index 8ab61506c..bd91de39a 100644 --- a/apps/api/plane/api/views/state.py +++ b/apps/api/plane/api/views/state.py @@ -80,9 +80,7 @@ class StateListCreateAPIEndpoint(BaseAPIView): Supports external ID tracking for integration purposes. """ try: - serializer = StateSerializer( - data=request.data, context={"project_id": project_id} - ) + serializer = StateSerializer(data=request.data, context={"project_id": project_id}) if serializer.is_valid(): if ( request.data.get("external_id") @@ -153,9 +151,7 @@ class StateListCreateAPIEndpoint(BaseAPIView): return self.paginate( request=request, queryset=(self.get_queryset()), - on_results=lambda states: StateSerializer( - states, many=True, fields=self.fields, expand=self.expand - ).data, + on_results=lambda states: StateSerializer(states, many=True, fields=self.fields, expand=self.expand).data, ) @@ -213,7 +209,7 @@ class StateDetailAPIEndpoint(BaseAPIView): @state_docs( operation_id="delete_state", summary="Delete state", - description="Permanently remove a workflow state from a project. Default states and states with existing work items cannot be deleted.", + description="Permanently remove a workflow state from a project. Default states and states with existing work items cannot be deleted.", # noqa: E501 parameters=[ STATE_ID_PARAMETER, ], @@ -228,9 +224,7 @@ class StateDetailAPIEndpoint(BaseAPIView): Permanently remove a workflow state from a project. Default states and states with existing work items cannot be deleted. """ - state = State.objects.get( - is_triage=False, pk=state_id, project_id=project_id, workspace__slug=slug - ) + state = State.objects.get(is_triage=False, pk=state_id, project_id=project_id, workspace__slug=slug) if state.default: return Response( @@ -277,9 +271,7 @@ class StateDetailAPIEndpoint(BaseAPIView): Partially update an existing workflow state's properties like name, color, or group. Validates external ID uniqueness if provided. """ - state = State.objects.get( - workspace__slug=slug, project_id=project_id, pk=state_id - ) + state = State.objects.get(workspace__slug=slug, project_id=project_id, pk=state_id) serializer = StateSerializer(state, data=request.data, partial=True) if serializer.is_valid(): if ( @@ -288,9 +280,7 @@ class StateDetailAPIEndpoint(BaseAPIView): and State.objects.filter( project_id=project_id, workspace__slug=slug, - external_source=request.data.get( - "external_source", state.external_source - ), + external_source=request.data.get("external_source", state.external_source), external_id=request.data.get("external_id"), ).exists() ): diff --git a/apps/api/plane/app/permissions/base.py b/apps/api/plane/app/permissions/base.py index 881088a3f..a2b1a18ff 100644 --- a/apps/api/plane/app/permissions/base.py +++ b/apps/api/plane/app/permissions/base.py @@ -18,16 +18,12 @@ def allow_permission(allowed_roles, level="PROJECT", creator=False, model=None): def _wrapped_view(instance, request, *args, **kwargs): # Check for creator if required if creator and model: - obj = model.objects.filter( - id=kwargs["pk"], created_by=request.user - ).exists() + obj = model.objects.filter(id=kwargs["pk"], created_by=request.user).exists() if obj: return view_func(instance, request, *args, **kwargs) # Convert allowed_roles to their values if they are enum members - allowed_role_values = [ - role.value if isinstance(role, ROLE) else role for role in allowed_roles - ] + allowed_role_values = [role.value if isinstance(role, ROLE) else role for role in allowed_roles] # Check role permissions if level == "WORKSPACE": @@ -47,7 +43,7 @@ def allow_permission(allowed_roles, level="PROJECT", creator=False, model=None): is_active=True, ).exists() - # Return if the user has the allowed role else if they are workspace admin and part of the project regardless of the role + # Return if the user has the allowed role else if they are workspace admin and part of the project regardless of the role # noqa: E501 if is_user_has_allowed_role: return view_func(instance, request, *args, **kwargs) elif ( diff --git a/apps/api/plane/app/permissions/page.py b/apps/api/plane/app/permissions/page.py index 96bf63527..bea878f4c 100644 --- a/apps/api/plane/app/permissions/page.py +++ b/apps/api/plane/app/permissions/page.py @@ -30,9 +30,7 @@ class ProjectPagePermission(BasePermission): project_id = view.kwargs.get("project_id") # Hook for extended validation - extended_access, role = self._check_access_and_get_role( - request, slug, project_id - ) + extended_access, role = self._check_access_and_get_role(request, slug, project_id) if extended_access is False: return False @@ -45,9 +43,7 @@ class ProjectPagePermission(BasePermission): # Handle private page access if page.access == Page.PRIVATE_ACCESS: - return self._has_private_page_action_access( - request, slug, page, project_id - ) + return self._has_private_page_action_access(request, slug, page, project_id) # Handle public page access return self._has_public_page_action_access(request, role) diff --git a/apps/api/plane/app/serializers/base.py b/apps/api/plane/app/serializers/base.py index 715ad6eae..0d8c855c9 100644 --- a/apps/api/plane/app/serializers/base.py +++ b/apps/api/plane/app/serializers/base.py @@ -168,13 +168,9 @@ class DynamicBaseSerializer(BaseSerializer): # Check if field in expansion then expand the field if expand in expansion: if isinstance(response.get(expand), list): - exp_serializer = expansion[expand]( - getattr(instance, expand), many=True - ) + exp_serializer = expansion[expand](getattr(instance, expand), many=True) else: - exp_serializer = expansion[expand]( - getattr(instance, expand) - ) + exp_serializer = expansion[expand](getattr(instance, expand)) response[expand] = exp_serializer.data else: # You might need to handle this case differently @@ -194,9 +190,7 @@ class DynamicBaseSerializer(BaseSerializer): entity_type=FileAsset.EntityTypeContext.ISSUE_ATTACHMENT, ) # Serialize issue_attachments and add them to the response - response["issue_attachments"] = IssueAttachmentLiteSerializer( - issue_attachments, many=True - ).data + response["issue_attachments"] = IssueAttachmentLiteSerializer(issue_attachments, many=True).data else: response["issue_attachments"] = [] diff --git a/apps/api/plane/app/serializers/cycle.py b/apps/api/plane/app/serializers/cycle.py index 2aa2ac7b7..89a5efc06 100644 --- a/apps/api/plane/app/serializers/cycle.py +++ b/apps/api/plane/app/serializers/cycle.py @@ -16,10 +16,7 @@ class CycleWriteSerializer(BaseSerializer): and data.get("start_date", None) > data.get("end_date", None) ): raise serializers.ValidationError("Start date cannot exceed end date") - if ( - data.get("start_date", None) is not None - and data.get("end_date", None) is not None - ): + if data.get("start_date", None) is not None and data.get("end_date", None) is not None: project_id = ( self.initial_data.get("project_id", None) or (self.instance and self.instance.project_id) diff --git a/apps/api/plane/app/serializers/draft.py b/apps/api/plane/app/serializers/draft.py index 1de584cf8..b017a03ba 100644 --- a/apps/api/plane/app/serializers/draft.py +++ b/apps/api/plane/app/serializers/draft.py @@ -1,4 +1,3 @@ - # Django imports from django.utils import timezone @@ -75,13 +74,9 @@ class DraftIssueCreateSerializer(BaseSerializer): # Validate description content for security if "description_html" in attrs and attrs["description_html"]: - is_valid, error_msg, sanitized_html = validate_html_content( - attrs["description_html"] - ) + is_valid, error_msg, sanitized_html = validate_html_content(attrs["description_html"]) if not is_valid: - raise serializers.ValidationError( - {"error": "html content is not valid"} - ) + raise serializers.ValidationError({"error": "html content is not valid"}) # Update the attrs with sanitized HTML if available if sanitized_html is not None: attrs["description_html"] = sanitized_html @@ -89,9 +84,7 @@ class DraftIssueCreateSerializer(BaseSerializer): if "description_binary" in attrs and attrs["description_binary"]: is_valid, error_msg = validate_binary_data(attrs["description_binary"]) if not is_valid: - raise serializers.ValidationError( - {"description_binary": "Invalid binary data"} - ) + raise serializers.ValidationError({"description_binary": "Invalid binary data"}) # Validate assignees are from project if attrs.get("assignee_ids", []): @@ -106,9 +99,9 @@ class DraftIssueCreateSerializer(BaseSerializer): if attrs.get("label_ids"): label_ids = [label.id for label in attrs["label_ids"]] attrs["label_ids"] = list( - Label.objects.filter( - project_id=self.context.get("project_id"), id__in=label_ids - ).values_list("id", flat=True) + Label.objects.filter(project_id=self.context.get("project_id"), id__in=label_ids).values_list( + "id", flat=True + ) ) # # Check state is from the project only else raise validation error @@ -119,9 +112,7 @@ class DraftIssueCreateSerializer(BaseSerializer): pk=attrs.get("state").id, ).exists() ): - raise serializers.ValidationError( - "State is not valid please pass a valid state_id" - ) + raise serializers.ValidationError("State is not valid please pass a valid state_id") # # Check parent issue is from workspace as it can be cross workspace if ( @@ -131,9 +122,7 @@ class DraftIssueCreateSerializer(BaseSerializer): pk=attrs.get("parent").id, ).exists() ): - raise serializers.ValidationError( - "Parent is not valid issue_id please pass a valid issue_id" - ) + raise serializers.ValidationError("Parent is not valid issue_id please pass a valid issue_id") if ( attrs.get("estimate_point") @@ -142,9 +131,7 @@ class DraftIssueCreateSerializer(BaseSerializer): pk=attrs.get("estimate_point").id, ).exists() ): - raise serializers.ValidationError( - "Estimate point is not valid please pass a valid estimate_point_id" - ) + raise serializers.ValidationError("Estimate point is not valid please pass a valid estimate_point_id") return attrs @@ -159,9 +146,7 @@ class DraftIssueCreateSerializer(BaseSerializer): project_id = self.context["project_id"] # Create Issue - issue = DraftIssue.objects.create( - **validated_data, workspace_id=workspace_id, project_id=project_id - ) + issue = DraftIssue.objects.create(**validated_data, workspace_id=workspace_id, project_id=project_id) # Issue Audit Users created_by_id = issue.created_by_id diff --git a/apps/api/plane/app/serializers/favorite.py b/apps/api/plane/app/serializers/favorite.py index 940b8ee82..246461f8f 100644 --- a/apps/api/plane/app/serializers/favorite.py +++ b/apps/api/plane/app/serializers/favorite.py @@ -17,9 +17,7 @@ class PageFavoriteLiteSerializer(serializers.ModelSerializer): fields = ["id", "name", "logo_props", "project_id"] def get_project_id(self, obj): - project = ( - obj.projects.first() - ) # This gets the first project related to the Page + project = obj.projects.first() # This gets the first project related to the Page return project.id if project else None diff --git a/apps/api/plane/app/serializers/intake.py b/apps/api/plane/app/serializers/intake.py index 8b8bbacf7..7bc258220 100644 --- a/apps/api/plane/app/serializers/intake.py +++ b/apps/api/plane/app/serializers/intake.py @@ -45,9 +45,7 @@ class IntakeIssueSerializer(BaseSerializer): class IntakeIssueDetailSerializer(BaseSerializer): issue = IssueDetailSerializer(read_only=True) - duplicate_issue_detail = IssueIntakeSerializer( - read_only=True, source="duplicate_to" - ) + duplicate_issue_detail = IssueIntakeSerializer(read_only=True, source="duplicate_to") class Meta: model = IntakeIssue diff --git a/apps/api/plane/app/serializers/issue.py b/apps/api/plane/app/serializers/issue.py index 367b5ce69..583b62fd6 100644 --- a/apps/api/plane/app/serializers/issue.py +++ b/apps/api/plane/app/serializers/issue.py @@ -1,4 +1,3 @@ - # Django imports from django.utils import timezone from django.core.validators import URLValidator @@ -127,13 +126,9 @@ class IssueCreateSerializer(BaseSerializer): # Validate description content for security if "description_html" in attrs and attrs["description_html"]: - is_valid, error_msg, sanitized_html = validate_html_content( - attrs["description_html"] - ) + is_valid, error_msg, sanitized_html = validate_html_content(attrs["description_html"]) if not is_valid: - raise serializers.ValidationError( - {"error": "html content is not valid"} - ) + raise serializers.ValidationError({"error": "html content is not valid"}) # Update the attrs with sanitized HTML if available if sanitized_html is not None: attrs["description_html"] = sanitized_html @@ -141,9 +136,7 @@ class IssueCreateSerializer(BaseSerializer): if "description_binary" in attrs and attrs["description_binary"]: is_valid, error_msg = validate_binary_data(attrs["description_binary"]) if not is_valid: - raise serializers.ValidationError( - {"description_binary": "Invalid binary data"} - ) + raise serializers.ValidationError({"description_binary": "Invalid binary data"}) # Validate assignees are from project if attrs.get("assignee_ids", []): @@ -172,9 +165,7 @@ class IssueCreateSerializer(BaseSerializer): pk=attrs.get("state").id, ).exists() ): - raise serializers.ValidationError( - "State is not valid please pass a valid state_id" - ) + raise serializers.ValidationError("State is not valid please pass a valid state_id") # Check parent issue is from workspace as it can be cross workspace if ( @@ -184,9 +175,7 @@ class IssueCreateSerializer(BaseSerializer): pk=attrs.get("parent").id, ).exists() ): - raise serializers.ValidationError( - "Parent is not valid issue_id please pass a valid issue_id" - ) + raise serializers.ValidationError("Parent is not valid issue_id please pass a valid issue_id") if ( attrs.get("estimate_point") @@ -195,9 +184,7 @@ class IssueCreateSerializer(BaseSerializer): pk=attrs.get("estimate_point").id, ).exists() ): - raise serializers.ValidationError( - "Estimate point is not valid please pass a valid estimate_point_id" - ) + raise serializers.ValidationError("Estimate point is not valid please pass a valid estimate_point_id") return attrs @@ -343,11 +330,7 @@ class IssueActivitySerializer(BaseSerializer): source_data = serializers.SerializerMethodField() def get_source_data(self, obj): - if ( - hasattr(obj, "issue") - and hasattr(obj.issue, "source_data") - and obj.issue.source_data - ): + if hasattr(obj, "issue") and hasattr(obj.issue, "source_data") and obj.issue.source_data: return { "source": obj.issue.source_data[0].source, "source_email": obj.issue.source_data[0].source_email, @@ -397,12 +380,8 @@ class IssueLabelSerializer(BaseSerializer): class IssueRelationSerializer(BaseSerializer): id = serializers.UUIDField(source="related_issue.id", read_only=True) - project_id = serializers.PrimaryKeyRelatedField( - source="related_issue.project_id", read_only=True - ) - sequence_id = serializers.IntegerField( - source="related_issue.sequence_id", read_only=True - ) + project_id = serializers.PrimaryKeyRelatedField(source="related_issue.project_id", read_only=True) + sequence_id = serializers.IntegerField(source="related_issue.sequence_id", read_only=True) name = serializers.CharField(source="related_issue.name", read_only=True) relation_type = serializers.CharField(read_only=True) state_id = serializers.UUIDField(source="related_issue.state.id", read_only=True) @@ -441,9 +420,7 @@ class IssueRelationSerializer(BaseSerializer): class RelatedIssueSerializer(BaseSerializer): id = serializers.UUIDField(source="issue.id", read_only=True) - project_id = serializers.PrimaryKeyRelatedField( - source="issue.project_id", read_only=True - ) + project_id = serializers.PrimaryKeyRelatedField(source="issue.project_id", read_only=True) sequence_id = serializers.IntegerField(source="issue.sequence_id", read_only=True) name = serializers.CharField(source="issue.name", read_only=True) relation_type = serializers.CharField(read_only=True) @@ -585,25 +562,17 @@ class IssueLinkSerializer(BaseSerializer): # Validation if url already exists def create(self, validated_data): - if IssueLink.objects.filter( - url=validated_data.get("url"), issue_id=validated_data.get("issue_id") - ).exists(): - raise serializers.ValidationError( - {"error": "URL already exists for this Issue"} - ) + if IssueLink.objects.filter(url=validated_data.get("url"), issue_id=validated_data.get("issue_id")).exists(): + raise serializers.ValidationError({"error": "URL already exists for this Issue"}) return IssueLink.objects.create(**validated_data) def update(self, instance, validated_data): if ( - IssueLink.objects.filter( - url=validated_data.get("url"), issue_id=instance.issue_id - ) + IssueLink.objects.filter(url=validated_data.get("url"), issue_id=instance.issue_id) .exclude(pk=instance.id) .exists() ): - raise serializers.ValidationError( - {"error": "URL already exists for this Issue"} - ) + raise serializers.ValidationError({"error": "URL already exists for this Issue"}) return super().update(instance, validated_data) @@ -941,9 +910,7 @@ class IssueDetailSerializer(IssueSerializer): class IssuePublicSerializer(BaseSerializer): project_detail = ProjectLiteSerializer(read_only=True, source="project") state_detail = StateLiteSerializer(read_only=True, source="state") - reactions = IssueReactionSerializer( - read_only=True, many=True, source="issue_reactions" - ) + reactions = IssueReactionSerializer(read_only=True, many=True, source="issue_reactions") votes = IssueVoteSerializer(read_only=True, many=True) class Meta: diff --git a/apps/api/plane/app/serializers/module.py b/apps/api/plane/app/serializers/module.py index 22ff44279..b5e2953cc 100644 --- a/apps/api/plane/app/serializers/module.py +++ b/apps/api/plane/app/serializers/module.py @@ -65,9 +65,7 @@ class ModuleWriteSerializer(BaseSerializer): if module_name: # Lookup for the module name in the module table for that project if Module.objects.filter(name=module_name, project=project).exists(): - raise serializers.ValidationError( - {"error": "Module with this name already exists"} - ) + raise serializers.ValidationError({"error": "Module with this name already exists"}) module = Module.objects.create(**validated_data, project=project) if members is not None: @@ -94,14 +92,8 @@ class ModuleWriteSerializer(BaseSerializer): module_name = validated_data.get("name") if module_name: # Lookup for the module name in the module table for that project - if ( - Module.objects.filter(name=module_name, project=instance.project) - .exclude(id=instance.id) - .exists() - ): - raise serializers.ValidationError( - {"error": "Module with this name already exists"} - ) + if Module.objects.filter(name=module_name, project=instance.project).exclude(id=instance.id).exists(): + raise serializers.ValidationError({"error": "Module with this name already exists"}) if members is not None: ModuleMember.objects.filter(module=instance).delete() @@ -191,32 +183,24 @@ class ModuleLinkSerializer(BaseSerializer): def create(self, validated_data): validated_data["url"] = self.validate_url(validated_data.get("url")) - if ModuleLink.objects.filter( - url=validated_data.get("url"), module_id=validated_data.get("module_id") - ).exists(): + if ModuleLink.objects.filter(url=validated_data.get("url"), module_id=validated_data.get("module_id")).exists(): raise serializers.ValidationError({"error": "URL already exists."}) return super().create(validated_data) def update(self, instance, validated_data): validated_data["url"] = self.validate_url(validated_data.get("url")) if ( - ModuleLink.objects.filter( - url=validated_data.get("url"), module_id=instance.module_id - ) + ModuleLink.objects.filter(url=validated_data.get("url"), module_id=instance.module_id) .exclude(pk=instance.id) .exists() ): - raise serializers.ValidationError( - {"error": "URL already exists for this Issue"} - ) + raise serializers.ValidationError({"error": "URL already exists for this Issue"}) return super().update(instance, validated_data) class ModuleSerializer(DynamicBaseSerializer): - member_ids = serializers.ListField( - child=serializers.UUIDField(), required=False, allow_null=True - ) + member_ids = serializers.ListField(child=serializers.UUIDField(), required=False, allow_null=True) is_favorite = serializers.BooleanField(read_only=True) total_issues = serializers.IntegerField(read_only=True) cancelled_issues = serializers.IntegerField(read_only=True) diff --git a/apps/api/plane/app/serializers/page.py b/apps/api/plane/app/serializers/page.py index 174a6ee48..3aecbafda 100644 --- a/apps/api/plane/app/serializers/page.py +++ b/apps/api/plane/app/serializers/page.py @@ -10,7 +10,6 @@ from plane.utils.content_validator import ( ) from plane.db.models import ( Page, - PageLog, PageLabel, Label, ProjectPage, @@ -186,9 +185,7 @@ class PageBinaryUpdateSerializer(serializers.Serializer): # Validate the binary data is_valid, error_message = validate_binary_data(binary_data) if not is_valid: - raise serializers.ValidationError( - f"Invalid binary data: {error_message}" - ) + raise serializers.ValidationError(f"Invalid binary data: {error_message}") return binary_data except Exception as e: @@ -209,7 +206,6 @@ class PageBinaryUpdateSerializer(serializers.Serializer): # Return sanitized HTML if available, otherwise return original return sanitized_html if sanitized_html is not None else value - def update(self, instance, validated_data): """Update the page instance with validated data""" if "description_binary" in validated_data: diff --git a/apps/api/plane/app/serializers/project.py b/apps/api/plane/app/serializers/project.py index 76f76d0e0..c709093ad 100644 --- a/apps/api/plane/app/serializers/project.py +++ b/apps/api/plane/app/serializers/project.py @@ -47,9 +47,7 @@ class ProjectSerializer(BaseSerializer): project_id = self.instance.id if self.instance else None workspace_id = self.context["workspace_id"] - project = Project.objects.filter( - identifier=identifier, workspace_id=workspace_id - ) + project = Project.objects.filter(identifier=identifier, workspace_id=workspace_id) if project_id: project = project.exclude(id=project_id) @@ -64,17 +62,13 @@ class ProjectSerializer(BaseSerializer): def validate(self, data): # Validate description content for security if "description_html" in data and data["description_html"]: - is_valid, error_msg, sanitized_html = validate_html_content( - str(data["description_html"]) - ) + is_valid, error_msg, sanitized_html = validate_html_content(str(data["description_html"])) # Update the data with sanitized HTML if available if sanitized_html is not None: data["description_html"] = sanitized_html if not is_valid: - raise serializers.ValidationError( - {"error": "html content is not valid"} - ) + raise serializers.ValidationError({"error": "html content is not valid"}) return data @@ -83,9 +77,7 @@ class ProjectSerializer(BaseSerializer): project = Project.objects.create(**validated_data, workspace_id=workspace_id) - ProjectIdentifier.objects.create( - name=project.identifier, project=project, workspace_id=workspace_id - ) + ProjectIdentifier.objects.create(name=project.identifier, project=project, workspace_id=workspace_id) return project @@ -118,11 +110,7 @@ class ProjectListSerializer(DynamicBaseSerializer): project_members = getattr(obj, "members_list", None) if project_members is not None: # Filter members by the project ID - return [ - member.member_id - for member in project_members - if member.is_active and not member.member.is_bot - ] + return [member.member_id for member in project_members if member.is_active and not member.member.is_bot] return [] class Meta: diff --git a/apps/api/plane/app/serializers/user.py b/apps/api/plane/app/serializers/user.py index 7b5453568..670667a85 100644 --- a/apps/api/plane/app/serializers/user.py +++ b/apps/api/plane/app/serializers/user.py @@ -91,9 +91,7 @@ class UserMeSettingsSerializer(BaseSerializer): read_only_fields = fields def get_workspace(self, obj): - workspace_invites = WorkspaceMemberInvite.objects.filter( - email=obj.email - ).count() + workspace_invites = WorkspaceMemberInvite.objects.filter(email=obj.email).count() # profile profile = Profile.objects.get(user=obj) @@ -110,43 +108,27 @@ class UserMeSettingsSerializer(BaseSerializer): workspace_member__member=obj.id, workspace_member__is_active=True, ).first() - logo_asset_url = ( - workspace.logo_asset.asset_url - if workspace.logo_asset is not None - else "" - ) + logo_asset_url = workspace.logo_asset.asset_url if workspace.logo_asset is not None else "" return { "last_workspace_id": profile.last_workspace_id, - "last_workspace_slug": ( - workspace.slug if workspace is not None else "" - ), - "last_workspace_name": ( - workspace.name if workspace is not None else "" - ), + "last_workspace_slug": (workspace.slug if workspace is not None else ""), + "last_workspace_name": (workspace.name if workspace is not None else ""), "last_workspace_logo": (logo_asset_url), "fallback_workspace_id": profile.last_workspace_id, - "fallback_workspace_slug": ( - workspace.slug if workspace is not None else "" - ), + "fallback_workspace_slug": (workspace.slug if workspace is not None else ""), "invites": workspace_invites, } else: fallback_workspace = ( - Workspace.objects.filter( - workspace_member__member_id=obj.id, workspace_member__is_active=True - ) + Workspace.objects.filter(workspace_member__member_id=obj.id, workspace_member__is_active=True) .order_by("created_at") .first() ) return { "last_workspace_id": None, "last_workspace_slug": None, - "fallback_workspace_id": ( - fallback_workspace.id if fallback_workspace is not None else None - ), - "fallback_workspace_slug": ( - fallback_workspace.slug if fallback_workspace is not None else None - ), + "fallback_workspace_id": (fallback_workspace.id if fallback_workspace is not None else None), + "fallback_workspace_slug": (fallback_workspace.slug if fallback_workspace is not None else None), "invites": workspace_invites, } @@ -195,14 +177,10 @@ class ChangePasswordSerializer(serializers.Serializer): def validate(self, data): if data.get("old_password") == data.get("new_password"): - raise serializers.ValidationError( - {"error": "New password cannot be same as old password."} - ) + raise serializers.ValidationError({"error": "New password cannot be same as old password."}) if data.get("new_password") != data.get("confirm_password"): - raise serializers.ValidationError( - {"error": "Confirm password should be same as the new password."} - ) + raise serializers.ValidationError({"error": "Confirm password should be same as the new password."}) return data diff --git a/apps/api/plane/app/serializers/webhook.py b/apps/api/plane/app/serializers/webhook.py index 1036b700c..ef193e24d 100644 --- a/apps/api/plane/app/serializers/webhook.py +++ b/apps/api/plane/app/serializers/webhook.py @@ -21,29 +21,21 @@ class WebhookSerializer(DynamicBaseSerializer): # Extract the hostname from the URL hostname = urlparse(url).hostname if not hostname: - raise serializers.ValidationError( - {"url": "Invalid URL: No hostname found."} - ) + raise serializers.ValidationError({"url": "Invalid URL: No hostname found."}) # Resolve the hostname to IP addresses try: ip_addresses = socket.getaddrinfo(hostname, None) except socket.gaierror: - raise serializers.ValidationError( - {"url": "Hostname could not be resolved."} - ) + raise serializers.ValidationError({"url": "Hostname could not be resolved."}) if not ip_addresses: - raise serializers.ValidationError( - {"url": "No IP addresses found for the hostname."} - ) + raise serializers.ValidationError({"url": "No IP addresses found for the hostname."}) for addr in ip_addresses: ip = ipaddress.ip_address(addr[4][0]) if ip.is_loopback: - raise serializers.ValidationError( - {"url": "URL resolves to a blocked IP address."} - ) + raise serializers.ValidationError({"url": "URL resolves to a blocked IP address."}) # Additional validation for multiple request domains and their subdomains request = self.context.get("request") @@ -53,13 +45,8 @@ class WebhookSerializer(DynamicBaseSerializer): disallowed_domains.append(request_host) # Check if hostname is a subdomain or exact match of any disallowed domain - if any( - hostname == domain or hostname.endswith("." + domain) - for domain in disallowed_domains - ): - raise serializers.ValidationError( - {"url": "URL domain or its subdomain is not allowed."} - ) + if any(hostname == domain or hostname.endswith("." + domain) for domain in disallowed_domains): + raise serializers.ValidationError({"url": "URL domain or its subdomain is not allowed."}) return Webhook.objects.create(**validated_data) @@ -69,47 +56,32 @@ class WebhookSerializer(DynamicBaseSerializer): # Extract the hostname from the URL hostname = urlparse(url).hostname if not hostname: - raise serializers.ValidationError( - {"url": "Invalid URL: No hostname found."} - ) + raise serializers.ValidationError({"url": "Invalid URL: No hostname found."}) # Resolve the hostname to IP addresses try: ip_addresses = socket.getaddrinfo(hostname, None) except socket.gaierror: - raise serializers.ValidationError( - {"url": "Hostname could not be resolved."} - ) + raise serializers.ValidationError({"url": "Hostname could not be resolved."}) if not ip_addresses: - raise serializers.ValidationError( - {"url": "No IP addresses found for the hostname."} - ) + raise serializers.ValidationError({"url": "No IP addresses found for the hostname."}) for addr in ip_addresses: ip = ipaddress.ip_address(addr[4][0]) if ip.is_loopback: - raise serializers.ValidationError( - {"url": "URL resolves to a blocked IP address."} - ) + raise serializers.ValidationError({"url": "URL resolves to a blocked IP address."}) # Additional validation for multiple request domains and their subdomains request = self.context.get("request") disallowed_domains = ["plane.so"] # Add your disallowed domains here if request: - request_host = request.get_host().split(":")[ - 0 - ] # Remove port if present + request_host = request.get_host().split(":")[0] # Remove port if present disallowed_domains.append(request_host) # Check if hostname is a subdomain or exact match of any disallowed domain - if any( - hostname == domain or hostname.endswith("." + domain) - for domain in disallowed_domains - ): - raise serializers.ValidationError( - {"url": "URL domain or its subdomain is not allowed."} - ) + if any(hostname == domain or hostname.endswith("." + domain) for domain in disallowed_domains): + raise serializers.ValidationError({"url": "URL domain or its subdomain is not allowed."}) return super().update(instance, validated_data) diff --git a/apps/api/plane/app/serializers/workspace.py b/apps/api/plane/app/serializers/workspace.py index 6b22f59e8..ba59f2429 100644 --- a/apps/api/plane/app/serializers/workspace.py +++ b/apps/api/plane/app/serializers/workspace.py @@ -173,9 +173,7 @@ class WorkspaceUserLinkSerializer(BaseSerializer): ) if workspace_user_link.exists(): - raise serializers.ValidationError( - {"error": "URL already exists for this workspace and owner"} - ) + raise serializers.ValidationError({"error": "URL already exists for this workspace and owner"}) return super().create(validated_data) @@ -189,9 +187,7 @@ class WorkspaceUserLinkSerializer(BaseSerializer): ) if workspace_user_link.exclude(pk=instance.id).exists(): - raise serializers.ValidationError( - {"error": "URL already exists for this workspace and owner"} - ) + raise serializers.ValidationError({"error": "URL already exists for this workspace and owner"}) return super().update(instance, validated_data) @@ -219,11 +215,7 @@ class IssueRecentVisitSerializer(serializers.ModelSerializer): return project.identifier if project else None def get_assignees(self, obj): - return list( - obj.assignees.filter(issue_assignee__deleted_at__isnull=True).values_list( - "id", flat=True - ) - ) + return list(obj.assignees.filter(issue_assignee__deleted_at__isnull=True).values_list("id", flat=True)) class ProjectRecentVisitSerializer(serializers.ModelSerializer): @@ -234,9 +226,9 @@ class ProjectRecentVisitSerializer(serializers.ModelSerializer): fields = ["id", "name", "logo_props", "project_members", "identifier"] def get_project_members(self, obj): - members = ProjectMember.objects.filter( - project_id=obj.id, member__is_bot=False, is_active=True - ).values_list("member", flat=True) + members = ProjectMember.objects.filter(project_id=obj.id, member__is_bot=False, is_active=True).values_list( + "member", flat=True + ) return members @@ -257,11 +249,7 @@ class PageRecentVisitSerializer(serializers.ModelSerializer): ] def get_project_id(self, obj): - return ( - obj.project_id - if hasattr(obj, "project_id") - else obj.projects.values_list("id", flat=True).first() - ) + return obj.project_id if hasattr(obj, "project_id") else obj.projects.values_list("id", flat=True).first() def get_project_identifier(self, obj): project = obj.projects.first() @@ -319,13 +307,9 @@ class StickySerializer(BaseSerializer): def validate(self, data): # Validate description content for security if "description_html" in data and data["description_html"]: - is_valid, error_msg, sanitized_html = validate_html_content( - data["description_html"] - ) + is_valid, error_msg, sanitized_html = validate_html_content(data["description_html"]) if not is_valid: - raise serializers.ValidationError( - {"error": "html content is not valid"} - ) + raise serializers.ValidationError({"error": "html content is not valid"}) # Update the data with sanitized HTML if available if sanitized_html is not None: data["description_html"] = sanitized_html @@ -333,9 +317,7 @@ class StickySerializer(BaseSerializer): if "description_binary" in data and data["description_binary"]: is_valid, error_msg = validate_binary_data(data["description_binary"]) if not is_valid: - raise serializers.ValidationError( - {"description_binary": "Invalid binary data"} - ) + raise serializers.ValidationError({"description_binary": "Invalid binary data"}) return data diff --git a/apps/api/plane/app/urls/analytic.py b/apps/api/plane/app/urls/analytic.py index 3e4172771..df6ad2498 100644 --- a/apps/api/plane/app/urls/analytic.py +++ b/apps/api/plane/app/urls/analytic.py @@ -30,9 +30,7 @@ urlpatterns = [ ), path( "workspaces//analytic-view//", - AnalyticViewViewset.as_view( - {"get": "retrieve", "patch": "partial_update", "delete": "destroy"} - ), + AnalyticViewViewset.as_view({"get": "retrieve", "patch": "partial_update", "delete": "destroy"}), name="analytic-view", ), path( diff --git a/apps/api/plane/app/urls/estimate.py b/apps/api/plane/app/urls/estimate.py index 8e5af2a85..c77a5b6b6 100644 --- a/apps/api/plane/app/urls/estimate.py +++ b/apps/api/plane/app/urls/estimate.py @@ -21,9 +21,7 @@ urlpatterns = [ ), path( "workspaces//projects//estimates//", - BulkEstimatePointEndpoint.as_view( - {"get": "retrieve", "patch": "partial_update", "delete": "destroy"} - ), + BulkEstimatePointEndpoint.as_view({"get": "retrieve", "patch": "partial_update", "delete": "destroy"}), name="bulk-create-estimate-points", ), path( diff --git a/apps/api/plane/app/urls/intake.py b/apps/api/plane/app/urls/intake.py index ac4b7ca5c..dd1efc872 100644 --- a/apps/api/plane/app/urls/intake.py +++ b/apps/api/plane/app/urls/intake.py @@ -16,9 +16,7 @@ urlpatterns = [ ), path( "workspaces//projects//intakes//", - IntakeViewSet.as_view( - {"get": "retrieve", "patch": "partial_update", "delete": "destroy"} - ), + IntakeViewSet.as_view({"get": "retrieve", "patch": "partial_update", "delete": "destroy"}), name="intake", ), path( @@ -28,9 +26,7 @@ urlpatterns = [ ), path( "workspaces//projects//intake-issues//", - IntakeIssueViewSet.as_view( - {"get": "retrieve", "patch": "partial_update", "delete": "destroy"} - ), + IntakeIssueViewSet.as_view({"get": "retrieve", "patch": "partial_update", "delete": "destroy"}), name="intake-issue", ), path( @@ -40,9 +36,7 @@ urlpatterns = [ ), path( "workspaces//projects//inboxes//", - IntakeViewSet.as_view( - {"get": "retrieve", "patch": "partial_update", "delete": "destroy"} - ), + IntakeViewSet.as_view({"get": "retrieve", "patch": "partial_update", "delete": "destroy"}), name="inbox", ), path( @@ -52,9 +46,7 @@ urlpatterns = [ ), path( "workspaces//projects//inbox-issues//", - IntakeIssueViewSet.as_view( - {"get": "retrieve", "patch": "partial_update", "delete": "destroy"} - ), + IntakeIssueViewSet.as_view({"get": "retrieve", "patch": "partial_update", "delete": "destroy"}), name="inbox-issue", ), path( diff --git a/apps/api/plane/app/urls/issue.py b/apps/api/plane/app/urls/issue.py index db56a6240..0521a33dc 100644 --- a/apps/api/plane/app/urls/issue.py +++ b/apps/api/plane/app/urls/issue.py @@ -187,9 +187,7 @@ urlpatterns = [ ), path( "workspaces//projects//issues//subscribe/", - IssueSubscriberViewSet.as_view( - {"get": "subscription_status", "post": "subscribe", "delete": "unsubscribe"} - ), + IssueSubscriberViewSet.as_view({"get": "subscription_status", "post": "subscribe", "delete": "unsubscribe"}), name="project-issue-subscribers", ), ## End Issue Subscribers @@ -232,9 +230,7 @@ urlpatterns = [ ), path( "workspaces//projects//issues//archive/", - IssueArchiveViewSet.as_view( - {"get": "retrieve", "post": "archive", "delete": "unarchive"} - ), + IssueArchiveViewSet.as_view({"get": "retrieve", "post": "archive", "delete": "unarchive"}), name="project-issue-archive-unarchive", ), ## End Issue Archives diff --git a/apps/api/plane/app/urls/notification.py b/apps/api/plane/app/urls/notification.py index cd5647ea4..0c992d49e 100644 --- a/apps/api/plane/app/urls/notification.py +++ b/apps/api/plane/app/urls/notification.py @@ -17,9 +17,7 @@ urlpatterns = [ ), path( "workspaces//users/notifications//", - NotificationViewSet.as_view( - {"get": "retrieve", "patch": "partial_update", "delete": "destroy"} - ), + NotificationViewSet.as_view({"get": "retrieve", "patch": "partial_update", "delete": "destroy"}), name="notifications", ), path( diff --git a/apps/api/plane/app/urls/page.py b/apps/api/plane/app/urls/page.py index e4e46bc93..8cac22a2f 100644 --- a/apps/api/plane/app/urls/page.py +++ b/apps/api/plane/app/urls/page.py @@ -22,9 +22,7 @@ urlpatterns = [ ), path( "workspaces//projects//pages//", - PageViewSet.as_view( - {"get": "retrieve", "patch": "partial_update", "delete": "destroy"} - ), + PageViewSet.as_view({"get": "retrieve", "patch": "partial_update", "delete": "destroy"}), name="project-pages", ), # favorite pages diff --git a/apps/api/plane/app/urls/project.py b/apps/api/plane/app/urls/project.py index d673d191e..61d30f916 100644 --- a/apps/api/plane/app/urls/project.py +++ b/apps/api/plane/app/urls/project.py @@ -77,9 +77,7 @@ urlpatterns = [ ), path( "workspaces//projects//members//", - ProjectMemberViewSet.as_view( - {"get": "retrieve", "patch": "partial_update", "delete": "destroy"} - ), + ProjectMemberViewSet.as_view({"get": "retrieve", "patch": "partial_update", "delete": "destroy"}), name="project-member", ), path( @@ -119,9 +117,7 @@ urlpatterns = [ ), path( "workspaces//projects//project-deploy-boards//", - DeployBoardViewSet.as_view( - {"get": "retrieve", "patch": "partial_update", "delete": "destroy"} - ), + DeployBoardViewSet.as_view({"get": "retrieve", "patch": "partial_update", "delete": "destroy"}), name="project-deploy-board", ), path( diff --git a/apps/api/plane/app/urls/state.py b/apps/api/plane/app/urls/state.py index b9ffd0341..7dcf01d62 100644 --- a/apps/api/plane/app/urls/state.py +++ b/apps/api/plane/app/urls/state.py @@ -12,9 +12,7 @@ urlpatterns = [ ), path( "workspaces//projects//states//", - StateViewSet.as_view( - {"get": "retrieve", "patch": "partial_update", "delete": "destroy"} - ), + StateViewSet.as_view({"get": "retrieve", "patch": "partial_update", "delete": "destroy"}), name="project-state", ), path( diff --git a/apps/api/plane/app/urls/user.py b/apps/api/plane/app/urls/user.py index 443961d0e..ef4162c10 100644 --- a/apps/api/plane/app/urls/user.py +++ b/apps/api/plane/app/urls/user.py @@ -21,9 +21,7 @@ urlpatterns = [ # User Profile path( "users/me/", - UserEndpoint.as_view( - {"get": "retrieve", "patch": "partial_update", "delete": "deactivate"} - ), + UserEndpoint.as_view({"get": "retrieve", "patch": "partial_update", "delete": "deactivate"}), name="users", ), path("users/session/", UserSessionEndpoint.as_view(), name="user-session"), @@ -44,21 +42,15 @@ urlpatterns = [ UserEndpoint.as_view({"get": "retrieve_instance_admin"}), name="users", ), - path( - "users/me/onboard/", UpdateUserOnBoardedEndpoint.as_view(), name="user-onboard" - ), + path("users/me/onboard/", UpdateUserOnBoardedEndpoint.as_view(), name="user-onboard"), path( "users/me/tour-completed/", UpdateUserTourCompletedEndpoint.as_view(), name="user-tour", ), - path( - "users/me/activities/", UserActivityEndpoint.as_view(), name="user-activities" - ), + path("users/me/activities/", UserActivityEndpoint.as_view(), name="user-activities"), # user workspaces - path( - "users/me/workspaces/", UserWorkSpacesEndpoint.as_view(), name="user-workspace" - ), + path("users/me/workspaces/", UserWorkSpacesEndpoint.as_view(), name="user-workspace"), # User Graphs path( "users/me/workspaces//activity-graph/", diff --git a/apps/api/plane/app/urls/workspace.py b/apps/api/plane/app/urls/workspace.py index f16fdb161..016b68088 100644 --- a/apps/api/plane/app/urls/workspace.py +++ b/apps/api/plane/app/urls/workspace.py @@ -65,9 +65,7 @@ urlpatterns = [ ), path( "workspaces//invitations//", - WorkspaceInvitationsViewset.as_view( - {"delete": "destroy", "get": "retrieve", "patch": "partial_update"} - ), + WorkspaceInvitationsViewset.as_view({"delete": "destroy", "get": "retrieve", "patch": "partial_update"}), name="workspace-invitations", ), # user workspace invitations @@ -94,9 +92,7 @@ urlpatterns = [ ), path( "workspaces//members//", - WorkSpaceMemberViewSet.as_view( - {"patch": "partial_update", "delete": "destroy", "get": "retrieve"} - ), + WorkSpaceMemberViewSet.as_view({"patch": "partial_update", "delete": "destroy", "get": "retrieve"}), name="workspace-member", ), path( @@ -126,9 +122,7 @@ urlpatterns = [ ), path( "workspaces//workspace-themes//", - WorkspaceThemeViewSet.as_view( - {"get": "retrieve", "patch": "partial_update", "delete": "destroy"} - ), + WorkspaceThemeViewSet.as_view({"get": "retrieve", "patch": "partial_update", "delete": "destroy"}), name="workspace-themes", ), path( @@ -208,9 +202,7 @@ urlpatterns = [ ), path( "workspaces//draft-issues//", - WorkspaceDraftIssueViewSet.as_view( - {"get": "retrieve", "patch": "partial_update", "delete": "destroy"} - ), + WorkspaceDraftIssueViewSet.as_view({"get": "retrieve", "patch": "partial_update", "delete": "destroy"}), name="workspace-drafts-issues", ), path( @@ -226,9 +218,7 @@ urlpatterns = [ ), path( "workspaces//quick-links//", - QuickLinkViewSet.as_view( - {"get": "retrieve", "patch": "partial_update", "delete": "destroy"} - ), + QuickLinkViewSet.as_view({"get": "retrieve", "patch": "partial_update", "delete": "destroy"}), name="workspace-quick-links", ), # Widgets @@ -254,9 +244,7 @@ urlpatterns = [ ), path( "workspaces//stickies//", - WorkspaceStickyViewSet.as_view( - {"get": "retrieve", "patch": "partial_update", "delete": "destroy"} - ), + WorkspaceStickyViewSet.as_view({"get": "retrieve", "patch": "partial_update", "delete": "destroy"}), name="workspace-sticky", ), # User Preference diff --git a/apps/api/plane/app/views/analytic/advance.py b/apps/api/plane/app/views/analytic/advance.py index 8a47cdd02..1a5b1b34c 100644 --- a/apps/api/plane/app/views/analytic/advance.py +++ b/apps/api/plane/app/views/analytic/advance.py @@ -41,26 +41,16 @@ class AdvanceAnalyticsEndpoint(AdvanceAnalyticsBaseView): def get_filtered_count() -> int: if self.filters["analytics_date_range"]: return queryset.filter( - created_at__gte=self.filters["analytics_date_range"]["current"][ - "gte" - ], - created_at__lte=self.filters["analytics_date_range"]["current"][ - "lte" - ], + created_at__gte=self.filters["analytics_date_range"]["current"]["gte"], + created_at__lte=self.filters["analytics_date_range"]["current"]["lte"], ).count() return queryset.count() def get_previous_count() -> int: - if self.filters["analytics_date_range"] and self.filters[ - "analytics_date_range" - ].get("previous"): + if self.filters["analytics_date_range"] and self.filters["analytics_date_range"].get("previous"): return queryset.filter( - created_at__gte=self.filters["analytics_date_range"]["previous"][ - "gte" - ], - created_at__lte=self.filters["analytics_date_range"]["previous"][ - "lte" - ], + created_at__gte=self.filters["analytics_date_range"]["previous"]["gte"], + created_at__lte=self.filters["analytics_date_range"]["previous"]["lte"], ).count() return 0 @@ -71,39 +61,27 @@ class AdvanceAnalyticsEndpoint(AdvanceAnalyticsBaseView): def get_overview_data(self) -> Dict[str, Dict[str, int]]: members_query = WorkspaceMember.objects.filter( - workspace__slug=self._workspace_slug, is_active=True + workspace__slug=self._workspace_slug, is_active=True, member__is_bot=False ) if self.request.GET.get("project_ids", None): project_ids = self.request.GET.get("project_ids", None) project_ids = [str(project_id) for project_id in project_ids.split(",")] members_query = ProjectMember.objects.filter( - project_id__in=project_ids, is_active=True + project_id__in=project_ids, is_active=True, member__is_bot=False ) return { "total_users": self.get_filtered_counts(members_query), - "total_admins": self.get_filtered_counts( - members_query.filter(role=ROLE.ADMIN.value) - ), - "total_members": self.get_filtered_counts( - members_query.filter(role=ROLE.MEMBER.value) - ), - "total_guests": self.get_filtered_counts( - members_query.filter(role=ROLE.GUEST.value) - ), - "total_projects": self.get_filtered_counts( - Project.objects.filter(**self.filters["project_filters"]) - ), - "total_work_items": self.get_filtered_counts( - Issue.issue_objects.filter(**self.filters["base_filters"]) - ), - "total_cycles": self.get_filtered_counts( - Cycle.objects.filter(**self.filters["base_filters"]) - ), + "total_admins": self.get_filtered_counts(members_query.filter(role=ROLE.ADMIN.value)), + "total_members": self.get_filtered_counts(members_query.filter(role=ROLE.MEMBER.value)), + "total_guests": self.get_filtered_counts(members_query.filter(role=ROLE.GUEST.value)), + "total_projects": self.get_filtered_counts(Project.objects.filter(**self.filters["project_filters"])), + "total_work_items": self.get_filtered_counts(Issue.issue_objects.filter(**self.filters["base_filters"])), + "total_cycles": self.get_filtered_counts(Cycle.objects.filter(**self.filters["base_filters"])), "total_intake": self.get_filtered_counts( Issue.objects.filter(**self.filters["base_filters"]).filter( - issue_intake__status__in=["-2", "0"] + issue_intake__status__in=["-2", "-1", "0", "1", "2"] # TODO: Add description for reference. ) ), } @@ -113,18 +91,10 @@ class AdvanceAnalyticsEndpoint(AdvanceAnalyticsBaseView): return { "total_work_items": self.get_filtered_counts(base_queryset), - "started_work_items": self.get_filtered_counts( - base_queryset.filter(state__group="started") - ), - "backlog_work_items": self.get_filtered_counts( - base_queryset.filter(state__group="backlog") - ), - "un_started_work_items": self.get_filtered_counts( - base_queryset.filter(state__group="unstarted") - ), - "completed_work_items": self.get_filtered_counts( - base_queryset.filter(state__group="completed") - ), + "started_work_items": self.get_filtered_counts(base_queryset.filter(state__group="started")), + "backlog_work_items": self.get_filtered_counts(base_queryset.filter(state__group="backlog")), + "un_started_work_items": self.get_filtered_counts(base_queryset.filter(state__group="unstarted")), + "completed_work_items": self.get_filtered_counts(base_queryset.filter(state__group="completed")), } @allow_permission([ROLE.ADMIN, ROLE.MEMBER], level="WORKSPACE") @@ -153,9 +123,7 @@ class AdvanceAnalyticsStatsEndpoint(AdvanceAnalyticsBaseView): # Apply date range filter if available if self.filters["chart_period_range"]: start_date, end_date = self.filters["chart_period_range"] - base_queryset = base_queryset.filter( - created_at__date__gte=start_date, created_at__date__lte=end_date - ) + base_queryset = base_queryset.filter(created_at__date__gte=start_date, created_at__date__lte=end_date) return ( base_queryset.values("project_id", "project__name") @@ -212,24 +180,16 @@ class AdvanceAnalyticsChartEndpoint(AdvanceAnalyticsBaseView): } total_work_items = base_queryset.filter(**date_filter).count() - total_cycles = Cycle.objects.filter( - **self.filters["base_filters"], **date_filter - ).count() - total_modules = Module.objects.filter( - **self.filters["base_filters"], **date_filter - ).count() + total_cycles = Cycle.objects.filter(**self.filters["base_filters"], **date_filter).count() + total_modules = Module.objects.filter(**self.filters["base_filters"], **date_filter).count() total_intake = Issue.objects.filter( issue_intake__isnull=False, **self.filters["base_filters"], **date_filter ).count() total_members = WorkspaceMember.objects.filter( workspace__slug=self._workspace_slug, is_active=True, **date_filter ).count() - total_pages = ProjectPage.objects.filter( - **self.filters["base_filters"], **date_filter - ).count() - total_views = IssueView.objects.filter( - **self.filters["base_filters"], **date_filter - ).count() + total_pages = ProjectPage.objects.filter(**self.filters["base_filters"], **date_filter).count() + total_views = IssueView.objects.filter(**self.filters["base_filters"], **date_filter).count() data = { "work_items": total_work_items, @@ -255,9 +215,7 @@ class AdvanceAnalyticsChartEndpoint(AdvanceAnalyticsBaseView): queryset = ( Issue.issue_objects.filter(**self.filters["base_filters"]) .select_related("workspace", "state", "parent") - .prefetch_related( - "assignees", "labels", "issue_module__module", "issue_cycle__cycle" - ) + .prefetch_related("assignees", "labels", "issue_module__module", "issue_cycle__cycle") ) workspace = Workspace.objects.get(slug=self._workspace_slug) @@ -266,9 +224,7 @@ class AdvanceAnalyticsChartEndpoint(AdvanceAnalyticsBaseView): # Apply date range filter if available if self.filters["chart_period_range"]: start_date, end_date = self.filters["chart_period_range"] - queryset = queryset.filter( - created_at__date__gte=start_date, created_at__date__lte=end_date - ) + queryset = queryset.filter(created_at__date__gte=start_date, created_at__date__lte=end_date) # Annotate by month and count monthly_stats = ( @@ -311,9 +267,7 @@ class AdvanceAnalyticsChartEndpoint(AdvanceAnalyticsBaseView): ) # Move to next month if current_month.month == 12: - current_month = current_month.replace( - year=current_month.year + 1, month=1 - ) + current_month = current_month.replace(year=current_month.year + 1, month=1) else: current_month = current_month.replace(month=current_month.month + 1) @@ -338,17 +292,13 @@ class AdvanceAnalyticsChartEndpoint(AdvanceAnalyticsBaseView): queryset = ( Issue.issue_objects.filter(**self.filters["base_filters"]) .select_related("workspace", "state", "parent") - .prefetch_related( - "assignees", "labels", "issue_module__module", "issue_cycle__cycle" - ) + .prefetch_related("assignees", "labels", "issue_module__module", "issue_cycle__cycle") ) # Apply date range filter if available if self.filters["chart_period_range"]: start_date, end_date = self.filters["chart_period_range"] - queryset = queryset.filter( - created_at__date__gte=start_date, created_at__date__lte=end_date - ) + queryset = queryset.filter(created_at__date__gte=start_date, created_at__date__lte=end_date) return Response( build_analytics_chart(queryset, x_axis, group_by), diff --git a/apps/api/plane/app/views/analytic/base.py b/apps/api/plane/app/views/analytic/base.py index 631c6884a..6e9311a18 100644 --- a/apps/api/plane/app/views/analytic/base.py +++ b/apps/api/plane/app/views/analytic/base.py @@ -55,25 +55,16 @@ class AnalyticsEndpoint(BaseAPIView): valid_yaxis = ["issue_count", "estimate"] # Check for x-axis and y-axis as thery are required parameters - if ( - not x_axis - or not y_axis - or x_axis not in valid_xaxis_segment - or y_axis not in valid_yaxis - ): + if not x_axis or not y_axis or x_axis not in valid_xaxis_segment or y_axis not in valid_yaxis: return Response( - { - "error": "x-axis and y-axis dimensions are required and the values should be valid" - }, + {"error": "x-axis and y-axis dimensions are required and the values should be valid"}, status=status.HTTP_400_BAD_REQUEST, ) # If segment is present it cannot be same as x-axis if segment and (segment not in valid_xaxis_segment or x_axis == segment): return Response( - { - "error": "Both segment and x axis cannot be same and segment should be valid" - }, + {"error": "Both segment and x axis cannot be same and segment should be valid"}, status=status.HTTP_400_BAD_REQUEST, ) @@ -87,9 +78,7 @@ class AnalyticsEndpoint(BaseAPIView): total_issues = queryset.count() # Build the graph payload - distribution = build_graph_plot( - queryset=queryset, x_axis=x_axis, y_axis=y_axis, segment=segment - ) + distribution = build_graph_plot(queryset=queryset, x_axis=x_axis, y_axis=y_axis, segment=segment) state_details = {} if x_axis in ["state_id"] or segment in ["state_id"]: @@ -118,10 +107,7 @@ class AnalyticsEndpoint(BaseAPIView): if x_axis in ["assignees__id"] or segment in ["assignees__id"]: assignee_details = ( Issue.issue_objects.filter( - Q( - Q(assignees__avatar__isnull=False) - | Q(assignees__avatar_asset__isnull=False) - ), + Q(Q(assignees__avatar__isnull=False) | Q(assignees__avatar_asset__isnull=False)), workspace__slug=slug, **filters, ) @@ -171,9 +157,7 @@ class AnalyticsEndpoint(BaseAPIView): ) module_details = {} - if x_axis in ["issue_module__module_id"] or segment in [ - "issue_module__module_id" - ]: + if x_axis in ["issue_module__module_id"] or segment in ["issue_module__module_id"]: module_details = ( Issue.issue_objects.filter( workspace__slug=slug, @@ -212,9 +196,7 @@ class AnalyticViewViewset(BaseViewSet): serializer.save(workspace_id=workspace.id) def get_queryset(self): - return self.filter_queryset( - super().get_queryset().filter(workspace__slug=self.kwargs.get("slug")) - ) + return self.filter_queryset(super().get_queryset().filter(workspace__slug=self.kwargs.get("slug"))) class SavedAnalyticEndpoint(BaseAPIView): @@ -235,9 +217,7 @@ class SavedAnalyticEndpoint(BaseAPIView): ) segment = request.GET.get("segment", False) - distribution = build_graph_plot( - queryset=queryset, x_axis=x_axis, y_axis=y_axis, segment=segment - ) + distribution = build_graph_plot(queryset=queryset, x_axis=x_axis, y_axis=y_axis, segment=segment) total_issues = queryset.count() return Response( {"total": total_issues, "distribution": distribution}, @@ -270,36 +250,23 @@ class ExportAnalyticsEndpoint(BaseAPIView): valid_yaxis = ["issue_count", "estimate"] # Check for x-axis and y-axis as thery are required parameters - if ( - not x_axis - or not y_axis - or x_axis not in valid_xaxis_segment - or y_axis not in valid_yaxis - ): + if not x_axis or not y_axis or x_axis not in valid_xaxis_segment or y_axis not in valid_yaxis: return Response( - { - "error": "x-axis and y-axis dimensions are required and the values should be valid" - }, + {"error": "x-axis and y-axis dimensions are required and the values should be valid"}, status=status.HTTP_400_BAD_REQUEST, ) # If segment is present it cannot be same as x-axis if segment and (segment not in valid_xaxis_segment or x_axis == segment): return Response( - { - "error": "Both segment and x axis cannot be same and segment should be valid" - }, + {"error": "Both segment and x axis cannot be same and segment should be valid"}, status=status.HTTP_400_BAD_REQUEST, ) - analytic_export_task.delay( - email=request.user.email, data=request.data, slug=slug - ) + analytic_export_task.delay(email=request.user.email, data=request.data, slug=slug) return Response( - { - "message": f"Once the export is ready it will be emailed to you at {str(request.user.email)}" - }, + {"message": f"Once the export is ready it will be emailed to you at {str(request.user.email)}"}, status=status.HTTP_200_OK, ) @@ -315,9 +282,7 @@ class DefaultAnalyticsEndpoint(BaseAPIView): state_groups = base_issues.annotate(state_group=F("state__group")) total_issues_classified = ( - state_groups.values("state_group") - .annotate(state_count=Count("state_group")) - .order_by("state_group") + state_groups.values("state_group").annotate(state_count=Count("state_group")).order_by("state_group") ) open_issues_groups = ["backlog", "unstarted", "started"] @@ -362,9 +327,7 @@ class DefaultAnalyticsEndpoint(BaseAPIView): ), ), # If `avatar_asset` is None, fall back to using `avatar` field directly - When( - created_by__avatar_asset__isnull=True, then="created_by__avatar" - ), + When(created_by__avatar_asset__isnull=True, then="created_by__avatar"), default=Value(None), output_field=models.CharField(), ) @@ -395,9 +358,7 @@ class DefaultAnalyticsEndpoint(BaseAPIView): ), ), # If `avatar_asset` is None, fall back to using `avatar` field directly - When( - assignees__avatar_asset__isnull=True, then="assignees__avatar" - ), + When(assignees__avatar_asset__isnull=True, then="assignees__avatar"), default=Value(None), output_field=models.CharField(), ) @@ -422,9 +383,7 @@ class DefaultAnalyticsEndpoint(BaseAPIView): ), ), # If `avatar_asset` is None, fall back to using `avatar` field directly - When( - assignees__avatar_asset__isnull=True, then="assignees__avatar" - ), + When(assignees__avatar_asset__isnull=True, then="assignees__avatar"), default=Value(None), output_field=models.CharField(), ) @@ -485,9 +444,7 @@ class ProjectStatsEndpoint(BaseAPIView): if "completed_issues" in requested_fields: annotations["completed_issues"] = ( - Issue.issue_objects.filter( - project_id=OuterRef("pk"), state__group="completed" - ) + Issue.issue_objects.filter(project_id=OuterRef("pk"), state__group__in=["completed", "cancelled"]) .order_by() .annotate(count=Func(F("id"), function="Count")) .values("count") @@ -511,9 +468,7 @@ class ProjectStatsEndpoint(BaseAPIView): if "total_members" in requested_fields: annotations["total_members"] = ( - ProjectMember.objects.filter( - project_id=OuterRef("id"), member__is_bot=False, is_active=True - ) + ProjectMember.objects.filter(project_id=OuterRef("id"), member__is_bot=False, is_active=True) .order_by() .annotate(count=Func(F("id"), function="Count")) .values("count") diff --git a/apps/api/plane/app/views/analytic/project_analytics.py b/apps/api/plane/app/views/analytic/project_analytics.py index 655f8e989..2529900b0 100644 --- a/apps/api/plane/app/views/analytic/project_analytics.py +++ b/apps/api/plane/app/views/analytic/project_analytics.py @@ -42,12 +42,8 @@ class ProjectAdvanceAnalyticsEndpoint(ProjectAdvanceAnalyticsBaseView): def get_filtered_count() -> int: if self.filters["analytics_date_range"]: return queryset.filter( - created_at__gte=self.filters["analytics_date_range"]["current"][ - "gte" - ], - created_at__lte=self.filters["analytics_date_range"]["current"][ - "lte" - ], + created_at__gte=self.filters["analytics_date_range"]["current"]["gte"], + created_at__lte=self.filters["analytics_date_range"]["current"]["lte"], ).count() return queryset.count() @@ -55,42 +51,30 @@ class ProjectAdvanceAnalyticsEndpoint(ProjectAdvanceAnalyticsBaseView): "count": get_filtered_count(), } - def get_work_items_stats( - self, project_id, cycle_id=None, module_id=None - ) -> Dict[str, Dict[str, int]]: + def get_work_items_stats(self, project_id, cycle_id=None, module_id=None) -> Dict[str, Dict[str, int]]: """ Returns work item stats for the workspace, or filtered by cycle_id or module_id if provided. """ base_queryset = None if cycle_id is not None: - cycle_issues = CycleIssue.objects.filter( - **self.filters["base_filters"], cycle_id=cycle_id - ).values_list("issue_id", flat=True) + cycle_issues = CycleIssue.objects.filter(**self.filters["base_filters"], cycle_id=cycle_id).values_list( + "issue_id", flat=True + ) base_queryset = Issue.issue_objects.filter(id__in=cycle_issues) elif module_id is not None: - module_issues = ModuleIssue.objects.filter( - **self.filters["base_filters"], module_id=module_id - ).values_list("issue_id", flat=True) + module_issues = ModuleIssue.objects.filter(**self.filters["base_filters"], module_id=module_id).values_list( + "issue_id", flat=True + ) base_queryset = Issue.issue_objects.filter(id__in=module_issues) else: - base_queryset = Issue.issue_objects.filter( - **self.filters["base_filters"], project_id=project_id - ) + base_queryset = Issue.issue_objects.filter(**self.filters["base_filters"], project_id=project_id) return { "total_work_items": self.get_filtered_counts(base_queryset), - "started_work_items": self.get_filtered_counts( - base_queryset.filter(state__group="started") - ), - "backlog_work_items": self.get_filtered_counts( - base_queryset.filter(state__group="backlog") - ), - "un_started_work_items": self.get_filtered_counts( - base_queryset.filter(state__group="unstarted") - ), - "completed_work_items": self.get_filtered_counts( - base_queryset.filter(state__group="completed") - ), + "started_work_items": self.get_filtered_counts(base_queryset.filter(state__group="started")), + "backlog_work_items": self.get_filtered_counts(base_queryset.filter(state__group="backlog")), + "un_started_work_items": self.get_filtered_counts(base_queryset.filter(state__group="unstarted")), + "completed_work_items": self.get_filtered_counts(base_queryset.filter(state__group="completed")), } @allow_permission([ROLE.ADMIN, ROLE.MEMBER]) @@ -101,9 +85,7 @@ class ProjectAdvanceAnalyticsEndpoint(ProjectAdvanceAnalyticsBaseView): cycle_id = request.GET.get("cycle_id", None) module_id = request.GET.get("module_id", None) return Response( - self.get_work_items_stats( - cycle_id=cycle_id, module_id=module_id, project_id=project_id - ), + self.get_work_items_stats(cycle_id=cycle_id, module_id=module_id, project_id=project_id), status=status.HTTP_200_OK, ) @@ -116,9 +98,7 @@ class ProjectAdvanceAnalyticsStatsEndpoint(ProjectAdvanceAnalyticsBaseView): # Apply date range filter if available if self.filters["chart_period_range"]: start_date, end_date = self.filters["chart_period_range"] - base_queryset = base_queryset.filter( - created_at__date__gte=start_date, created_at__date__lte=end_date - ) + base_queryset = base_queryset.filter(created_at__date__gte=start_date, created_at__date__lte=end_date) return ( base_queryset.values("project_id", "project__name") @@ -132,24 +112,20 @@ class ProjectAdvanceAnalyticsStatsEndpoint(ProjectAdvanceAnalyticsBaseView): .order_by("project_id") ) - def get_work_items_stats( - self, project_id, cycle_id=None, module_id=None - ) -> Dict[str, Dict[str, int]]: + def get_work_items_stats(self, project_id, cycle_id=None, module_id=None) -> Dict[str, Dict[str, int]]: base_queryset = None if cycle_id is not None: - cycle_issues = CycleIssue.objects.filter( - **self.filters["base_filters"], cycle_id=cycle_id - ).values_list("issue_id", flat=True) + cycle_issues = CycleIssue.objects.filter(**self.filters["base_filters"], cycle_id=cycle_id).values_list( + "issue_id", flat=True + ) base_queryset = Issue.issue_objects.filter(id__in=cycle_issues) elif module_id is not None: - module_issues = ModuleIssue.objects.filter( - **self.filters["base_filters"], module_id=module_id - ).values_list("issue_id", flat=True) + module_issues = ModuleIssue.objects.filter(**self.filters["base_filters"], module_id=module_id).values_list( + "issue_id", flat=True + ) base_queryset = Issue.issue_objects.filter(id__in=module_issues) else: - base_queryset = Issue.issue_objects.filter( - **self.filters["base_filters"], project_id=project_id - ) + base_queryset = Issue.issue_objects.filter(**self.filters["base_filters"], project_id=project_id) return ( base_queryset.annotate(display_name=F("assignees__display_name")) .annotate(assignee_id=F("assignees__id")) @@ -166,30 +142,18 @@ class ProjectAdvanceAnalyticsStatsEndpoint(ProjectAdvanceAnalyticsBaseView): ), ), # If `avatar_asset` is None, fall back to using `avatar` field directly - When( - assignees__avatar_asset__isnull=True, then="assignees__avatar" - ), + When(assignees__avatar_asset__isnull=True, then="assignees__avatar"), default=Value(None), output_field=models.CharField(), ) ) .values("display_name", "assignee_id", "avatar_url") .annotate( - cancelled_work_items=Count( - "id", filter=Q(state__group="cancelled"), distinct=True - ), - completed_work_items=Count( - "id", filter=Q(state__group="completed"), distinct=True - ), - backlog_work_items=Count( - "id", filter=Q(state__group="backlog"), distinct=True - ), - un_started_work_items=Count( - "id", filter=Q(state__group="unstarted"), distinct=True - ), - started_work_items=Count( - "id", filter=Q(state__group="started"), distinct=True - ), + cancelled_work_items=Count("id", filter=Q(state__group="cancelled"), distinct=True), + completed_work_items=Count("id", filter=Q(state__group="completed"), distinct=True), + backlog_work_items=Count("id", filter=Q(state__group="backlog"), distinct=True), + un_started_work_items=Count("id", filter=Q(state__group="unstarted"), distinct=True), + started_work_items=Count("id", filter=Q(state__group="started"), distinct=True), ) .order_by("display_name") ) @@ -204,9 +168,7 @@ class ProjectAdvanceAnalyticsStatsEndpoint(ProjectAdvanceAnalyticsBaseView): cycle_id = request.GET.get("cycle_id", None) module_id = request.GET.get("module_id", None) return Response( - self.get_work_items_stats( - project_id=project_id, cycle_id=cycle_id, module_id=module_id - ), + self.get_work_items_stats(project_id=project_id, cycle_id=cycle_id, module_id=module_id), status=status.HTTP_200_OK, ) @@ -214,23 +176,19 @@ class ProjectAdvanceAnalyticsStatsEndpoint(ProjectAdvanceAnalyticsBaseView): class ProjectAdvanceAnalyticsChartEndpoint(ProjectAdvanceAnalyticsBaseView): - def work_item_completion_chart( - self, project_id, cycle_id=None, module_id=None - ) -> Dict[str, Any]: + def work_item_completion_chart(self, project_id, cycle_id=None, module_id=None) -> Dict[str, Any]: # Get the base queryset queryset = ( Issue.issue_objects.filter(**self.filters["base_filters"]) .filter(project_id=project_id) .select_related("workspace", "state", "parent") - .prefetch_related( - "assignees", "labels", "issue_module__module", "issue_cycle__cycle" - ) + .prefetch_related("assignees", "labels", "issue_module__module", "issue_cycle__cycle") ) if cycle_id is not None: - cycle_issues = CycleIssue.objects.filter( - **self.filters["base_filters"], cycle_id=cycle_id - ).values_list("issue_id", flat=True) + cycle_issues = CycleIssue.objects.filter(**self.filters["base_filters"], cycle_id=cycle_id).values_list( + "issue_id", flat=True + ) cycle = Cycle.objects.filter(id=cycle_id).first() if cycle and cycle.start_date: start_date = cycle.start_date.date() @@ -240,9 +198,9 @@ class ProjectAdvanceAnalyticsChartEndpoint(ProjectAdvanceAnalyticsBaseView): queryset = cycle_issues elif module_id is not None: - module_issues = ModuleIssue.objects.filter( - **self.filters["base_filters"], module_id=module_id - ).values_list("issue_id", flat=True) + module_issues = ModuleIssue.objects.filter(**self.filters["base_filters"], module_id=module_id).values_list( + "issue_id", flat=True + ) module = Module.objects.filter(id=module_id).first() if module and module.start_date: start_date = module.start_date @@ -264,9 +222,7 @@ class ProjectAdvanceAnalyticsChartEndpoint(ProjectAdvanceAnalyticsBaseView): queryset.values("created_at__date") .annotate( created_count=Count("id"), - completed_count=Count( - "id", filter=Q(issue__state__group="completed") - ), + completed_count=Count("id", filter=Q(issue__state__group="completed")), ) .order_by("created_at__date") ) @@ -285,9 +241,7 @@ class ProjectAdvanceAnalyticsChartEndpoint(ProjectAdvanceAnalyticsBaseView): current_date = start_date while current_date <= end_date: date_str = current_date.strftime("%Y-%m-%d") - stats = stats_dict.get( - date_str, {"created_count": 0, "completed_count": 0} - ) + stats = stats_dict.get(date_str, {"created_count": 0, "completed_count": 0}) data.append( { "key": date_str, @@ -302,9 +256,7 @@ class ProjectAdvanceAnalyticsChartEndpoint(ProjectAdvanceAnalyticsBaseView): # Apply date range filter if available if self.filters["chart_period_range"]: start_date, end_date = self.filters["chart_period_range"] - queryset = queryset.filter( - created_at__date__gte=start_date, created_at__date__lte=end_date - ) + queryset = queryset.filter(created_at__date__gte=start_date, created_at__date__lte=end_date) # Annotate by month and count monthly_stats = ( @@ -335,9 +287,7 @@ class ProjectAdvanceAnalyticsChartEndpoint(ProjectAdvanceAnalyticsBaseView): while current_month <= last_month: date_str = current_month.strftime("%Y-%m-%d") - stats = stats_dict.get( - date_str, {"created_count": 0, "completed_count": 0} - ) + stats = stats_dict.get(date_str, {"created_count": 0, "completed_count": 0}) data.append( { "key": date_str, @@ -349,9 +299,7 @@ class ProjectAdvanceAnalyticsChartEndpoint(ProjectAdvanceAnalyticsBaseView): ) # Move to next month if current_month.month == 12: - current_month = current_month.replace( - year=current_month.year + 1, month=1 - ) + current_month = current_month.replace(year=current_month.year + 1, month=1) else: current_month = current_month.replace(month=current_month.month + 1) @@ -376,16 +324,14 @@ class ProjectAdvanceAnalyticsChartEndpoint(ProjectAdvanceAnalyticsBaseView): Issue.issue_objects.filter(**self.filters["base_filters"]) .filter(project_id=project_id) .select_related("workspace", "state", "parent") - .prefetch_related( - "assignees", "labels", "issue_module__module", "issue_cycle__cycle" - ) + .prefetch_related("assignees", "labels", "issue_module__module", "issue_cycle__cycle") ) # Apply cycle/module filters if present if cycle_id is not None: - cycle_issues = CycleIssue.objects.filter( - **self.filters["base_filters"], cycle_id=cycle_id - ).values_list("issue_id", flat=True) + cycle_issues = CycleIssue.objects.filter(**self.filters["base_filters"], cycle_id=cycle_id).values_list( + "issue_id", flat=True + ) queryset = queryset.filter(id__in=cycle_issues) elif module_id is not None: @@ -397,9 +343,7 @@ class ProjectAdvanceAnalyticsChartEndpoint(ProjectAdvanceAnalyticsBaseView): # Apply date range filter if available if self.filters["chart_period_range"]: start_date, end_date = self.filters["chart_period_range"] - queryset = queryset.filter( - created_at__date__gte=start_date, created_at__date__lte=end_date - ) + queryset = queryset.filter(created_at__date__gte=start_date, created_at__date__lte=end_date) return Response( build_analytics_chart(queryset, x_axis, group_by), @@ -412,9 +356,7 @@ class ProjectAdvanceAnalyticsChartEndpoint(ProjectAdvanceAnalyticsBaseView): module_id = request.GET.get("module_id", None) return Response( - self.work_item_completion_chart( - project_id=project_id, cycle_id=cycle_id, module_id=module_id - ), + self.work_item_completion_chart(project_id=project_id, cycle_id=cycle_id, module_id=module_id), status=status.HTTP_200_OK, ) diff --git a/apps/api/plane/app/views/api.py b/apps/api/plane/app/views/api.py index fa7cc7466..419859902 100644 --- a/apps/api/plane/app/views/api.py +++ b/apps/api/plane/app/views/api.py @@ -65,9 +65,7 @@ class ServiceApiTokenEndpoint(BaseAPIView): def post(self, request: Request, slug: str) -> Response: workspace = Workspace.objects.get(slug=slug) - api_token = APIToken.objects.filter( - workspace=workspace, is_service=True - ).first() + api_token = APIToken.objects.filter(workspace=workspace, is_service=True).first() if api_token: return Response({"token": str(api_token.token)}, status=status.HTTP_200_OK) @@ -83,6 +81,4 @@ class ServiceApiTokenEndpoint(BaseAPIView): user_type=user_type, is_service=True, ) - return Response( - {"token": str(api_token.token)}, status=status.HTTP_201_CREATED - ) + return Response({"token": str(api_token.token)}, status=status.HTTP_201_CREATED) diff --git a/apps/api/plane/app/views/asset/base.py b/apps/api/plane/app/views/asset/base.py index d30f0bb26..522d4af75 100644 --- a/apps/api/plane/app/views/asset/base.py +++ b/apps/api/plane/app/views/asset/base.py @@ -20,12 +20,8 @@ class FileAssetEndpoint(BaseAPIView): asset_key = str(workspace_id) + "/" + asset_key files = FileAsset.objects.filter(asset=asset_key) if files.exists(): - serializer = FileAssetSerializer( - files, context={"request": request}, many=True - ) - return Response( - {"data": serializer.data, "status": True}, status=status.HTTP_200_OK - ) + serializer = FileAssetSerializer(files, context={"request": request}, many=True) + return Response({"data": serializer.data, "status": True}, status=status.HTTP_200_OK) else: return Response( {"error": "Asset key does not exist", "status": False}, @@ -65,9 +61,7 @@ class UserAssetsEndpoint(BaseAPIView): files = FileAsset.objects.filter(asset=asset_key, created_by=request.user) if files.exists(): serializer = FileAssetSerializer(files, context={"request": request}) - return Response( - {"data": serializer.data, "status": True}, status=status.HTTP_200_OK - ) + return Response({"data": serializer.data, "status": True}, status=status.HTTP_200_OK) else: return Response( {"error": "Asset key does not exist", "status": False}, diff --git a/apps/api/plane/app/views/asset/v2.py b/apps/api/plane/app/views/asset/v2.py index b69949621..610c5335f 100644 --- a/apps/api/plane/app/views/asset/v2.py +++ b/apps/api/plane/app/views/asset/v2.py @@ -44,9 +44,7 @@ class UserAssetsV2Endpoint(BaseAPIView): # 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, @@ -64,9 +62,7 @@ class UserAssetsV2Endpoint(BaseAPIView): # 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, @@ -82,9 +78,7 @@ class UserAssetsV2Endpoint(BaseAPIView): 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, @@ -97,9 +91,7 @@ class UserAssetsV2Endpoint(BaseAPIView): 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, @@ -159,9 +151,7 @@ class UserAssetsV2Endpoint(BaseAPIView): # 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( { @@ -198,9 +188,7 @@ class UserAssetsV2Endpoint(BaseAPIView): 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) @@ -264,18 +252,14 @@ class WorkspaceFileAssetEndpoint(BaseAPIView): 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 @@ -302,18 +286,14 @@ class WorkspaceFileAssetEndpoint(BaseAPIView): 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: @@ -374,17 +354,13 @@ class WorkspaceFileAssetEndpoint(BaseAPIView): 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( { @@ -421,9 +397,7 @@ class WorkspaceFileAssetEndpoint(BaseAPIView): 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) @@ -586,9 +560,7 @@ class ProjectAssetEndpoint(BaseAPIView): # 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( { @@ -618,9 +590,7 @@ class ProjectAssetEndpoint(BaseAPIView): @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() @@ -631,9 +601,7 @@ class ProjectAssetEndpoint(BaseAPIView): @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: @@ -666,9 +634,7 @@ class ProjectBulkAssetEndpoint(BaseAPIView): # 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) @@ -722,9 +688,7 @@ 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) diff --git a/apps/api/plane/app/views/base.py b/apps/api/plane/app/views/base.py index 4cefb75a1..0323302c5 100644 --- a/apps/api/plane/app/views/base.py +++ b/apps/api/plane/app/views/base.py @@ -72,11 +72,7 @@ class BaseViewSet(TimezoneMixin, ReadReplicaControlMixin, ModelViewSet, BasePagi response = super().handle_exception(exc) return response except Exception as e: - ( - print(e, traceback.format_exc()) - if settings.DEBUG - else print("Server Error") - ) + (print(e, traceback.format_exc()) if settings.DEBUG else print("Server Error")) if isinstance(e, IntegrityError): return Response( {"error": "The payload is not valid"}, @@ -115,9 +111,7 @@ class BaseViewSet(TimezoneMixin, ReadReplicaControlMixin, ModelViewSet, BasePagi if settings.DEBUG: from django.db import connection - print( - f"{request.method} - {request.get_full_path()} of Queries: {len(connection.queries)}" - ) + print(f"{request.method} - {request.get_full_path()} of Queries: {len(connection.queries)}") return response except Exception as exc: @@ -139,16 +133,12 @@ class BaseViewSet(TimezoneMixin, ReadReplicaControlMixin, ModelViewSet, BasePagi @property def fields(self): - fields = [ - field for field in self.request.GET.get("fields", "").split(",") if field - ] + fields = [field for field in self.request.GET.get("fields", "").split(",") if field] return fields if fields else None @property def expand(self): - expand = [ - expand for expand in self.request.GET.get("expand", "").split(",") if expand - ] + expand = [expand for expand in self.request.GET.get("expand", "").split(",") if expand] return expand if expand else None @@ -216,9 +206,7 @@ class BaseAPIView(TimezoneMixin, ReadReplicaControlMixin, APIView, BasePaginator if settings.DEBUG: from django.db import connection - print( - f"{request.method} - {request.get_full_path()} of Queries: {len(connection.queries)}" - ) + print(f"{request.method} - {request.get_full_path()} of Queries: {len(connection.queries)}") return response except Exception as exc: @@ -235,14 +223,10 @@ class BaseAPIView(TimezoneMixin, ReadReplicaControlMixin, APIView, BasePaginator @property def fields(self): - fields = [ - field for field in self.request.GET.get("fields", "").split(",") if field - ] + fields = [field for field in self.request.GET.get("fields", "").split(",") if field] return fields if fields else None @property def expand(self): - expand = [ - expand for expand in self.request.GET.get("expand", "").split(",") if expand - ] + expand = [expand for expand in self.request.GET.get("expand", "").split(",") if expand] return expand if expand else None diff --git a/apps/api/plane/app/views/cycle/archive.py b/apps/api/plane/app/views/cycle/archive.py index f58ad9aea..a2f89d53f 100644 --- a/apps/api/plane/app/views/cycle/archive.py +++ b/apps/api/plane/app/views/cycle/archive.py @@ -50,9 +50,7 @@ class CycleArchiveUnarchiveEndpoint(BaseAPIView): issue_cycle__deleted_at__isnull=True, ) .values("issue_cycle__cycle_id") - .annotate( - backlog_estimate_point=Sum(Cast("estimate_point__value", FloatField())) - ) + .annotate(backlog_estimate_point=Sum(Cast("estimate_point__value", FloatField()))) .values("backlog_estimate_point")[:1] ) unstarted_estimate_point = ( @@ -63,11 +61,7 @@ class CycleArchiveUnarchiveEndpoint(BaseAPIView): issue_cycle__deleted_at__isnull=True, ) .values("issue_cycle__cycle_id") - .annotate( - unstarted_estimate_point=Sum( - Cast("estimate_point__value", FloatField()) - ) - ) + .annotate(unstarted_estimate_point=Sum(Cast("estimate_point__value", FloatField()))) .values("unstarted_estimate_point")[:1] ) started_estimate_point = ( @@ -78,9 +72,7 @@ class CycleArchiveUnarchiveEndpoint(BaseAPIView): issue_cycle__deleted_at__isnull=True, ) .values("issue_cycle__cycle_id") - .annotate( - started_estimate_point=Sum(Cast("estimate_point__value", FloatField())) - ) + .annotate(started_estimate_point=Sum(Cast("estimate_point__value", FloatField()))) .values("started_estimate_point")[:1] ) cancelled_estimate_point = ( @@ -91,11 +83,7 @@ class CycleArchiveUnarchiveEndpoint(BaseAPIView): issue_cycle__deleted_at__isnull=True, ) .values("issue_cycle__cycle_id") - .annotate( - cancelled_estimate_point=Sum( - Cast("estimate_point__value", FloatField()) - ) - ) + .annotate(cancelled_estimate_point=Sum(Cast("estimate_point__value", FloatField()))) .values("cancelled_estimate_point")[:1] ) completed_estimate_point = ( @@ -106,11 +94,7 @@ class CycleArchiveUnarchiveEndpoint(BaseAPIView): issue_cycle__deleted_at__isnull=True, ) .values("issue_cycle__cycle_id") - .annotate( - completed_estimate_points=Sum( - Cast("estimate_point__value", FloatField()) - ) - ) + .annotate(completed_estimate_points=Sum(Cast("estimate_point__value", FloatField()))) .values("completed_estimate_points")[:1] ) total_estimate_point = ( @@ -120,9 +104,7 @@ class CycleArchiveUnarchiveEndpoint(BaseAPIView): issue_cycle__deleted_at__isnull=True, ) .values("issue_cycle__cycle_id") - .annotate( - total_estimate_points=Sum(Cast("estimate_point__value", FloatField())) - ) + .annotate(total_estimate_points=Sum(Cast("estimate_point__value", FloatField()))) .values("total_estimate_points")[:1] ) return ( @@ -138,9 +120,7 @@ class CycleArchiveUnarchiveEndpoint(BaseAPIView): .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( @@ -224,8 +204,7 @@ class CycleArchiveUnarchiveEndpoint(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")), @@ -279,9 +258,7 @@ class CycleArchiveUnarchiveEndpoint(BaseAPIView): ) ) .annotate( - total_estimate_points=Coalesce( - Subquery(total_estimate_point), Value(0, output_field=FloatField()) - ) + total_estimate_points=Coalesce(Subquery(total_estimate_point), Value(0, output_field=FloatField())) ) .order_by("-is_favorite", "name") .distinct() @@ -322,9 +299,7 @@ class CycleArchiveUnarchiveEndpoint(BaseAPIView): ).order_by("-is_favorite", "-created_at") return Response(queryset, status=status.HTTP_200_OK) else: - queryset = ( - self.get_queryset().filter(archived_at__isnull=False).filter(pk=pk) - ) + queryset = self.get_queryset().filter(archived_at__isnull=False).filter(pk=pk) data = ( self.get_queryset() .filter(pk=pk) @@ -415,9 +390,7 @@ class CycleArchiveUnarchiveEndpoint(BaseAPIView): ) ) .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()), @@ -452,9 +425,7 @@ class CycleArchiveUnarchiveEndpoint(BaseAPIView): .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()), @@ -531,11 +502,7 @@ class CycleArchiveUnarchiveEndpoint(BaseAPIView): "avatar_url", "display_name", ) - .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", @@ -571,11 +538,7 @@ class CycleArchiveUnarchiveEndpoint(BaseAPIView): .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", @@ -618,9 +581,7 @@ class CycleArchiveUnarchiveEndpoint(BaseAPIView): @allow_permission([ROLE.ADMIN, ROLE.MEMBER]) def post(self, request, slug, project_id, cycle_id): - 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( @@ -636,15 +597,11 @@ class CycleArchiveUnarchiveEndpoint(BaseAPIView): project_id=project_id, workspace__slug=slug, ).delete() - return Response( - {"archived_at": str(cycle.archived_at)}, status=status.HTTP_200_OK - ) + return Response({"archived_at": str(cycle.archived_at)}, status=status.HTTP_200_OK) @allow_permission([ROLE.ADMIN, ROLE.MEMBER]) def delete(self, request, slug, project_id, cycle_id): - 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) diff --git a/apps/api/plane/app/views/cycle/base.py b/apps/api/plane/app/views/cycle/base.py index a9a651c6d..3eda971b5 100644 --- a/apps/api/plane/app/views/cycle/base.py +++ b/apps/api/plane/app/views/cycle/base.py @@ -46,7 +46,6 @@ from plane.db.models import ( Label, User, Project, - ProjectMember, UserRecentVisit, ) from plane.utils.analytics_plot import burndown_plot @@ -97,9 +96,7 @@ class CycleViewSet(BaseViewSet): .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 +147,7 @@ class CycleViewSet(BaseViewSet): .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 +166,7 @@ class CycleViewSet(BaseViewSet): "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 +197,7 @@ class CycleViewSet(BaseViewSet): # 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 +264,10 @@ class CycleViewSet(BaseViewSet): @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 +307,7 @@ class CycleViewSet(BaseViewSet): 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 +323,13 @@ class CycleViewSet(BaseViewSet): 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 +337,21 @@ class CycleViewSet(BaseViewSet): 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 +451,7 @@ class CycleViewSet(BaseViewSet): ) 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 +473,7 @@ class CycleViewSet(BaseViewSet): 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 +524,7 @@ class CycleDateCheckEndpoint(BaseAPIView): 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, @@ -581,7 +543,7 @@ class CycleDateCheckEndpoint(BaseAPIView): if cycles.exists(): return Response( { - "error": "You have a cycle already on the given dates, if you want to create a draft cycle you can do that by removing dates", + "error": "You have a cycle already on the given dates, if you want to create a draft cycle you can do that by removing dates", # noqa: E501 "status": False, } ) @@ -635,14 +597,10 @@ class TransferCycleIssueEndpoint(BaseAPIView): status=status.HTTP_400_BAD_REQUEST, ) - 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() old_cycle = ( - Cycle.objects.filter( - workspace__slug=slug, project_id=project_id, pk=cycle_id - ) + Cycle.objects.filter(workspace__slug=slug, project_id=project_id, pk=cycle_id) .annotate( total_issues=Count( "issue_cycle", @@ -755,9 +713,7 @@ class TransferCycleIssueEndpoint(BaseAPIView): ) ) .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()), @@ -784,9 +740,7 @@ class TransferCycleIssueEndpoint(BaseAPIView): 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": item.get("avatar"), "avatar_url": item.get("avatar_url"), "total_estimates": item["total_estimates"], @@ -807,9 +761,7 @@ class TransferCycleIssueEndpoint(BaseAPIView): .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()), @@ -875,19 +827,13 @@ class TransferCycleIssueEndpoint(BaseAPIView): ), ), # If `avatar_asset` is None, fall back to using `avatar` field directly - When( - assignees__avatar_asset__isnull=True, then="assignees__avatar" - ), + When(assignees__avatar_asset__isnull=True, then="assignees__avatar"), default=Value(None), output_field=models.CharField(), ) ) .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", @@ -914,9 +860,7 @@ class TransferCycleIssueEndpoint(BaseAPIView): assignee_distribution_data = [ { "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": item.get("avatar"), "avatar_url": item.get("avatar_url"), "total_issues": item["total_issues"], @@ -938,11 +882,7 @@ class TransferCycleIssueEndpoint(BaseAPIView): .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", @@ -988,9 +928,7 @@ class TransferCycleIssueEndpoint(BaseAPIView): cycle_id=cycle_id, ) - 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, @@ -1018,9 +956,7 @@ class TransferCycleIssueEndpoint(BaseAPIView): if new_cycle.end_date is not None and new_cycle.end_date < timezone.now(): return Response( - { - "error": "The cycle where the issues are transferred is already completed" - }, + {"error": "The cycle where the issues are transferred is already completed"}, status=status.HTTP_400_BAD_REQUEST, ) @@ -1044,9 +980,7 @@ class TransferCycleIssueEndpoint(BaseAPIView): } ) - 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( @@ -1080,12 +1014,8 @@ class CycleUserPropertiesEndpoint(BaseAPIView): ) 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 ) @@ -1109,13 +1039,9 @@ class CycleUserPropertiesEndpoint(BaseAPIView): 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", @@ -1161,9 +1087,7 @@ class CycleProgressEndpoint(BaseAPIView): 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: @@ -1223,22 +1147,11 @@ class CycleProgressEndpoint(BaseAPIView): 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, @@ -1256,9 +1169,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", @@ -1341,9 +1252,7 @@ class CycleAnalyticsEndpoint(BaseAPIView): ) ) .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()), @@ -1378,9 +1287,7 @@ class CycleAnalyticsEndpoint(BaseAPIView): .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()), @@ -1482,11 +1389,7 @@ class CycleAnalyticsEndpoint(BaseAPIView): .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/cycle/issue.py b/apps/api/plane/app/views/cycle/issue.py index 0e143b58f..ad3923b17 100644 --- a/apps/api/plane/app/views/cycle/issue.py +++ b/apps/api/plane/app/views/cycle/issue.py @@ -74,9 +74,7 @@ class CycleIssueViewSet(BaseViewSet): return ( issues.annotate( cycle_id=Subquery( - CycleIssue.objects.filter( - issue=OuterRef("id"), deleted_at__isnull=True - ).values("cycle_id")[:1] + CycleIssue.objects.filter(issue=OuterRef("id"), deleted_at__isnull=True).values("cycle_id")[:1] ) ) .annotate( @@ -100,9 +98,7 @@ class CycleIssueViewSet(BaseViewSet): .annotate(count=Func(F("id"), function="Count")) .values("count") ) - .prefetch_related( - "assignees", "labels", "issue_module__module", "issue_cycle__cycle" - ) + .prefetch_related("assignees", "labels", "issue_module__module", "issue_cycle__cycle") ) @method_decorator(gzip_page) @@ -110,9 +106,7 @@ class CycleIssueViewSet(BaseViewSet): def list(self, request, slug, project_id, cycle_id): filters = issue_filters(request.query_params, "GET") issue_queryset = ( - 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) .filter(project_id=project_id) .filter(workspace__slug=slug) ) @@ -140,18 +134,14 @@ class CycleIssueViewSet(BaseViewSet): sub_group_by = request.GET.get("sub_group_by", False) # issue queryset - issue_queryset = issue_queryset_grouper( - queryset=issue_queryset, group_by=group_by, sub_group_by=sub_group_by - ) + issue_queryset = issue_queryset_grouper(queryset=issue_queryset, group_by=group_by, sub_group_by=sub_group_by) if group_by: # Check group and sub group value paginate if sub_group_by: if group_by == sub_group_by: return Response( - { - "error": "Group by and sub group by cannot have same parameters" - }, + {"error": "Group by and sub group by cannot have same parameters"}, status=status.HTTP_400_BAD_REQUEST, ) else: @@ -223,9 +213,7 @@ class CycleIssueViewSet(BaseViewSet): request=request, queryset=issue_queryset, total_count_queryset=total_issue_queryset, - on_results=lambda issues: issue_on_results( - group_by=group_by, issues=issues, sub_group_by=sub_group_by - ), + on_results=lambda issues: issue_on_results(group_by=group_by, issues=issues, sub_group_by=sub_group_by), ) @allow_permission([ROLE.ADMIN, ROLE.MEMBER]) @@ -233,26 +221,18 @@ class CycleIssueViewSet(BaseViewSet): issues = request.data.get("issues", []) if not issues: - return Response( - {"error": "Issues are required"}, status=status.HTTP_400_BAD_REQUEST - ) + return Response({"error": "Issues are required"}, 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( - { - "error": "The Cycle has already been completed so no new issues can be added" - }, + {"error": "The Cycle has already been completed so no new issues can be added"}, status=status.HTTP_400_BAD_REQUEST, ) # Get all CycleIssues 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] new_issues = list(set(issues) - set(existing_issues)) @@ -303,9 +283,7 @@ class CycleIssueViewSet(BaseViewSet): 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()), diff --git a/apps/api/plane/app/views/estimate/base.py b/apps/api/plane/app/views/estimate/base.py index c0e931ca6..f54115a4f 100644 --- a/apps/api/plane/app/views/estimate/base.py +++ b/apps/api/plane/app/views/estimate/base.py @@ -56,9 +56,7 @@ class BulkEstimatePointEndpoint(BaseViewSet): serializer = EstimateReadSerializer(estimates, many=True) return Response(serializer.data, status=status.HTTP_200_OK) - @invalidate_cache( - path="/api/workspaces/:slug/estimates/", url_params=True, user=False - ) + @invalidate_cache(path="/api/workspaces/:slug/estimates/", url_params=True, user=False) def create(self, request, slug, project_id): estimate = request.data.get("estimate") estimate_name = estimate.get("name", generate_random_name()) @@ -73,9 +71,7 @@ class BulkEstimatePointEndpoint(BaseViewSet): estimate_points = request.data.get("estimate_points", []) - serializer = EstimatePointSerializer( - data=request.data.get("estimate_points"), many=True - ) + serializer = EstimatePointSerializer(data=request.data.get("estimate_points"), many=True) if not serializer.is_valid(): return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) @@ -101,15 +97,11 @@ class BulkEstimatePointEndpoint(BaseViewSet): return Response(serializer.data, status=status.HTTP_200_OK) def retrieve(self, request, slug, project_id, estimate_id): - estimate = Estimate.objects.get( - pk=estimate_id, workspace__slug=slug, project_id=project_id - ) + estimate = Estimate.objects.get(pk=estimate_id, workspace__slug=slug, project_id=project_id) serializer = EstimateReadSerializer(estimate) return Response(serializer.data, status=status.HTTP_200_OK) - @invalidate_cache( - path="/api/workspaces/:slug/estimates/", url_params=True, user=False - ) + @invalidate_cache(path="/api/workspaces/:slug/estimates/", url_params=True, user=False) def partial_update(self, request, slug, project_id, estimate_id): if not len(request.data.get("estimate_points", [])): return Response( @@ -127,9 +119,7 @@ class BulkEstimatePointEndpoint(BaseViewSet): estimate_points_data = request.data.get("estimate_points", []) estimate_points = EstimatePoint.objects.filter( - pk__in=[ - estimate_point.get("id") for estimate_point in estimate_points_data - ], + pk__in=[estimate_point.get("id") for estimate_point in estimate_points_data], workspace__slug=slug, project_id=project_id, estimate_id=estimate_id, @@ -138,34 +128,20 @@ class BulkEstimatePointEndpoint(BaseViewSet): updated_estimate_points = [] for estimate_point in estimate_points: # Find the data for that estimate point - estimate_point_data = [ - point - for point in estimate_points_data - if point.get("id") == str(estimate_point.id) - ] + estimate_point_data = [point for point in estimate_points_data if point.get("id") == str(estimate_point.id)] if len(estimate_point_data): - estimate_point.value = estimate_point_data[0].get( - "value", estimate_point.value - ) - estimate_point.key = estimate_point_data[0].get( - "key", estimate_point.key - ) + estimate_point.value = estimate_point_data[0].get("value", estimate_point.value) + estimate_point.key = estimate_point_data[0].get("key", estimate_point.key) updated_estimate_points.append(estimate_point) - EstimatePoint.objects.bulk_update( - updated_estimate_points, ["key", "value"], batch_size=10 - ) + EstimatePoint.objects.bulk_update(updated_estimate_points, ["key", "value"], batch_size=10) estimate_serializer = EstimateReadSerializer(estimate) return Response(estimate_serializer.data, status=status.HTTP_200_OK) - @invalidate_cache( - path="/api/workspaces/:slug/estimates/", url_params=True, user=False - ) + @invalidate_cache(path="/api/workspaces/:slug/estimates/", url_params=True, user=False) def destroy(self, request, slug, project_id, estimate_id): - estimate = Estimate.objects.get( - pk=estimate_id, workspace__slug=slug, project_id=project_id - ) + estimate = Estimate.objects.get(pk=estimate_id, workspace__slug=slug, project_id=project_id) estimate.delete() return Response(status=status.HTTP_204_NO_CONTENT) @@ -196,9 +172,7 @@ class EstimatePointEndpoint(BaseViewSet): project_id=project_id, workspace__slug=slug, ) - serializer = EstimatePointSerializer( - estimate_point, data=request.data, partial=True - ) + serializer = EstimatePointSerializer(estimate_point, data=request.data, partial=True) if not serializer.is_valid(): return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) serializer.save() @@ -220,24 +194,12 @@ class EstimatePointEndpoint(BaseViewSet): for issue in issues: issue_activity.delay( type="issue.activity.updated", - requested_data=json.dumps( - { - "estimate_point": ( - str(new_estimate_id) if new_estimate_id else None - ) - } - ), + requested_data=json.dumps({"estimate_point": (str(new_estimate_id) if new_estimate_id else None)}), actor_id=str(request.user.id), issue_id=issue.id, project_id=str(project_id), current_instance=json.dumps( - { - "estimate_point": ( - str(issue.estimate_point_id) - if issue.estimate_point_id - else None - ) - } + {"estimate_point": (str(issue.estimate_point_id) if issue.estimate_point_id else None)} ), epoch=int(timezone.now().timestamp()), ) @@ -256,13 +218,7 @@ class EstimatePointEndpoint(BaseViewSet): issue_id=issue.id, project_id=str(project_id), current_instance=json.dumps( - { - "estimate_point": ( - str(issue.estimate_point_id) - if issue.estimate_point_id - else None - ) - } + {"estimate_point": (str(issue.estimate_point_id) if issue.estimate_point_id else None)} ), epoch=int(timezone.now().timestamp()), ) @@ -277,9 +233,7 @@ class EstimatePointEndpoint(BaseViewSet): estimate_point.key -= 1 updated_estimate_points.append(estimate_point) - EstimatePoint.objects.bulk_update( - updated_estimate_points, ["key"], batch_size=10 - ) + EstimatePoint.objects.bulk_update(updated_estimate_points, ["key"], batch_size=10) old_estimate_point.delete() diff --git a/apps/api/plane/app/views/exporter/base.py b/apps/api/plane/app/views/exporter/base.py index 8e683e56d..5f446ff94 100644 --- a/apps/api/plane/app/views/exporter/base.py +++ b/apps/api/plane/app/views/exporter/base.py @@ -62,18 +62,16 @@ class ExportIssuesEndpoint(BaseAPIView): @allow_permission(allowed_roles=[ROLE.ADMIN, ROLE.MEMBER], level="WORKSPACE") def get(self, request, slug): - exporter_history = ExporterHistory.objects.filter( - workspace__slug=slug, type="issue_exports" - ).select_related("workspace", "initiated_by") + exporter_history = ExporterHistory.objects.filter(workspace__slug=slug, type="issue_exports").select_related( + "workspace", "initiated_by" + ) if request.GET.get("per_page", False) and request.GET.get("cursor", False): return self.paginate( order_by=request.GET.get("order_by", "-created_at"), request=request, queryset=exporter_history, - on_results=lambda exporter_history: ExporterHistorySerializer( - exporter_history, many=True - ).data, + on_results=lambda exporter_history: ExporterHistorySerializer(exporter_history, many=True).data, ) else: return Response( diff --git a/apps/api/plane/app/views/external/base.py b/apps/api/plane/app/views/external/base.py index 864d0ff8c..2c554bbc8 100644 --- a/apps/api/plane/app/views/external/base.py +++ b/apps/api/plane/app/views/external/base.py @@ -108,8 +108,7 @@ def get_llm_config() -> Tuple[str | None, str | None, str | None]: if model not in provider.models: log_exception( ValueError( - f"Model {model} not supported by {provider.name}. " - f"Supported models: {', '.join(provider.models)}" + f"Model {model} not supported by {provider.name}. Supported models: {', '.join(provider.models)}" ) ) return None, None, None @@ -117,9 +116,7 @@ def get_llm_config() -> Tuple[str | None, str | None, str | None]: return api_key, model, provider_key -def get_llm_response( - task, prompt, api_key: str, model: str, provider: str -) -> Tuple[str | None, str | None]: +def get_llm_response(task, prompt, api_key: str, model: str, provider: str) -> Tuple[str | None, str | None]: """Helper to get LLM completion response""" final_text = task + "\n" + prompt try: @@ -157,13 +154,9 @@ class GPTIntegrationEndpoint(BaseAPIView): task = request.data.get("task", False) if not task: - return Response( - {"error": "Task is required"}, status=status.HTTP_400_BAD_REQUEST - ) + return Response({"error": "Task is required"}, status=status.HTTP_400_BAD_REQUEST) - text, error = get_llm_response( - task, request.data.get("prompt", False), api_key, model, provider - ) + text, error = get_llm_response(task, request.data.get("prompt", False), api_key, model, provider) if not text and error: return Response( {"error": "An internal error has occurred."}, @@ -197,13 +190,9 @@ class WorkspaceGPTIntegrationEndpoint(BaseAPIView): task = request.data.get("task", False) if not task: - return Response( - {"error": "Task is required"}, status=status.HTTP_400_BAD_REQUEST - ) + return Response({"error": "Task is required"}, status=status.HTTP_400_BAD_REQUEST) - text, error = get_llm_response( - task, request.data.get("prompt", False), api_key, model, provider - ) + text, error = get_llm_response(task, request.data.get("prompt", False), api_key, model, provider) if not text and error: return Response( {"error": "An internal error has occurred."}, diff --git a/apps/api/plane/app/views/intake/base.py b/apps/api/plane/app/views/intake/base.py index a55b40a7c..cc6379131 100644 --- a/apps/api/plane/app/views/intake/base.py +++ b/apps/api/plane/app/views/intake/base.py @@ -60,11 +60,7 @@ class IntakeViewSet(BaseViewSet): workspace__slug=self.kwargs.get("slug"), project_id=self.kwargs.get("project_id"), ) - .annotate( - pending_issue_count=Count( - "issue_intake", filter=Q(issue_intake__status=-2) - ) - ) + .annotate(pending_issue_count=Count("issue_intake", filter=Q(issue_intake__status=-2))) .select_related("workspace", "project") ) @@ -79,9 +75,7 @@ class IntakeViewSet(BaseViewSet): @allow_permission([ROLE.ADMIN, ROLE.MEMBER]) def destroy(self, request, slug, project_id, pk): - intake = Intake.objects.filter( - workspace__slug=slug, project_id=project_id, pk=pk - ).first() + intake = Intake.objects.filter(workspace__slug=slug, project_id=project_id, pk=pk).first() # Handle default intake delete if intake.is_default: return Response( @@ -109,16 +103,12 @@ class IntakeIssueViewSet(BaseViewSet): .prefetch_related( Prefetch( "issue_intake", - queryset=IntakeIssue.objects.only( - "status", "duplicate_to", "snoozed_till", "source" - ), + queryset=IntakeIssue.objects.only("status", "duplicate_to", "snoozed_till", "source"), ) ) .annotate( cycle_id=Subquery( - CycleIssue.objects.filter( - issue=OuterRef("id"), deleted_at__isnull=True - ).values("cycle_id")[:1] + CycleIssue.objects.filter(issue=OuterRef("id"), deleted_at__isnull=True).values("cycle_id")[:1] ) ) .annotate( @@ -147,10 +137,7 @@ class IntakeIssueViewSet(BaseViewSet): ArrayAgg( "labels__id", distinct=True, - filter=Q( - ~Q(labels__id__isnull=True) - & Q(label_issue__deleted_at__isnull=True) - ), + filter=Q(~Q(labels__id__isnull=True) & Q(label_issue__deleted_at__isnull=True)), ), Value([], output_field=ArrayField(UUIDField())), ), @@ -183,20 +170,14 @@ class IntakeIssueViewSet(BaseViewSet): @allow_permission([ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST]) def list(self, request, slug, project_id): - intake = Intake.objects.filter( - workspace__slug=slug, project_id=project_id - ).first() + intake = Intake.objects.filter(workspace__slug=slug, project_id=project_id).first() if not intake: - return Response( - {"error": "Intake not found"}, status=status.HTTP_404_NOT_FOUND - ) + return Response({"error": "Intake not found"}, status=status.HTTP_404_NOT_FOUND) project = Project.objects.get(pk=project_id) filters = issue_filters(request.GET, "GET", "issue__") intake_issue = ( - IntakeIssue.objects.filter( - intake_id=intake.id, project_id=project_id, **filters - ) + IntakeIssue.objects.filter(intake_id=intake.id, project_id=project_id, **filters) .select_related("issue") .prefetch_related("issue__labels") .annotate( @@ -204,21 +185,14 @@ class IntakeIssueViewSet(BaseViewSet): ArrayAgg( "issue__labels__id", distinct=True, - filter=Q( - ~Q(issue__labels__id__isnull=True) - & Q(issue__label_issue__deleted_at__isnull=True) - ), + filter=Q(~Q(issue__labels__id__isnull=True) & Q(issue__label_issue__deleted_at__isnull=True)), ), Value([], output_field=ArrayField(UUIDField())), ) ) ).order_by(request.GET.get("order_by", "-issue__created_at")) # Intake status filter - intake_status = [ - item - for item in request.GET.get("status", "-2").split(",") - if item != "null" - ] + intake_status = [item for item in request.GET.get("status", "-2").split(",") if item != "null"] if intake_status: intake_issue = intake_issue.filter(status__in=intake_status) @@ -236,17 +210,13 @@ class IntakeIssueViewSet(BaseViewSet): return self.paginate( request=request, queryset=(intake_issue), - on_results=lambda intake_issues: IntakeIssueSerializer( - intake_issues, many=True - ).data, + on_results=lambda intake_issues: IntakeIssueSerializer(intake_issues, many=True).data, ) @allow_permission([ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST]) def create(self, request, slug, project_id): if not request.data.get("issue", {}).get("name", False): - return Response( - {"error": "Name is required"}, status=status.HTTP_400_BAD_REQUEST - ) + return Response({"error": "Name is required"}, status=status.HTTP_400_BAD_REQUEST) # Check for valid priority if request.data.get("issue", {}).get("priority", "none") not in [ @@ -256,9 +226,7 @@ class IntakeIssueViewSet(BaseViewSet): "urgent", "none", ]: - return Response( - {"error": "Invalid priority"}, status=status.HTTP_400_BAD_REQUEST - ) + return Response({"error": "Invalid priority"}, status=status.HTTP_400_BAD_REQUEST) # create an issue project = Project.objects.get(pk=project_id) @@ -272,9 +240,7 @@ class IntakeIssueViewSet(BaseViewSet): ) if serializer.is_valid(): serializer.save() - intake_id = Intake.objects.filter( - workspace__slug=slug, project_id=project_id - ).first() + intake_id = Intake.objects.filter(workspace__slug=slug, project_id=project_id).first() # create an intake issue intake_issue = IntakeIssue.objects.create( intake_id=intake_id.id, @@ -311,8 +277,7 @@ class IntakeIssueViewSet(BaseViewSet): "issue__labels__id", distinct=True, filter=Q( - ~Q(issue__labels__id__isnull=True) - & Q(issue__label_issue__deleted_at__isnull=True) + ~Q(issue__labels__id__isnull=True) & Q(issue__label_issue__deleted_at__isnull=True) ), ), Value([], output_field=ArrayField(UUIDField())), @@ -340,9 +305,7 @@ class IntakeIssueViewSet(BaseViewSet): @allow_permission(allowed_roles=[ROLE.ADMIN], creator=True, model=Issue) def partial_update(self, request, slug, project_id, pk): - intake_id = Intake.objects.filter( - workspace__slug=slug, project_id=project_id - ).first() + intake_id = Intake.objects.filter(workspace__slug=slug, project_id=project_id).first() intake_issue = IntakeIssue.objects.get( issue_id=pk, workspace__slug=slug, @@ -371,10 +334,9 @@ class IntakeIssueViewSet(BaseViewSet): ) # Only project members admins and created_by users can access this endpoint - if ( - (project_member and project_member.role <= ROLE.GUEST.value) - and not is_workspace_admin - ) and str(intake_issue.created_by_id) != str(request.user.id): + if ((project_member and project_member.role <= ROLE.GUEST.value) and not is_workspace_admin) and str( + intake_issue.created_by_id + ) != str(request.user.id): return Response( {"error": "You cannot edit intake issues"}, status=status.HTTP_400_BAD_REQUEST, @@ -388,10 +350,7 @@ class IntakeIssueViewSet(BaseViewSet): ArrayAgg( "labels__id", distinct=True, - filter=Q( - ~Q(labels__id__isnull=True) - & Q(label_issue__deleted_at__isnull=True) - ), + filter=Q(~Q(labels__id__isnull=True) & Q(label_issue__deleted_at__isnull=True)), ), Value([], output_field=ArrayField(UUIDField())), ), @@ -399,10 +358,7 @@ class IntakeIssueViewSet(BaseViewSet): ArrayAgg( "assignees__id", distinct=True, - filter=Q( - ~Q(assignees__id__isnull=True) - & Q(issue_assignee__deleted_at__isnull=True) - ), + filter=Q(~Q(assignees__id__isnull=True) & Q(issue_assignee__deleted_at__isnull=True)), ), Value([], output_field=ArrayField(UUIDField())), ), @@ -411,15 +367,11 @@ class IntakeIssueViewSet(BaseViewSet): if project_member and project_member.role <= ROLE.GUEST.value: issue_data = { "name": issue_data.get("name", issue.name), - "description_html": issue_data.get( - "description_html", issue.description_html - ), + "description_html": issue_data.get("description_html", issue.description_html), "description": issue_data.get("description", issue.description), } - current_instance = json.dumps( - IssueDetailSerializer(issue).data, cls=DjangoJSONEncoder - ) + current_instance = json.dumps(IssueDetailSerializer(issue).data, cls=DjangoJSONEncoder) issue_serializer = IssueCreateSerializer( issue, data=issue_data, partial=True, context={"project_id": project_id} @@ -449,20 +401,12 @@ class IntakeIssueViewSet(BaseViewSet): ) issue_serializer.save() else: - return Response( - issue_serializer.errors, status=status.HTTP_400_BAD_REQUEST - ) + return Response(issue_serializer.errors, status=status.HTTP_400_BAD_REQUEST) # Only project admins can edit intake issue attributes - if ( - project_member and project_member.role > ROLE.MEMBER.value - ) or is_workspace_admin: - serializer = IntakeIssueSerializer( - intake_issue, data=request.data, partial=True - ) - current_instance = json.dumps( - IntakeIssueSerializer(intake_issue).data, cls=DjangoJSONEncoder - ) + if (project_member and project_member.role > ROLE.MEMBER.value) or is_workspace_admin: + serializer = IntakeIssueSerializer(intake_issue, data=request.data, partial=True) + current_instance = json.dumps(IntakeIssueSerializer(intake_issue).data, cls=DjangoJSONEncoder) if serializer.is_valid(): serializer.save() # Update the issue state if the issue is rejected or marked as duplicate @@ -472,9 +416,7 @@ class IntakeIssueViewSet(BaseViewSet): workspace__slug=slug, project_id=project_id, ) - state = State.objects.filter( - group="cancelled", workspace__slug=slug, project_id=project_id - ).first() + state = State.objects.filter(group="cancelled", workspace__slug=slug, project_id=project_id).first() if state is not None: issue.state = state issue.save() @@ -490,9 +432,7 @@ class IntakeIssueViewSet(BaseViewSet): # Update the issue state only if it is in triage state if issue.state.is_triage: # Move to default state - state = State.objects.filter( - workspace__slug=slug, project_id=project_id, default=True - ).first() + state = State.objects.filter(workspace__slug=slug, project_id=project_id, default=True).first() if state is not None: issue.state = state issue.save() @@ -519,8 +459,7 @@ class IntakeIssueViewSet(BaseViewSet): "issue__labels__id", distinct=True, filter=Q( - ~Q(issue__labels__id__isnull=True) - & Q(issue__label_issue__deleted_at__isnull=True) + ~Q(issue__labels__id__isnull=True) & Q(issue__label_issue__deleted_at__isnull=True) ), ), Value([], output_field=ArrayField(UUIDField())), @@ -546,13 +485,9 @@ class IntakeIssueViewSet(BaseViewSet): serializer = IntakeIssueDetailSerializer(intake_issue).data return Response(serializer, status=status.HTTP_200_OK) - @allow_permission( - allowed_roles=[ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST], creator=True, model=Issue - ) + @allow_permission(allowed_roles=[ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST], creator=True, model=Issue) def retrieve(self, request, slug, project_id, pk): - intake_id = Intake.objects.filter( - workspace__slug=slug, project_id=project_id - ).first() + intake_id = Intake.objects.filter(workspace__slug=slug, project_id=project_id).first() project = Project.objects.get(pk=project_id) intake_issue = ( IntakeIssue.objects.select_related("issue") @@ -562,10 +497,7 @@ class IntakeIssueViewSet(BaseViewSet): ArrayAgg( "issue__labels__id", distinct=True, - filter=Q( - ~Q(issue__labels__id__isnull=True) - & Q(issue__label_issue__deleted_at__isnull=True) - ), + filter=Q(~Q(issue__labels__id__isnull=True) & Q(issue__label_issue__deleted_at__isnull=True)), ), Value([], output_field=ArrayField(UUIDField())), ), @@ -574,8 +506,7 @@ class IntakeIssueViewSet(BaseViewSet): "issue__assignees__id", distinct=True, filter=Q( - ~Q(issue__assignees__id__isnull=True) - & Q(issue__issue_assignee__deleted_at__isnull=True) + ~Q(issue__assignees__id__isnull=True) & Q(issue__issue_assignee__deleted_at__isnull=True) ), ), Value([], output_field=ArrayField(UUIDField())), @@ -603,9 +534,7 @@ class IntakeIssueViewSet(BaseViewSet): @allow_permission(allowed_roles=[ROLE.ADMIN], creator=True, model=Issue) def destroy(self, request, slug, project_id, pk): - intake_id = Intake.objects.filter( - workspace__slug=slug, project_id=project_id - ).first() + intake_id = Intake.objects.filter(workspace__slug=slug, project_id=project_id).first() intake_issue = IntakeIssue.objects.get( issue_id=pk, workspace__slug=slug, @@ -616,9 +545,7 @@ class IntakeIssueViewSet(BaseViewSet): # Check the issue status if intake_issue.status in [-2, -1, 0, 2]: # Delete the issue also - issue = Issue.objects.filter( - workspace__slug=slug, project_id=project_id, pk=pk - ).first() + issue = Issue.objects.filter(workspace__slug=slug, project_id=project_id, pk=pk).first() issue.delete() intake_issue.delete() @@ -630,18 +557,14 @@ class IntakeWorkItemDescriptionVersionEndpoint(BaseAPIView): paginated_data = results.values(*fields) datetime_fields = ["created_at", "updated_at"] - paginated_data = user_timezone_converter( - paginated_data, datetime_fields, timezone - ) + paginated_data = user_timezone_converter(paginated_data, datetime_fields, timezone) return paginated_data @allow_permission(allowed_roles=[ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST]) def get(self, request, slug, project_id, work_item_id, pk=None): project = Project.objects.get(pk=project_id) - issue = Issue.objects.get( - workspace__slug=slug, project_id=project_id, pk=work_item_id - ) + issue = Issue.objects.get(workspace__slug=slug, project_id=project_id, pk=work_item_id) if ( ProjectMember.objects.filter( @@ -667,9 +590,7 @@ class IntakeWorkItemDescriptionVersionEndpoint(BaseAPIView): pk=pk, ) - serializer = IssueDescriptionVersionDetailSerializer( - issue_description_version - ) + serializer = IssueDescriptionVersionDetailSerializer(issue_description_version) return Response(serializer.data, status=status.HTTP_200_OK) cursor = request.GET.get("cursor", None) diff --git a/apps/api/plane/app/views/issue/activity.py b/apps/api/plane/app/views/issue/activity.py index b9ef58ffd..fdfcd129a 100644 --- a/apps/api/plane/app/views/issue/activity.py +++ b/apps/api/plane/app/views/issue/activity.py @@ -63,9 +63,7 @@ class IssueActivityEndpoint(BaseAPIView): issue_activities = issue_activities.prefetch_related( Prefetch( "issue__issue_intake", - queryset=IntakeIssue.objects.only( - "source_email", "source", "extra" - ), + queryset=IntakeIssue.objects.only("source_email", "source", "extra"), to_attr="source_data", ) ) diff --git a/apps/api/plane/app/views/issue/archive.py b/apps/api/plane/app/views/issue/archive.py index 9cd69d905..b8f858969 100644 --- a/apps/api/plane/app/views/issue/archive.py +++ b/apps/api/plane/app/views/issue/archive.py @@ -4,7 +4,7 @@ import json # Django imports from django.core.serializers.json import DjangoJSONEncoder -from django.db.models import F, Func, OuterRef, Q, Prefetch, Exists, Subquery, Count +from django.db.models import OuterRef, Q, Prefetch, Exists, Subquery, Count from django.utils import timezone from django.utils.decorators import method_decorator from django.views.decorators.gzip import gzip_page @@ -57,9 +57,7 @@ class IssueArchiveViewSet(BaseViewSet): return ( issues.annotate( cycle_id=Subquery( - CycleIssue.objects.filter( - issue=OuterRef("id"), deleted_at__isnull=True - ).values("cycle_id")[:1] + CycleIssue.objects.filter(issue=OuterRef("id"), deleted_at__isnull=True).values("cycle_id")[:1] ) ) .annotate( @@ -110,11 +108,7 @@ class IssueArchiveViewSet(BaseViewSet): issue_queryset = self.get_queryset() - issue_queryset = ( - issue_queryset - if show_sub_issues == "true" - else issue_queryset.filter(parent__isnull=True) - ) + issue_queryset = issue_queryset if show_sub_issues == "true" else issue_queryset.filter(parent__isnull=True) # Apply filtering from filterset issue_queryset = self.filter_queryset(issue_queryset) @@ -137,18 +131,14 @@ class IssueArchiveViewSet(BaseViewSet): sub_group_by = request.GET.get("sub_group_by", False) # issue queryset - issue_queryset = issue_queryset_grouper( - queryset=issue_queryset, group_by=group_by, sub_group_by=sub_group_by - ) + issue_queryset = issue_queryset_grouper(queryset=issue_queryset, group_by=group_by, sub_group_by=sub_group_by) if group_by: # Check group and sub group value paginate if sub_group_by: if group_by == sub_group_by: return Response( - { - "error": "Group by and sub group by cannot have same parameters" - }, + {"error": "Group by and sub group by cannot have same parameters"}, status=status.HTTP_400_BAD_REQUEST, ) else: @@ -220,9 +210,7 @@ class IssueArchiveViewSet(BaseViewSet): request=request, queryset=issue_queryset, total_count_queryset=total_issue_queryset, - on_results=lambda issues: issue_on_results( - group_by=group_by, issues=issues, sub_group_by=sub_group_by - ), + on_results=lambda issues: issue_on_results(group_by=group_by, issues=issues, sub_group_by=sub_group_by), ) @allow_permission([ROLE.ADMIN, ROLE.MEMBER]) @@ -263,9 +251,7 @@ class IssueArchiveViewSet(BaseViewSet): @allow_permission([ROLE.ADMIN, ROLE.MEMBER]) def archive(self, request, slug, project_id, pk=None): - issue = Issue.issue_objects.get( - workspace__slug=slug, project_id=project_id, pk=pk - ) + issue = Issue.issue_objects.get(workspace__slug=slug, project_id=project_id, pk=pk) if issue.state.group not in ["completed", "cancelled"]: return Response( {"error": "Can only archive completed or cancelled state group issue"}, @@ -273,15 +259,11 @@ class IssueArchiveViewSet(BaseViewSet): ) issue_activity.delay( type="issue.activity.updated", - requested_data=json.dumps( - {"archived_at": str(timezone.now().date()), "automation": False} - ), + requested_data=json.dumps({"archived_at": str(timezone.now().date()), "automation": False}), actor_id=str(request.user.id), issue_id=str(issue.id), project_id=str(project_id), - current_instance=json.dumps( - IssueSerializer(issue).data, cls=DjangoJSONEncoder - ), + current_instance=json.dumps(IssueSerializer(issue).data, cls=DjangoJSONEncoder), epoch=int(timezone.now().timestamp()), notification=True, origin=base_host(request=request, is_app=True), @@ -289,9 +271,7 @@ class IssueArchiveViewSet(BaseViewSet): issue.archived_at = timezone.now().date() issue.save() - return Response( - {"archived_at": str(issue.archived_at)}, status=status.HTTP_200_OK - ) + return Response({"archived_at": str(issue.archived_at)}, status=status.HTTP_200_OK) @allow_permission([ROLE.ADMIN, ROLE.MEMBER]) def unarchive(self, request, slug, project_id, pk=None): @@ -307,9 +287,7 @@ class IssueArchiveViewSet(BaseViewSet): actor_id=str(request.user.id), issue_id=str(issue.id), project_id=str(project_id), - current_instance=json.dumps( - IssueSerializer(issue).data, cls=DjangoJSONEncoder - ), + current_instance=json.dumps(IssueSerializer(issue).data, cls=DjangoJSONEncoder), epoch=int(timezone.now().timestamp()), notification=True, origin=base_host(request=request, is_app=True), @@ -328,13 +306,11 @@ class BulkArchiveIssuesEndpoint(BaseAPIView): issue_ids = request.data.get("issue_ids", []) if not len(issue_ids): - return Response( - {"error": "Issue IDs are required"}, status=status.HTTP_400_BAD_REQUEST - ) + return Response({"error": "Issue IDs are required"}, status=status.HTTP_400_BAD_REQUEST) - issues = Issue.objects.filter( - workspace__slug=slug, project_id=project_id, pk__in=issue_ids - ).select_related("state") + issues = Issue.objects.filter(workspace__slug=slug, project_id=project_id, pk__in=issue_ids).select_related( + "state" + ) bulk_archive_issues = [] for issue in issues: if issue.state.group not in ["completed", "cancelled"]: @@ -347,15 +323,11 @@ class BulkArchiveIssuesEndpoint(BaseAPIView): ) issue_activity.delay( type="issue.activity.updated", - requested_data=json.dumps( - {"archived_at": str(timezone.now().date()), "automation": False} - ), + requested_data=json.dumps({"archived_at": str(timezone.now().date()), "automation": False}), actor_id=str(request.user.id), issue_id=str(issue.id), project_id=str(project_id), - current_instance=json.dumps( - IssueSerializer(issue).data, cls=DjangoJSONEncoder - ), + current_instance=json.dumps(IssueSerializer(issue).data, cls=DjangoJSONEncoder), epoch=int(timezone.now().timestamp()), notification=True, origin=base_host(request=request, is_app=True), @@ -364,6 +336,4 @@ class BulkArchiveIssuesEndpoint(BaseAPIView): bulk_archive_issues.append(issue) Issue.objects.bulk_update(bulk_archive_issues, ["archived_at"]) - return Response( - {"archived_at": str(timezone.now().date())}, status=status.HTTP_200_OK - ) + return Response({"archived_at": str(timezone.now().date())}, status=status.HTTP_200_OK) diff --git a/apps/api/plane/app/views/issue/attachment.py b/apps/api/plane/app/views/issue/attachment.py index 423710e4a..7b7ecf378 100644 --- a/apps/api/plane/app/views/issue/attachment.py +++ b/apps/api/plane/app/views/issue/attachment.py @@ -75,9 +75,7 @@ class IssueAttachmentEndpoint(BaseAPIView): @allow_permission([ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST]) def get(self, request, slug, project_id, issue_id): - issue_attachments = FileAsset.objects.filter( - issue_id=issue_id, workspace__slug=slug, project_id=project_id - ) + issue_attachments = FileAsset.objects.filter(issue_id=issue_id, workspace__slug=slug, project_id=project_id) serializer = IssueAttachmentSerializer(issue_attachments, many=True) return Response(serializer.data, status=status.HTTP_200_OK) @@ -123,9 +121,7 @@ class IssueAttachmentV2Endpoint(BaseAPIView): 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( @@ -140,9 +136,7 @@ class IssueAttachmentV2Endpoint(BaseAPIView): @allow_permission([ROLE.ADMIN], creator=True, model=FileAsset) def delete(self, request, slug, project_id, issue_id, pk): - issue_attachment = FileAsset.objects.get( - pk=pk, workspace__slug=slug, project_id=project_id - ) + issue_attachment = FileAsset.objects.get(pk=pk, workspace__slug=slug, project_id=project_id) issue_attachment.is_deleted = True issue_attachment.deleted_at = timezone.now() issue_attachment.save() @@ -165,9 +159,7 @@ class IssueAttachmentV2Endpoint(BaseAPIView): def get(self, request, slug, project_id, issue_id, pk=None): if 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 if the asset is uploaded if not asset.is_uploaded: @@ -198,9 +190,7 @@ class IssueAttachmentV2Endpoint(BaseAPIView): @allow_permission([ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST]) def patch(self, request, slug, project_id, issue_id, pk): - issue_attachment = FileAsset.objects.get( - pk=pk, workspace__slug=slug, project_id=project_id - ) + issue_attachment = FileAsset.objects.get(pk=pk, workspace__slug=slug, project_id=project_id) serializer = IssueAttachmentSerializer(issue_attachment) # Send this activity only if the attachment is not uploaded before diff --git a/apps/api/plane/app/views/issue/base.py b/apps/api/plane/app/views/issue/base.py index 62ea3d295..c24db6169 100644 --- a/apps/api/plane/app/views/issue/base.py +++ b/apps/api/plane/app/views/issue/base.py @@ -82,16 +82,12 @@ class IssueListEndpoint(BaseAPIView): issue_ids = request.GET.get("issues", False) if not issue_ids: - return Response( - {"error": "Issues are required"}, status=status.HTTP_400_BAD_REQUEST - ) + return Response({"error": "Issues are required"}, status=status.HTTP_400_BAD_REQUEST) issue_ids = [issue_id for issue_id in issue_ids.split(",") if issue_id != ""] # Base queryset with basic filters - queryset = Issue.issue_objects.filter( - workspace__slug=slug, project_id=project_id, pk__in=issue_ids - ) + queryset = Issue.issue_objects.filter(workspace__slug=slug, project_id=project_id, pk__in=issue_ids) # Apply filtering from filterset queryset = self.filter_queryset(queryset) @@ -102,17 +98,15 @@ class IssueListEndpoint(BaseAPIView): # Add select_related, prefetch_related if fields or expand is not None if self.fields or self.expand: - issue_queryset = issue_queryset.select_related( - "workspace", "project", "state", "parent" - ).prefetch_related("assignees", "labels", "issue_module__module") + issue_queryset = issue_queryset.select_related("workspace", "project", "state", "parent").prefetch_related( + "assignees", "labels", "issue_module__module" + ) # Add annotations issue_queryset = ( issue_queryset.annotate( cycle_id=Subquery( - CycleIssue.objects.filter( - issue=OuterRef("id"), deleted_at__isnull=True - ).values("cycle_id")[:1] + CycleIssue.objects.filter(issue=OuterRef("id"), deleted_at__isnull=True).values("cycle_id")[:1] ) ) .annotate( @@ -141,18 +135,14 @@ class IssueListEndpoint(BaseAPIView): order_by_param = request.GET.get("order_by", "-created_at") # Issue queryset - issue_queryset, _ = order_issue_queryset( - issue_queryset=issue_queryset, order_by_param=order_by_param - ) + issue_queryset, _ = order_issue_queryset(issue_queryset=issue_queryset, order_by_param=order_by_param) # Group by group_by = request.GET.get("group_by", False) sub_group_by = request.GET.get("sub_group_by", False) # issue queryset - issue_queryset = issue_queryset_grouper( - queryset=issue_queryset, group_by=group_by, sub_group_by=sub_group_by - ) + issue_queryset = issue_queryset_grouper(queryset=issue_queryset, group_by=group_by, sub_group_by=sub_group_by) recent_visited_task.delay( slug=slug, @@ -163,9 +153,7 @@ class IssueListEndpoint(BaseAPIView): ) if self.fields or self.expand: - issues = IssueSerializer( - queryset, many=True, fields=self.fields, expand=self.expand - ).data + issues = IssueSerializer(queryset, many=True, fields=self.fields, expand=self.expand).data else: issues = issue_queryset.values( "id", @@ -196,9 +184,7 @@ class IssueListEndpoint(BaseAPIView): "deleted_at", ) datetime_fields = ["created_at", "updated_at"] - issues = user_timezone_converter( - issues, datetime_fields, request.user.user_timezone - ) + issues = user_timezone_converter(issues, datetime_fields, request.user.user_timezone) return Response(issues, status=status.HTTP_200_OK) @@ -210,11 +196,7 @@ class IssueViewSet(BaseViewSet): filterset_class = IssueFilterSet def get_serializer_class(self): - return ( - IssueCreateSerializer - if self.action in ["create", "update", "partial_update"] - else IssueSerializer - ) + return IssueCreateSerializer if self.action in ["create", "update", "partial_update"] else IssueSerializer def get_queryset(self): issues = Issue.issue_objects.filter( @@ -228,9 +210,7 @@ class IssueViewSet(BaseViewSet): issues = ( issues.annotate( cycle_id=Subquery( - CycleIssue.objects.filter( - issue=OuterRef("id"), deleted_at__isnull=True - ).values("cycle_id")[:1] + CycleIssue.objects.filter(issue=OuterRef("id"), deleted_at__isnull=True).values("cycle_id")[:1] ) ) .annotate( @@ -301,9 +281,7 @@ class IssueViewSet(BaseViewSet): sub_group_by = request.GET.get("sub_group_by", False) # issue queryset - issue_queryset = issue_queryset_grouper( - queryset=issue_queryset, group_by=group_by, sub_group_by=sub_group_by - ) + issue_queryset = issue_queryset_grouper(queryset=issue_queryset, group_by=group_by, sub_group_by=sub_group_by) recent_visited_task.delay( slug=slug, @@ -323,9 +301,7 @@ class IssueViewSet(BaseViewSet): and not project.guest_view_all_features ): issue_queryset = issue_queryset.filter(created_by=request.user) - filtered_issue_queryset = filtered_issue_queryset.filter( - created_by=request.user - ) + filtered_issue_queryset = filtered_issue_queryset.filter(created_by=request.user) if group_by: if sub_group_by: @@ -405,9 +381,7 @@ class IssueViewSet(BaseViewSet): request=request, queryset=issue_queryset, total_count_queryset=filtered_issue_queryset, - on_results=lambda issues: issue_on_results( - group_by=group_by, issues=issues, sub_group_by=sub_group_by - ), + on_results=lambda issues: issue_on_results(group_by=group_by, issues=issues, sub_group_by=sub_group_by), ) @allow_permission([ROLE.ADMIN, ROLE.MEMBER]) @@ -477,9 +451,7 @@ class IssueViewSet(BaseViewSet): .first() ) datetime_fields = ["created_at", "updated_at"] - issue = user_timezone_converter( - issue, datetime_fields, request.user.user_timezone - ) + issue = user_timezone_converter(issue, datetime_fields, request.user.user_timezone) # Send the model activity model_activity.delay( model_name="issue", @@ -500,9 +472,7 @@ class IssueViewSet(BaseViewSet): return Response(issue, status=status.HTTP_201_CREATED) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) - @allow_permission( - allowed_roles=[ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST], creator=True, model=Issue - ) + @allow_permission(allowed_roles=[ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST], creator=True, model=Issue) def retrieve(self, request, slug, project_id, pk=None): project = Project.objects.get(pk=project_id, workspace__slug=slug) @@ -513,13 +483,7 @@ class IssueViewSet(BaseViewSet): pk=pk, ) .select_related("state") - .annotate( - cycle_id=Subquery( - CycleIssue.objects.filter(issue=OuterRef("id")).values("cycle_id")[ - :1 - ] - ) - ) + .annotate(cycle_id=Subquery(CycleIssue.objects.filter(issue=OuterRef("id")).values("cycle_id")[:1])) .annotate( link_count=Subquery( IssueLink.objects.filter(issue=OuterRef("id")) @@ -643,9 +607,7 @@ class IssueViewSet(BaseViewSet): serializer = IssueDetailSerializer(issue, expand=self.expand) return Response(serializer.data, status=status.HTTP_200_OK) - @allow_permission( - allowed_roles=[ROLE.ADMIN, ROLE.MEMBER], creator=True, model=Issue - ) + @allow_permission(allowed_roles=[ROLE.ADMIN, ROLE.MEMBER], creator=True, model=Issue) def partial_update(self, request, slug, project_id, pk=None): queryset = self.get_queryset() queryset = self.apply_annotations(queryset) @@ -655,10 +617,7 @@ class IssueViewSet(BaseViewSet): ArrayAgg( "labels__id", distinct=True, - filter=Q( - ~Q(labels__id__isnull=True) - & Q(label_issue__deleted_at__isnull=True) - ), + filter=Q(~Q(labels__id__isnull=True) & Q(label_issue__deleted_at__isnull=True)), ), Value([], output_field=ArrayField(UUIDField())), ), @@ -692,18 +651,12 @@ class IssueViewSet(BaseViewSet): ) if not issue: - return Response( - {"error": "Issue not found"}, status=status.HTTP_404_NOT_FOUND - ) + return Response({"error": "Issue not found"}, status=status.HTTP_404_NOT_FOUND) - current_instance = json.dumps( - IssueDetailSerializer(issue).data, cls=DjangoJSONEncoder - ) + current_instance = json.dumps(IssueDetailSerializer(issue).data, cls=DjangoJSONEncoder) requested_data = json.dumps(self.request.data, cls=DjangoJSONEncoder) - serializer = IssueCreateSerializer( - issue, data=request.data, partial=True, context={"project_id": project_id} - ) + serializer = IssueCreateSerializer(issue, data=request.data, partial=True, context={"project_id": project_id}) if serializer.is_valid(): serializer.save() issue_activity.delay( @@ -765,29 +718,19 @@ class IssueViewSet(BaseViewSet): class IssueUserDisplayPropertyEndpoint(BaseAPIView): @allow_permission([ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST]) def patch(self, request, slug, project_id): - issue_property = IssueUserProperty.objects.get( - user=request.user, project_id=project_id - ) + issue_property = IssueUserProperty.objects.get(user=request.user, project_id=project_id) - issue_property.rich_filters = request.data.get( - "rich_filters", issue_property.rich_filters - ) + issue_property.rich_filters = request.data.get("rich_filters", issue_property.rich_filters) issue_property.filters = request.data.get("filters", issue_property.filters) - issue_property.display_filters = request.data.get( - "display_filters", issue_property.display_filters - ) - issue_property.display_properties = request.data.get( - "display_properties", issue_property.display_properties - ) + issue_property.display_filters = request.data.get("display_filters", issue_property.display_filters) + issue_property.display_properties = request.data.get("display_properties", issue_property.display_properties) issue_property.save() serializer = IssueUserPropertySerializer(issue_property) return Response(serializer.data, status=status.HTTP_201_CREATED) @allow_permission([ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST]) def get(self, request, slug, project_id): - issue_property, _ = IssueUserProperty.objects.get_or_create( - user=request.user, project_id=project_id - ) + issue_property, _ = IssueUserProperty.objects.get_or_create(user=request.user, project_id=project_id) serializer = IssueUserPropertySerializer(issue_property) return Response(serializer.data, status=status.HTTP_200_OK) @@ -798,13 +741,9 @@ class BulkDeleteIssuesEndpoint(BaseAPIView): issue_ids = request.data.get("issue_ids", []) if not len(issue_ids): - return Response( - {"error": "Issue IDs are required"}, status=status.HTTP_400_BAD_REQUEST - ) + return Response({"error": "Issue IDs are required"}, status=status.HTTP_400_BAD_REQUEST) - issues = Issue.issue_objects.filter( - workspace__slug=slug, project_id=project_id, pk__in=issue_ids - ) + issues = Issue.issue_objects.filter(workspace__slug=slug, project_id=project_id, pk__in=issue_ids) total_issues = len(issues) @@ -844,19 +783,11 @@ class IssuePaginatedViewSet(BaseViewSet): workspace_slug = self.kwargs.get("slug") project_id = self.kwargs.get("project_id") - issue_queryset = Issue.issue_objects.filter( - workspace__slug=workspace_slug, project_id=project_id - ) + issue_queryset = Issue.issue_objects.filter(workspace__slug=workspace_slug, project_id=project_id) return ( issue_queryset.select_related("state") - .annotate( - cycle_id=Subquery( - CycleIssue.objects.filter(issue=OuterRef("id")).values("cycle_id")[ - :1 - ] - ) - ) + .annotate(cycle_id=Subquery(CycleIssue.objects.filter(issue=OuterRef("id")).values("cycle_id")[:1])) .annotate( link_count=Subquery( IssueLink.objects.filter(issue=OuterRef("id")) @@ -891,9 +822,7 @@ class IssuePaginatedViewSet(BaseViewSet): # converting the datetime fields in paginated data datetime_fields = ["created_at", "updated_at"] - paginated_data = user_timezone_converter( - paginated_data, datetime_fields, timezone - ) + paginated_data = user_timezone_converter(paginated_data, datetime_fields, timezone) return paginated_data @@ -937,9 +866,7 @@ class IssuePaginatedViewSet(BaseViewSet): required_fields.append("description_html") # querying issues - base_queryset = Issue.issue_objects.filter( - workspace__slug=slug, project_id=project_id - ) + base_queryset = Issue.issue_objects.filter(workspace__slug=slug, project_id=project_id) base_queryset = base_queryset.order_by("updated_at") queryset = self.get_queryset().order_by("updated_at") @@ -1018,9 +945,7 @@ class IssueDetailEndpoint(BaseAPIView): return ( issues.annotate( cycle_id=Subquery( - CycleIssue.objects.filter( - issue=OuterRef("id"), deleted_at__isnull=True - ).values("cycle_id")[:1] + CycleIssue.objects.filter(issue=OuterRef("id"), deleted_at__isnull=True).values("cycle_id")[:1] ) ) .annotate( @@ -1071,9 +996,7 @@ class IssueDetailEndpoint(BaseAPIView): # check for the project member role, if the role is 5 then check for the guest_view_all_features # if it is true then show all the issues else show only the issues created by the user permission_subquery = ( - Issue.issue_objects.filter( - workspace__slug=slug, project_id=project_id, id=OuterRef("id") - ) + Issue.issue_objects.filter(workspace__slug=slug, project_id=project_id, id=OuterRef("id")) .filter( Q( project__project_projectmember__member=self.request.user, @@ -1097,9 +1020,9 @@ class IssueDetailEndpoint(BaseAPIView): .values("id") ) # Main issue query - issue = Issue.issue_objects.filter( - workspace__slug=slug, project_id=project_id - ).filter(Exists(permission_subquery)) + issue = Issue.issue_objects.filter(workspace__slug=slug, project_id=project_id).filter( + Exists(permission_subquery) + ) # Add additional prefetch based on expand parameter if self.expand: @@ -1133,9 +1056,7 @@ class IssueDetailEndpoint(BaseAPIView): order_by_param = request.GET.get("order_by", "-created_at") # Issue queryset - issue, order_by_param = order_issue_queryset( - issue_queryset=issue, order_by_param=order_by_param - ) + issue, order_by_param = order_issue_queryset(issue_queryset=issue, order_by_param=order_by_param) return self.paginate( request=request, order_by=order_by_param, @@ -1188,9 +1109,7 @@ class IssueBulkUpdateDateEndpoint(BaseAPIView): start_date = update.get("start_date") target_date = update.get("target_date") - validate_dates = self.validate_dates( - issue.start_date, issue.target_date, start_date, target_date - ) + validate_dates = self.validate_dates(issue.start_date, issue.target_date, start_date, target_date) if not validate_dates: return Response( {"message": "Start date cannot exceed target date"}, @@ -1213,12 +1132,8 @@ class IssueBulkUpdateDateEndpoint(BaseAPIView): if target_date: issue_activity.delay( type="issue.activity.updated", - requested_data=json.dumps( - {"target_date": update.get("target_date")} - ), - current_instance=json.dumps( - {"target_date": str(issue.target_date)} - ), + requested_data=json.dumps({"target_date": update.get("target_date")}), + current_instance=json.dumps({"target_date": str(issue.target_date)}), issue_id=str(issue_id), actor_id=str(request.user.id), project_id=str(project_id), @@ -1230,9 +1145,7 @@ class IssueBulkUpdateDateEndpoint(BaseAPIView): # Bulk update issues Issue.objects.bulk_update(issues_to_update, ["start_date", "target_date"]) - return Response( - {"message": "Issues updated successfully"}, status=status.HTTP_200_OK - ) + return Response({"message": "Issues updated successfully"}, status=status.HTTP_200_OK) class IssueMetaEndpoint(BaseAPIView): @@ -1267,9 +1180,7 @@ class IssueDetailIdentifierEndpoint(BaseAPIView): ) # Fetch the project - project = Project.objects.get( - identifier__iexact=project_identifier, workspace__slug=slug - ) + project = Project.objects.get(identifier__iexact=project_identifier, workspace__slug=slug) # Check if the user is a member of the project if not ProjectMember.objects.filter( @@ -1289,13 +1200,7 @@ class IssueDetailIdentifierEndpoint(BaseAPIView): .filter(workspace__slug=slug) .select_related("workspace", "project", "state", "parent") .prefetch_related("assignees", "labels", "issue_module__module") - .annotate( - cycle_id=Subquery( - CycleIssue.objects.filter(issue=OuterRef("id")).values("cycle_id")[ - :1 - ] - ) - ) + .annotate(cycle_id=Subquery(CycleIssue.objects.filter(issue=OuterRef("id")).values("cycle_id")[:1])) .annotate( link_count=IssueLink.objects.filter(issue=OuterRef("id")) .order_by() @@ -1323,10 +1228,7 @@ class IssueDetailIdentifierEndpoint(BaseAPIView): ArrayAgg( "labels__id", distinct=True, - filter=Q( - ~Q(labels__id__isnull=True) - & Q(label_issue__deleted_at__isnull=True) - ), + filter=Q(~Q(labels__id__isnull=True) & Q(label_issue__deleted_at__isnull=True)), ), Value([], output_field=ArrayField(UUIDField())), ), diff --git a/apps/api/plane/app/views/issue/comment.py b/apps/api/plane/app/views/issue/comment.py index 3dbe55eb6..72a986fea 100644 --- a/apps/api/plane/app/views/issue/comment.py +++ b/apps/api/plane/app/views/issue/comment.py @@ -77,9 +77,7 @@ class IssueCommentViewSet(BaseViewSet): ) serializer = IssueCommentSerializer(data=request.data) if serializer.is_valid(): - serializer.save( - project_id=project_id, issue_id=issue_id, actor=request.user - ) + serializer.save(project_id=project_id, issue_id=issue_id, actor=request.user) issue_activity.delay( type="comment.activity.created", requested_data=json.dumps(serializer.data, cls=DjangoJSONEncoder), @@ -106,21 +104,12 @@ class IssueCommentViewSet(BaseViewSet): @allow_permission(allowed_roles=[ROLE.ADMIN], creator=True, model=IssueComment) def partial_update(self, request, slug, project_id, issue_id, pk): - issue_comment = IssueComment.objects.get( - workspace__slug=slug, project_id=project_id, issue_id=issue_id, pk=pk - ) + issue_comment = IssueComment.objects.get(workspace__slug=slug, project_id=project_id, issue_id=issue_id, pk=pk) requested_data = json.dumps(self.request.data, cls=DjangoJSONEncoder) - current_instance = json.dumps( - IssueCommentSerializer(issue_comment).data, cls=DjangoJSONEncoder - ) - serializer = IssueCommentSerializer( - issue_comment, data=request.data, partial=True - ) + current_instance = json.dumps(IssueCommentSerializer(issue_comment).data, cls=DjangoJSONEncoder) + serializer = IssueCommentSerializer(issue_comment, data=request.data, partial=True) if serializer.is_valid(): - if ( - "comment_html" in request.data - and request.data["comment_html"] != issue_comment.comment_html - ): + if "comment_html" in request.data and request.data["comment_html"] != issue_comment.comment_html: serializer.save(edited_at=timezone.now()) else: serializer.save() @@ -150,12 +139,8 @@ class IssueCommentViewSet(BaseViewSet): @allow_permission(allowed_roles=[ROLE.ADMIN], creator=True, model=IssueComment) def destroy(self, request, slug, project_id, issue_id, pk): - issue_comment = IssueComment.objects.get( - workspace__slug=slug, project_id=project_id, issue_id=issue_id, pk=pk - ) - current_instance = json.dumps( - IssueCommentSerializer(issue_comment).data, cls=DjangoJSONEncoder - ) + issue_comment = IssueComment.objects.get(workspace__slug=slug, project_id=project_id, issue_id=issue_id, pk=pk) + current_instance = json.dumps(IssueCommentSerializer(issue_comment).data, cls=DjangoJSONEncoder) issue_comment.delete() issue_activity.delay( type="comment.activity.deleted", diff --git a/apps/api/plane/app/views/issue/label.py b/apps/api/plane/app/views/issue/label.py index 79a8a7770..85e79c011 100644 --- a/apps/api/plane/app/views/issue/label.py +++ b/apps/api/plane/app/views/issue/label.py @@ -35,9 +35,7 @@ class LabelViewSet(BaseViewSet): .order_by("sort_order") ) - @invalidate_cache( - path="/api/workspaces/:slug/labels/", url_params=True, user=False, multiple=True - ) + @invalidate_cache(path="/api/workspaces/:slug/labels/", url_params=True, user=False, multiple=True) @allow_permission([ROLE.ADMIN]) def create(self, request, slug, project_id): try: @@ -58,9 +56,7 @@ class LabelViewSet(BaseViewSet): # Check if the label name is unique within the project if ( "name" in request.data - and Label.objects.filter( - project_id=kwargs["project_id"], name=request.data["name"] - ) + and Label.objects.filter(project_id=kwargs["project_id"], name=request.data["name"]) .exclude(pk=kwargs["pk"]) .exists() ): diff --git a/apps/api/plane/app/views/issue/link.py b/apps/api/plane/app/views/issue/link.py index 0a574dc19..ee209f9fa 100644 --- a/apps/api/plane/app/views/issue/link.py +++ b/apps/api/plane/app/views/issue/link.py @@ -45,9 +45,7 @@ class IssueLinkViewSet(BaseViewSet): serializer = IssueLinkSerializer(data=request.data) if serializer.is_valid(): serializer.save(project_id=project_id, issue_id=issue_id) - crawl_work_item_link_title.delay( - serializer.data.get("id"), serializer.data.get("url") - ) + crawl_work_item_link_title.delay(serializer.data.get("id"), serializer.data.get("url")) issue_activity.delay( type="link.activity.created", requested_data=json.dumps(serializer.data, cls=DjangoJSONEncoder), @@ -67,20 +65,14 @@ class IssueLinkViewSet(BaseViewSet): return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) def partial_update(self, request, slug, project_id, issue_id, pk): - issue_link = IssueLink.objects.get( - workspace__slug=slug, project_id=project_id, issue_id=issue_id, pk=pk - ) + issue_link = IssueLink.objects.get(workspace__slug=slug, project_id=project_id, issue_id=issue_id, pk=pk) requested_data = json.dumps(request.data, cls=DjangoJSONEncoder) - current_instance = json.dumps( - IssueLinkSerializer(issue_link).data, cls=DjangoJSONEncoder - ) + current_instance = json.dumps(IssueLinkSerializer(issue_link).data, cls=DjangoJSONEncoder) serializer = IssueLinkSerializer(issue_link, data=request.data, partial=True) if serializer.is_valid(): serializer.save() - crawl_work_item_link_title.delay( - serializer.data.get("id"), serializer.data.get("url") - ) + crawl_work_item_link_title.delay(serializer.data.get("id"), serializer.data.get("url")) issue_activity.delay( type="link.activity.updated", @@ -100,12 +92,8 @@ class IssueLinkViewSet(BaseViewSet): return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) def destroy(self, request, slug, project_id, issue_id, pk): - issue_link = IssueLink.objects.get( - workspace__slug=slug, project_id=project_id, issue_id=issue_id, pk=pk - ) - current_instance = json.dumps( - IssueLinkSerializer(issue_link).data, cls=DjangoJSONEncoder - ) + issue_link = IssueLink.objects.get(workspace__slug=slug, project_id=project_id, issue_id=issue_id, pk=pk) + current_instance = json.dumps(IssueLinkSerializer(issue_link).data, cls=DjangoJSONEncoder) issue_activity.delay( type="link.activity.deleted", requested_data=json.dumps({"link_id": str(pk)}), diff --git a/apps/api/plane/app/views/issue/reaction.py b/apps/api/plane/app/views/issue/reaction.py index 8700b6345..fe8a63b13 100644 --- a/apps/api/plane/app/views/issue/reaction.py +++ b/apps/api/plane/app/views/issue/reaction.py @@ -42,9 +42,7 @@ class IssueReactionViewSet(BaseViewSet): def create(self, request, slug, project_id, issue_id): serializer = IssueReactionSerializer(data=request.data) if serializer.is_valid(): - serializer.save( - issue_id=issue_id, project_id=project_id, actor=request.user - ) + serializer.save(issue_id=issue_id, project_id=project_id, actor=request.user) issue_activity.delay( type="issue_reaction.activity.created", requested_data=json.dumps(request.data, cls=DjangoJSONEncoder), @@ -74,9 +72,7 @@ class IssueReactionViewSet(BaseViewSet): actor_id=str(self.request.user.id), issue_id=str(self.kwargs.get("issue_id", None)), project_id=str(self.kwargs.get("project_id", None)), - current_instance=json.dumps( - {"reaction": str(reaction_code), "identifier": str(issue_reaction.id)} - ), + current_instance=json.dumps({"reaction": str(reaction_code), "identifier": str(issue_reaction.id)}), epoch=int(timezone.now().timestamp()), notification=True, origin=base_host(request=request, is_app=True), diff --git a/apps/api/plane/app/views/issue/relation.py b/apps/api/plane/app/views/issue/relation.py index 50d319a88..0dfd686d2 100644 --- a/apps/api/plane/app/views/issue/relation.py +++ b/apps/api/plane/app/views/issue/relation.py @@ -37,9 +37,7 @@ class IssueRelationViewSet(BaseViewSet): def list(self, request, slug, project_id, issue_id): issue_relations = ( - IssueRelation.objects.filter( - Q(issue_id=issue_id) | Q(related_issue=issue_id) - ) + IssueRelation.objects.filter(Q(issue_id=issue_id) | Q(related_issue=issue_id)) .filter(workspace__slug=self.kwargs.get("slug")) .select_related("project") .select_related("workspace") @@ -48,19 +46,19 @@ class IssueRelationViewSet(BaseViewSet): .distinct() ) # get all blocking issues - blocking_issues = issue_relations.filter( - relation_type="blocked_by", related_issue_id=issue_id - ).values_list("issue_id", flat=True) + blocking_issues = issue_relations.filter(relation_type="blocked_by", related_issue_id=issue_id).values_list( + "issue_id", flat=True + ) # get all blocked by issues - blocked_by_issues = issue_relations.filter( - relation_type="blocked_by", issue_id=issue_id - ).values_list("related_issue_id", flat=True) + blocked_by_issues = issue_relations.filter(relation_type="blocked_by", issue_id=issue_id).values_list( + "related_issue_id", flat=True + ) # get all duplicate issues - duplicate_issues = issue_relations.filter( - issue_id=issue_id, relation_type="duplicate" - ).values_list("related_issue_id", flat=True) + duplicate_issues = issue_relations.filter(issue_id=issue_id, relation_type="duplicate").values_list( + "related_issue_id", flat=True + ) # get all relates to issues duplicate_issues_related = issue_relations.filter( @@ -68,9 +66,9 @@ class IssueRelationViewSet(BaseViewSet): ).values_list("issue_id", flat=True) # get all relates to issues - relates_to_issues = issue_relations.filter( - issue_id=issue_id, relation_type="relates_to" - ).values_list("related_issue_id", flat=True) + relates_to_issues = issue_relations.filter(issue_id=issue_id, relation_type="relates_to").values_list( + "related_issue_id", flat=True + ) # get all relates to issues relates_to_issues_related = issue_relations.filter( @@ -83,9 +81,9 @@ class IssueRelationViewSet(BaseViewSet): ).values_list("issue_id", flat=True) # get all start_before issues - start_before_issues = issue_relations.filter( - relation_type="start_before", issue_id=issue_id - ).values_list("related_issue_id", flat=True) + start_before_issues = issue_relations.filter(relation_type="start_before", issue_id=issue_id).values_list( + "related_issue_id", flat=True + ) # get all finish after issues finish_after_issues = issue_relations.filter( @@ -93,9 +91,9 @@ class IssueRelationViewSet(BaseViewSet): ).values_list("issue_id", flat=True) # get all finish before issues - finish_before_issues = issue_relations.filter( - relation_type="finish_before", issue_id=issue_id - ).values_list("related_issue_id", flat=True) + finish_before_issues = issue_relations.filter(relation_type="finish_before", issue_id=issue_id).values_list( + "related_issue_id", flat=True + ) queryset = ( Issue.issue_objects.filter(workspace__slug=slug) @@ -103,9 +101,7 @@ class IssueRelationViewSet(BaseViewSet): .prefetch_related("assignees", "labels", "issue_module__module") .annotate( cycle_id=Subquery( - CycleIssue.objects.filter( - issue=OuterRef("id"), deleted_at__isnull=True - ).values("cycle_id")[:1] + CycleIssue.objects.filter(issue=OuterRef("id"), deleted_at__isnull=True).values("cycle_id")[:1] ) ) .annotate( @@ -134,10 +130,7 @@ class IssueRelationViewSet(BaseViewSet): ArrayAgg( "labels__id", distinct=True, - filter=Q( - ~Q(labels__id__isnull=True) - & (Q(label_issue__deleted_at__isnull=True)) - ), + filter=Q(~Q(labels__id__isnull=True) & (Q(label_issue__deleted_at__isnull=True))), ), Value([], output_field=ArrayField(UUIDField())), ), @@ -223,15 +216,9 @@ class IssueRelationViewSet(BaseViewSet): issue_relation = IssueRelation.objects.bulk_create( [ IssueRelation( - issue_id=( - issue - if relation_type in ["blocking", "start_after", "finish_after"] - else issue_id - ), + issue_id=(issue if relation_type in ["blocking", "start_after", "finish_after"] else issue_id), related_issue_id=( - issue_id - if relation_type in ["blocking", "start_after", "finish_after"] - else issue + issue_id if relation_type in ["blocking", "start_after", "finish_after"] else issue ), relation_type=(get_actual_relation(relation_type)), project_id=project_id, @@ -274,13 +261,10 @@ class IssueRelationViewSet(BaseViewSet): issue_relations = IssueRelation.objects.filter( workspace__slug=slug, ).filter( - Q(issue_id=related_issue, related_issue_id=issue_id) - | Q(issue_id=issue_id, related_issue_id=related_issue) + Q(issue_id=related_issue, related_issue_id=issue_id) | Q(issue_id=issue_id, related_issue_id=related_issue) ) issue_relations = issue_relations.first() - current_instance = json.dumps( - IssueRelationSerializer(issue_relations).data, cls=DjangoJSONEncoder - ) + current_instance = json.dumps(IssueRelationSerializer(issue_relations).data, cls=DjangoJSONEncoder) issue_relations.delete() issue_activity.delay( type="issue_relation.activity.deleted", diff --git a/apps/api/plane/app/views/issue/sub_issue.py b/apps/api/plane/app/views/issue/sub_issue.py index 0843a9a51..2fa244dcf 100644 --- a/apps/api/plane/app/views/issue/sub_issue.py +++ b/apps/api/plane/app/views/issue/sub_issue.py @@ -37,9 +37,7 @@ class SubIssuesEndpoint(BaseAPIView): .prefetch_related("assignees", "labels", "issue_module__module") .annotate( cycle_id=Subquery( - CycleIssue.objects.filter( - issue=OuterRef("id"), deleted_at__isnull=True - ).values("cycle_id")[:1] + CycleIssue.objects.filter(issue=OuterRef("id"), deleted_at__isnull=True).values("cycle_id")[:1] ) ) .annotate( @@ -68,10 +66,7 @@ class SubIssuesEndpoint(BaseAPIView): ArrayAgg( "labels__id", distinct=True, - filter=Q( - ~Q(labels__id__isnull=True) - & Q(label_issue__deleted_at__isnull=True) - ), + filter=Q(~Q(labels__id__isnull=True) & Q(label_issue__deleted_at__isnull=True)), ), Value([], output_field=ArrayField(UUIDField())), ), @@ -109,9 +104,7 @@ class SubIssuesEndpoint(BaseAPIView): group_by = request.GET.get("group_by", False) if order_by_param: - sub_issues, order_by_param = order_issue_queryset( - sub_issues, order_by_param - ) + sub_issues, order_by_param = order_issue_queryset(sub_issues, order_by_param) # create's a dict with state group name with their respective issue id's result = defaultdict(list) @@ -146,9 +139,7 @@ class SubIssuesEndpoint(BaseAPIView): "archived_at", ) datetime_fields = ["created_at", "updated_at"] - sub_issues = user_timezone_converter( - sub_issues, datetime_fields, request.user.user_timezone - ) + sub_issues = user_timezone_converter(sub_issues, datetime_fields, request.user.user_timezone) # Grouping if group_by: result_dict = defaultdict(list) @@ -192,9 +183,7 @@ class SubIssuesEndpoint(BaseAPIView): _ = Issue.objects.bulk_update(sub_issues, ["parent"], batch_size=10) - updated_sub_issues = Issue.issue_objects.filter(id__in=sub_issue_ids).annotate( - state_group=F("state__group") - ) + updated_sub_issues = Issue.issue_objects.filter(id__in=sub_issue_ids).annotate(state_group=F("state__group")) # Track the issue _ = [ diff --git a/apps/api/plane/app/views/issue/version.py b/apps/api/plane/app/views/issue/version.py index 9f8d5c29d..358271ac8 100644 --- a/apps/api/plane/app/views/issue/version.py +++ b/apps/api/plane/app/views/issue/version.py @@ -25,9 +25,7 @@ class IssueVersionEndpoint(BaseAPIView): paginated_data = results.values(*fields) datetime_fields = ["created_at", "updated_at"] - paginated_data = user_timezone_converter( - paginated_data, datetime_fields, timezone - ) + paginated_data = user_timezone_converter(paginated_data, datetime_fields, timezone) return paginated_data @@ -77,18 +75,14 @@ class WorkItemDescriptionVersionEndpoint(BaseAPIView): paginated_data = results.values(*fields) datetime_fields = ["created_at", "updated_at"] - paginated_data = user_timezone_converter( - paginated_data, datetime_fields, timezone - ) + paginated_data = user_timezone_converter(paginated_data, datetime_fields, timezone) return paginated_data @allow_permission(allowed_roles=[ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST]) def get(self, request, slug, project_id, work_item_id, pk=None): project = Project.objects.get(pk=project_id) - issue = Issue.objects.get( - workspace__slug=slug, project_id=project_id, pk=work_item_id - ) + issue = Issue.objects.get(workspace__slug=slug, project_id=project_id, pk=work_item_id) if ( ProjectMember.objects.filter( @@ -114,9 +108,7 @@ class WorkItemDescriptionVersionEndpoint(BaseAPIView): pk=pk, ) - serializer = IssueDescriptionVersionDetailSerializer( - issue_description_version - ) + serializer = IssueDescriptionVersionDetailSerializer(issue_description_version) return Response(serializer.data, status=status.HTTP_200_OK) cursor = request.GET.get("cursor", None) diff --git a/apps/api/plane/app/views/module/archive.py b/apps/api/plane/app/views/module/archive.py index d5c632f96..129ff0dac 100644 --- a/apps/api/plane/app/views/module/archive.py +++ b/apps/api/plane/app/views/module/archive.py @@ -113,11 +113,7 @@ class ModuleArchiveUnarchiveEndpoint(BaseAPIView): issue_module__deleted_at__isnull=True, ) .values("issue_module__module_id") - .annotate( - completed_estimate_points=Sum( - Cast("estimate_point__value", FloatField()) - ) - ) + .annotate(completed_estimate_points=Sum(Cast("estimate_point__value", FloatField()))) .values("completed_estimate_points")[:1] ) @@ -128,9 +124,7 @@ class ModuleArchiveUnarchiveEndpoint(BaseAPIView): issue_module__deleted_at__isnull=True, ) .values("issue_module__module_id") - .annotate( - total_estimate_points=Sum(Cast("estimate_point__value", FloatField())) - ) + .annotate(total_estimate_points=Sum(Cast("estimate_point__value", FloatField()))) .values("total_estimate_points")[:1] ) backlog_estimate_point = ( @@ -141,9 +135,7 @@ class ModuleArchiveUnarchiveEndpoint(BaseAPIView): issue_module__deleted_at__isnull=True, ) .values("issue_module__module_id") - .annotate( - backlog_estimate_point=Sum(Cast("estimate_point__value", FloatField())) - ) + .annotate(backlog_estimate_point=Sum(Cast("estimate_point__value", FloatField()))) .values("backlog_estimate_point")[:1] ) unstarted_estimate_point = ( @@ -154,11 +146,7 @@ class ModuleArchiveUnarchiveEndpoint(BaseAPIView): issue_module__deleted_at__isnull=True, ) .values("issue_module__module_id") - .annotate( - unstarted_estimate_point=Sum( - Cast("estimate_point__value", FloatField()) - ) - ) + .annotate(unstarted_estimate_point=Sum(Cast("estimate_point__value", FloatField()))) .values("unstarted_estimate_point")[:1] ) started_estimate_point = ( @@ -169,9 +157,7 @@ class ModuleArchiveUnarchiveEndpoint(BaseAPIView): issue_module__deleted_at__isnull=True, ) .values("issue_module__module_id") - .annotate( - started_estimate_point=Sum(Cast("estimate_point__value", FloatField())) - ) + .annotate(started_estimate_point=Sum(Cast("estimate_point__value", FloatField()))) .values("started_estimate_point")[:1] ) cancelled_estimate_point = ( @@ -182,11 +168,7 @@ class ModuleArchiveUnarchiveEndpoint(BaseAPIView): issue_module__deleted_at__isnull=True, ) .values("issue_module__module_id") - .annotate( - cancelled_estimate_point=Sum( - Cast("estimate_point__value", FloatField()) - ) - ) + .annotate(cancelled_estimate_point=Sum(Cast("estimate_point__value", FloatField()))) .values("cancelled_estimate_point")[:1] ) return ( @@ -214,27 +196,15 @@ class ModuleArchiveUnarchiveEndpoint(BaseAPIView): Value(0, output_field=IntegerField()), ) ) - .annotate( - started_issues=Coalesce( - Subquery(started_issues[:1]), Value(0, output_field=IntegerField()) - ) - ) + .annotate(started_issues=Coalesce(Subquery(started_issues[:1]), Value(0, output_field=IntegerField()))) .annotate( unstarted_issues=Coalesce( Subquery(unstarted_issues[:1]), Value(0, output_field=IntegerField()), ) ) - .annotate( - backlog_issues=Coalesce( - Subquery(backlog_issues[:1]), Value(0, output_field=IntegerField()) - ) - ) - .annotate( - total_issues=Coalesce( - Subquery(total_issues[:1]), Value(0, output_field=IntegerField()) - ) - ) + .annotate(backlog_issues=Coalesce(Subquery(backlog_issues[:1]), Value(0, output_field=IntegerField()))) + .annotate(total_issues=Coalesce(Subquery(total_issues[:1]), Value(0, output_field=IntegerField()))) .annotate( backlog_estimate_points=Coalesce( Subquery(backlog_estimate_point), @@ -266,9 +236,7 @@ class ModuleArchiveUnarchiveEndpoint(BaseAPIView): ) ) .annotate( - total_estimate_points=Coalesce( - Subquery(total_estimate_point), Value(0, output_field=FloatField()) - ) + total_estimate_points=Coalesce(Subquery(total_estimate_point), Value(0, output_field=FloatField())) ) .annotate( member_ids=Coalesce( @@ -317,9 +285,7 @@ class ModuleArchiveUnarchiveEndpoint(BaseAPIView): "archived_at", ) datetime_fields = ["created_at", "updated_at"] - modules = user_timezone_converter( - modules, datetime_fields, request.user.user_timezone - ) + modules = user_timezone_converter(modules, datetime_fields, request.user.user_timezone) return Response(modules, status=status.HTTP_200_OK) else: queryset = ( @@ -389,9 +355,7 @@ class ModuleArchiveUnarchiveEndpoint(BaseAPIView): "avatar_url", "display_name", ) - .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()), @@ -425,9 +389,7 @@ class ModuleArchiveUnarchiveEndpoint(BaseAPIView): .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()), @@ -500,11 +462,7 @@ class ModuleArchiveUnarchiveEndpoint(BaseAPIView): "avatar_url", "display_name", ) - .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", @@ -539,11 +497,7 @@ class ModuleArchiveUnarchiveEndpoint(BaseAPIView): .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", @@ -584,9 +538,7 @@ class ModuleArchiveUnarchiveEndpoint(BaseAPIView): return Response(data, status=status.HTTP_200_OK) def post(self, request, slug, project_id, module_id): - module = Module.objects.get( - pk=module_id, project_id=project_id, workspace__slug=slug - ) + module = Module.objects.get(pk=module_id, project_id=project_id, workspace__slug=slug) if module.status not in ["completed", "cancelled"]: return Response( {"error": "Only completed or cancelled modules can be archived"}, @@ -600,14 +552,10 @@ class ModuleArchiveUnarchiveEndpoint(BaseAPIView): project_id=project_id, workspace__slug=slug, ).delete() - return Response( - {"archived_at": str(module.archived_at)}, status=status.HTTP_200_OK - ) + return Response({"archived_at": str(module.archived_at)}, status=status.HTTP_200_OK) def delete(self, request, slug, project_id, module_id): - module = Module.objects.get( - pk=module_id, project_id=project_id, workspace__slug=slug - ) + module = Module.objects.get(pk=module_id, project_id=project_id, workspace__slug=slug) module.archived_at = None module.save() return Response(status=status.HTTP_204_NO_CONTENT) diff --git a/apps/api/plane/app/views/module/base.py b/apps/api/plane/app/views/module/base.py index 522a7eaa0..ae429e750 100644 --- a/apps/api/plane/app/views/module/base.py +++ b/apps/api/plane/app/views/module/base.py @@ -69,11 +69,7 @@ class ModuleViewSet(BaseViewSet): webhook_event = "module" def get_serializer_class(self): - return ( - ModuleWriteSerializer - if self.action in ["create", "update", "partial_update"] - else ModuleSerializer - ) + return ModuleWriteSerializer if self.action in ["create", "update", "partial_update"] else ModuleSerializer def get_queryset(self): favorite_subquery = UserFavorite.objects.filter( @@ -150,11 +146,7 @@ class ModuleViewSet(BaseViewSet): issue_module__deleted_at__isnull=True, ) .values("issue_module__module_id") - .annotate( - completed_estimate_points=Sum( - Cast("estimate_point__value", FloatField()) - ) - ) + .annotate(completed_estimate_points=Sum(Cast("estimate_point__value", FloatField()))) .values("completed_estimate_points")[:1] ) @@ -165,9 +157,7 @@ class ModuleViewSet(BaseViewSet): issue_module__deleted_at__isnull=True, ) .values("issue_module__module_id") - .annotate( - total_estimate_points=Sum(Cast("estimate_point__value", FloatField())) - ) + .annotate(total_estimate_points=Sum(Cast("estimate_point__value", FloatField()))) .values("total_estimate_points")[:1] ) backlog_estimate_point = ( @@ -178,9 +168,7 @@ class ModuleViewSet(BaseViewSet): issue_module__deleted_at__isnull=True, ) .values("issue_module__module_id") - .annotate( - backlog_estimate_point=Sum(Cast("estimate_point__value", FloatField())) - ) + .annotate(backlog_estimate_point=Sum(Cast("estimate_point__value", FloatField()))) .values("backlog_estimate_point")[:1] ) unstarted_estimate_point = ( @@ -191,11 +179,7 @@ class ModuleViewSet(BaseViewSet): issue_module__deleted_at__isnull=True, ) .values("issue_module__module_id") - .annotate( - unstarted_estimate_point=Sum( - Cast("estimate_point__value", FloatField()) - ) - ) + .annotate(unstarted_estimate_point=Sum(Cast("estimate_point__value", FloatField()))) .values("unstarted_estimate_point")[:1] ) started_estimate_point = ( @@ -206,9 +190,7 @@ class ModuleViewSet(BaseViewSet): issue_module__deleted_at__isnull=True, ) .values("issue_module__module_id") - .annotate( - started_estimate_point=Sum(Cast("estimate_point__value", FloatField())) - ) + .annotate(started_estimate_point=Sum(Cast("estimate_point__value", FloatField()))) .values("started_estimate_point")[:1] ) cancelled_estimate_point = ( @@ -219,11 +201,7 @@ class ModuleViewSet(BaseViewSet): issue_module__deleted_at__isnull=True, ) .values("issue_module__module_id") - .annotate( - cancelled_estimate_point=Sum( - Cast("estimate_point__value", FloatField()) - ) - ) + .annotate(cancelled_estimate_point=Sum(Cast("estimate_point__value", FloatField()))) .values("cancelled_estimate_point")[:1] ) return ( @@ -251,27 +229,15 @@ class ModuleViewSet(BaseViewSet): Value(0, output_field=IntegerField()), ) ) - .annotate( - started_issues=Coalesce( - Subquery(started_issues[:1]), Value(0, output_field=IntegerField()) - ) - ) + .annotate(started_issues=Coalesce(Subquery(started_issues[:1]), Value(0, output_field=IntegerField()))) .annotate( unstarted_issues=Coalesce( Subquery(unstarted_issues[:1]), Value(0, output_field=IntegerField()), ) ) - .annotate( - backlog_issues=Coalesce( - Subquery(backlog_issues[:1]), Value(0, output_field=IntegerField()) - ) - ) - .annotate( - total_issues=Coalesce( - Subquery(total_issues[:1]), Value(0, output_field=IntegerField()) - ) - ) + .annotate(backlog_issues=Coalesce(Subquery(backlog_issues[:1]), Value(0, output_field=IntegerField()))) + .annotate(total_issues=Coalesce(Subquery(total_issues[:1]), Value(0, output_field=IntegerField()))) .annotate( backlog_estimate_points=Coalesce( Subquery(backlog_estimate_point), @@ -303,9 +269,7 @@ class ModuleViewSet(BaseViewSet): ) ) .annotate( - total_estimate_points=Coalesce( - Subquery(total_estimate_point), Value(0, output_field=FloatField()) - ) + total_estimate_points=Coalesce(Subquery(total_estimate_point), Value(0, output_field=FloatField())) ) .annotate( member_ids=Coalesce( @@ -326,9 +290,7 @@ class ModuleViewSet(BaseViewSet): @allow_permission([ROLE.ADMIN, ROLE.MEMBER]) def create(self, request, slug, project_id): project = Project.objects.get(workspace__slug=slug, pk=project_id) - serializer = ModuleWriteSerializer( - data=request.data, context={"project": project} - ) + serializer = ModuleWriteSerializer(data=request.data, context={"project": project}) if serializer.is_valid(): serializer.save() @@ -380,9 +342,7 @@ class ModuleViewSet(BaseViewSet): origin=base_host(request=request, is_app=True), ) datetime_fields = ["created_at", "updated_at"] - module = user_timezone_converter( - module, datetime_fields, request.user.user_timezone - ) + module = user_timezone_converter(module, datetime_fields, request.user.user_timezone) return Response(module, status=status.HTTP_201_CREATED) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) @@ -425,9 +385,7 @@ class ModuleViewSet(BaseViewSet): "updated_at", ) datetime_fields = ["created_at", "updated_at"] - modules = user_timezone_converter( - modules, datetime_fields, request.user.user_timezone - ) + modules = user_timezone_converter(modules, datetime_fields, request.user.user_timezone) return Response(modules, status=status.HTTP_200_OK) @allow_permission([ROLE.ADMIN, ROLE.MEMBER]) @@ -450,9 +408,7 @@ class ModuleViewSet(BaseViewSet): ) if not queryset.exists(): - return Response( - {"error": "Module not found"}, status=status.HTTP_404_NOT_FOUND - ) + return Response({"error": "Module not found"}, status=status.HTTP_404_NOT_FOUND) estimate_type = Project.objects.filter( workspace__slug=slug, @@ -505,9 +461,7 @@ class ModuleViewSet(BaseViewSet): "avatar_url", "display_name", ) - .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()), @@ -542,9 +496,7 @@ class ModuleViewSet(BaseViewSet): .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()), @@ -602,21 +554,13 @@ class ModuleViewSet(BaseViewSet): ), ), # If `avatar_asset` is None, fall back to using `avatar` field directly - When( - assignees__avatar_asset__isnull=True, then="assignees__avatar" - ), + When(assignees__avatar_asset__isnull=True, then="assignees__avatar"), default=Value(None), output_field=models.CharField(), ) ) - .values( - "first_name", "last_name", "assignee_id", "avatar_url", "display_name" - ) - .annotate( - total_issues=Count( - "id", filter=Q(archived_at__isnull=True, is_draft=False) - ) - ) + .values("first_name", "last_name", "assignee_id", "avatar_url", "display_name") + .annotate(total_issues=Count("id", filter=Q(archived_at__isnull=True, is_draft=False))) .annotate( completed_issues=Count( "id", @@ -651,11 +595,7 @@ class ModuleViewSet(BaseViewSet): .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", @@ -685,12 +625,7 @@ class ModuleViewSet(BaseViewSet): "completion_chart": {}, } - if ( - modules - and modules.start_date - and modules.target_date - and modules.total_issues > 0 - ): + if modules and modules.start_date and modules.target_date and modules.total_issues > 0: data["distribution"]["completion_chart"] = burndown_plot( queryset=modules, slug=slug, @@ -726,12 +661,8 @@ class ModuleViewSet(BaseViewSet): {"error": "Archived module cannot be updated"}, status=status.HTTP_400_BAD_REQUEST, ) - current_instance = json.dumps( - ModuleSerializer(current_module).data, cls=DjangoJSONEncoder - ) - serializer = ModuleWriteSerializer( - current_module, data=request.data, partial=True - ) + current_instance = json.dumps(ModuleSerializer(current_module).data, cls=DjangoJSONEncoder) + serializer = ModuleWriteSerializer(current_module, data=request.data, partial=True) if serializer.is_valid(): serializer.save() @@ -781,9 +712,7 @@ class ModuleViewSet(BaseViewSet): ) datetime_fields = ["created_at", "updated_at"] - module = user_timezone_converter( - module, datetime_fields, request.user.user_timezone - ) + module = user_timezone_converter(module, datetime_fields, request.user.user_timezone) return Response(module, status=status.HTTP_200_OK) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) @@ -791,9 +720,7 @@ class ModuleViewSet(BaseViewSet): def destroy(self, request, slug, project_id, pk): module = Module.objects.get(workspace__slug=slug, project_id=project_id, pk=pk) - module_issues = list( - ModuleIssue.objects.filter(module_id=pk).values_list("issue", flat=True) - ) + module_issues = list(ModuleIssue.objects.filter(module_id=pk).values_list("issue", flat=True)) _ = [ issue_activity.delay( type="module.activity.deleted", @@ -901,15 +828,9 @@ class ModuleUserPropertiesEndpoint(BaseAPIView): workspace__slug=slug, ) - module_properties.filters = request.data.get( - "filters", module_properties.filters - ) - module_properties.rich_filters = request.data.get( - "rich_filters", module_properties.rich_filters - ) - module_properties.display_filters = request.data.get( - "display_filters", module_properties.display_filters - ) + module_properties.filters = request.data.get("filters", module_properties.filters) + module_properties.rich_filters = request.data.get("rich_filters", module_properties.rich_filters) + module_properties.display_filters = request.data.get("display_filters", module_properties.display_filters) module_properties.display_properties = request.data.get( "display_properties", module_properties.display_properties ) diff --git a/apps/api/plane/app/views/module/issue.py b/apps/api/plane/app/views/module/issue.py index e18d61a4f..672bf4e1a 100644 --- a/apps/api/plane/app/views/module/issue.py +++ b/apps/api/plane/app/views/module/issue.py @@ -50,9 +50,7 @@ class ModuleIssueViewSet(BaseViewSet): return ( issues.annotate( cycle_id=Subquery( - CycleIssue.objects.filter( - issue=OuterRef("id"), deleted_at__isnull=True - ).values("cycle_id")[:1] + CycleIssue.objects.filter(issue=OuterRef("id"), deleted_at__isnull=True).values("cycle_id")[:1] ) ) .annotate( @@ -119,18 +117,14 @@ class ModuleIssueViewSet(BaseViewSet): sub_group_by = request.GET.get("sub_group_by", False) # issue queryset - issue_queryset = issue_queryset_grouper( - queryset=issue_queryset, group_by=group_by, sub_group_by=sub_group_by - ) + issue_queryset = issue_queryset_grouper(queryset=issue_queryset, group_by=group_by, sub_group_by=sub_group_by) if group_by: # Check group and sub group value paginate if sub_group_by: if group_by == sub_group_by: return Response( - { - "error": "Group by and sub group by cannot have same parameters" - }, + {"error": "Group by and sub group by cannot have same parameters"}, status=status.HTTP_400_BAD_REQUEST, ) else: @@ -205,9 +199,7 @@ class ModuleIssueViewSet(BaseViewSet): request=request, queryset=issue_queryset, total_count_queryset=total_issue_queryset, - on_results=lambda issues: issue_on_results( - group_by=group_by, issues=issues, sub_group_by=sub_group_by - ), + on_results=lambda issues: issue_on_results(group_by=group_by, issues=issues, sub_group_by=sub_group_by), ) @allow_permission([ROLE.ADMIN, ROLE.MEMBER]) @@ -215,9 +207,7 @@ class ModuleIssueViewSet(BaseViewSet): def create_module_issues(self, request, slug, project_id, module_id): issues = request.data.get("issues", []) if not issues: - return Response( - {"error": "Issues are required"}, status=status.HTTP_400_BAD_REQUEST - ) + return Response({"error": "Issues are required"}, status=status.HTTP_400_BAD_REQUEST) project = Project.objects.get(pk=project_id) _ = ModuleIssue.objects.bulk_create( [ @@ -334,9 +324,7 @@ class ModuleIssueViewSet(BaseViewSet): actor_id=str(request.user.id), issue_id=str(issue_id), project_id=str(project_id), - current_instance=json.dumps( - {"module_name": module_issue.first().module.name} - ), + current_instance=json.dumps({"module_name": module_issue.first().module.name}), epoch=int(timezone.now().timestamp()), notification=True, origin=base_host(request=request, is_app=True), diff --git a/apps/api/plane/app/views/notification/base.py b/apps/api/plane/app/views/notification/base.py index 329599c15..a11c12d16 100644 --- a/apps/api/plane/app/views/notification/base.py +++ b/apps/api/plane/app/views/notification/base.py @@ -40,9 +40,7 @@ class NotificationViewSet(BaseViewSet, BasePaginator): .select_related("workspace", "project", "triggered_by", "receiver") ) - @allow_permission( - allowed_roles=[ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST], level="WORKSPACE" - ) + @allow_permission(allowed_roles=[ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST], level="WORKSPACE") def list(self, request, slug): # Get query parameters snoozed = request.GET.get("snoozed", "false") @@ -59,9 +57,7 @@ class NotificationViewSet(BaseViewSet, BasePaginator): ) notifications = ( - Notification.objects.filter( - workspace__slug=slug, receiver_id=request.user.id - ) + Notification.objects.filter(workspace__slug=slug, receiver_id=request.user.id) .filter(entity_name="issue") .annotate(is_inbox_issue=Exists(intake_issue)) .annotate(is_intake_issue=Exists(intake_issue)) @@ -106,23 +102,9 @@ class NotificationViewSet(BaseViewSet, BasePaginator): # Subscribed issues if "subscribed" in type: issue_ids = ( - IssueSubscriber.objects.filter( - workspace__slug=slug, subscriber_id=request.user.id - ) - .annotate( - created=Exists( - Issue.objects.filter( - created_by=request.user, pk=OuterRef("issue_id") - ) - ) - ) - .annotate( - assigned=Exists( - IssueAssignee.objects.filter( - pk=OuterRef("issue_id"), assignee=request.user - ) - ) - ) + IssueSubscriber.objects.filter(workspace__slug=slug, subscriber_id=request.user.id) + .annotate(created=Exists(Issue.objects.filter(created_by=request.user, pk=OuterRef("issue_id")))) + .annotate(assigned=Exists(IssueAssignee.objects.filter(pk=OuterRef("issue_id"), assignee=request.user))) .filter(created=False, assigned=False) .values_list("issue_id", flat=True) ) @@ -130,9 +112,9 @@ class NotificationViewSet(BaseViewSet, BasePaginator): # Assigned Issues if "assigned" in type: - issue_ids = IssueAssignee.objects.filter( - workspace__slug=slug, assignee_id=request.user.id - ).values_list("issue_id", flat=True) + issue_ids = IssueAssignee.objects.filter(workspace__slug=slug, assignee_id=request.user.id).values_list( + "issue_id", flat=True + ) q_filters |= Q(entity_identifier__in=issue_ids) # Created issues @@ -142,9 +124,9 @@ class NotificationViewSet(BaseViewSet, BasePaginator): ).exists(): notifications = notifications.none() else: - issue_ids = Issue.objects.filter( - workspace__slug=slug, created_by=request.user - ).values_list("pk", flat=True) + issue_ids = Issue.objects.filter(workspace__slug=slug, created_by=request.user).values_list( + "pk", flat=True + ) q_filters |= Q(entity_identifier__in=issue_ids) # Apply the combined Q object filters @@ -156,75 +138,51 @@ class NotificationViewSet(BaseViewSet, BasePaginator): order_by=request.GET.get("order_by", "-created_at"), request=request, queryset=(notifications), - on_results=lambda notifications: NotificationSerializer( - notifications, many=True - ).data, + on_results=lambda notifications: NotificationSerializer(notifications, many=True).data, ) serializer = NotificationSerializer(notifications, many=True) return Response(serializer.data, status=status.HTTP_200_OK) - @allow_permission( - allowed_roles=[ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST], level="WORKSPACE" - ) + @allow_permission(allowed_roles=[ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST], level="WORKSPACE") def partial_update(self, request, slug, pk): - notification = Notification.objects.get( - workspace__slug=slug, pk=pk, receiver=request.user - ) + notification = Notification.objects.get(workspace__slug=slug, pk=pk, receiver=request.user) # Only read_at and snoozed_till can be updated notification_data = {"snoozed_till": request.data.get("snoozed_till", None)} - serializer = NotificationSerializer( - notification, data=notification_data, partial=True - ) + serializer = NotificationSerializer(notification, data=notification_data, partial=True) if serializer.is_valid(): serializer.save() return Response(serializer.data, status=status.HTTP_200_OK) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) - @allow_permission( - allowed_roles=[ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST], level="WORKSPACE" - ) + @allow_permission(allowed_roles=[ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST], level="WORKSPACE") def mark_read(self, request, slug, pk): - notification = Notification.objects.get( - receiver=request.user, workspace__slug=slug, pk=pk - ) + notification = Notification.objects.get(receiver=request.user, workspace__slug=slug, pk=pk) notification.read_at = timezone.now() notification.save() serializer = NotificationSerializer(notification) return Response(serializer.data, status=status.HTTP_200_OK) - @allow_permission( - allowed_roles=[ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST], level="WORKSPACE" - ) + @allow_permission(allowed_roles=[ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST], level="WORKSPACE") def mark_unread(self, request, slug, pk): - notification = Notification.objects.get( - receiver=request.user, workspace__slug=slug, pk=pk - ) + notification = Notification.objects.get(receiver=request.user, workspace__slug=slug, pk=pk) notification.read_at = None notification.save() serializer = NotificationSerializer(notification) return Response(serializer.data, status=status.HTTP_200_OK) - @allow_permission( - allowed_roles=[ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST], level="WORKSPACE" - ) + @allow_permission(allowed_roles=[ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST], level="WORKSPACE") def archive(self, request, slug, pk): - notification = Notification.objects.get( - receiver=request.user, workspace__slug=slug, pk=pk - ) + notification = Notification.objects.get(receiver=request.user, workspace__slug=slug, pk=pk) notification.archived_at = timezone.now() notification.save() serializer = NotificationSerializer(notification) return Response(serializer.data, status=status.HTTP_200_OK) - @allow_permission( - allowed_roles=[ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST], level="WORKSPACE" - ) + @allow_permission(allowed_roles=[ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST], level="WORKSPACE") def unarchive(self, request, slug, pk): - notification = Notification.objects.get( - receiver=request.user, workspace__slug=slug, pk=pk - ) + notification = Notification.objects.get(receiver=request.user, workspace__slug=slug, pk=pk) notification.archived_at = None notification.save() serializer = NotificationSerializer(notification) @@ -234,9 +192,7 @@ class NotificationViewSet(BaseViewSet, BasePaginator): class UnreadNotificationEndpoint(BaseAPIView): use_read_replica = True - @allow_permission( - allowed_roles=[ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST], level="WORKSPACE" - ) + @allow_permission(allowed_roles=[ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST], level="WORKSPACE") def get(self, request, slug): # Watching Issues Count unread_notifications_count = ( @@ -270,31 +226,23 @@ class UnreadNotificationEndpoint(BaseAPIView): class MarkAllReadNotificationViewSet(BaseViewSet): - @allow_permission( - allowed_roles=[ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST], level="WORKSPACE" - ) + @allow_permission(allowed_roles=[ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST], level="WORKSPACE") def create(self, request, slug): snoozed = request.data.get("snoozed", False) archived = request.data.get("archived", False) type = request.data.get("type", "all") notifications = ( - Notification.objects.filter( - workspace__slug=slug, receiver_id=request.user.id, read_at__isnull=True - ) + Notification.objects.filter(workspace__slug=slug, receiver_id=request.user.id, read_at__isnull=True) .select_related("workspace", "project", "triggered_by", "receiver") .order_by("snoozed_till", "-created_at") ) # Filter for snoozed notifications if snoozed: - notifications = notifications.filter( - Q(snoozed_till__lt=timezone.now()) | Q(snoozed_till__isnull=False) - ) + notifications = notifications.filter(Q(snoozed_till__lt=timezone.now()) | Q(snoozed_till__isnull=False)) else: - notifications = notifications.filter( - Q(snoozed_till__gte=timezone.now()) | Q(snoozed_till__isnull=True) - ) + notifications = notifications.filter(Q(snoozed_till__gte=timezone.now()) | Q(snoozed_till__isnull=True)) # Filter for archived or unarchive if archived: @@ -304,16 +252,16 @@ class MarkAllReadNotificationViewSet(BaseViewSet): # Subscribed issues if type == "watching": - issue_ids = IssueSubscriber.objects.filter( - workspace__slug=slug, subscriber_id=request.user.id - ).values_list("issue_id", flat=True) + issue_ids = IssueSubscriber.objects.filter(workspace__slug=slug, subscriber_id=request.user.id).values_list( + "issue_id", flat=True + ) notifications = notifications.filter(entity_identifier__in=issue_ids) # Assigned Issues if type == "assigned": - issue_ids = IssueAssignee.objects.filter( - workspace__slug=slug, assignee_id=request.user.id - ).values_list("issue_id", flat=True) + issue_ids = IssueAssignee.objects.filter(workspace__slug=slug, assignee_id=request.user.id).values_list( + "issue_id", flat=True + ) notifications = notifications.filter(entity_identifier__in=issue_ids) # Created issues @@ -323,18 +271,16 @@ class MarkAllReadNotificationViewSet(BaseViewSet): ).exists(): notifications = Notification.objects.none() else: - issue_ids = Issue.objects.filter( - workspace__slug=slug, created_by=request.user - ).values_list("pk", flat=True) + issue_ids = Issue.objects.filter(workspace__slug=slug, created_by=request.user).values_list( + "pk", flat=True + ) notifications = notifications.filter(entity_identifier__in=issue_ids) updated_notifications = [] for notification in notifications: notification.read_at = timezone.now() updated_notifications.append(notification) - Notification.objects.bulk_update( - updated_notifications, ["read_at"], batch_size=100 - ) + Notification.objects.bulk_update(updated_notifications, ["read_at"], batch_size=100) return Response({"message": "Successful"}, status=status.HTTP_200_OK) @@ -344,20 +290,14 @@ class UserNotificationPreferenceEndpoint(BaseAPIView): # request the object def get(self, request): - user_notification_preference = UserNotificationPreference.objects.get( - user=request.user - ) + user_notification_preference = UserNotificationPreference.objects.get(user=request.user) serializer = UserNotificationPreferenceSerializer(user_notification_preference) return Response(serializer.data, status=status.HTTP_200_OK) # update the object def patch(self, request): - user_notification_preference = UserNotificationPreference.objects.get( - user=request.user - ) - serializer = UserNotificationPreferenceSerializer( - user_notification_preference, data=request.data, partial=True - ) + user_notification_preference = UserNotificationPreference.objects.get(user=request.user) + serializer = UserNotificationPreferenceSerializer(user_notification_preference, data=request.data, partial=True) if serializer.is_valid(): serializer.save() return Response(serializer.data, status=status.HTTP_200_OK) diff --git a/apps/api/plane/app/views/page/base.py b/apps/api/plane/app/views/page/base.py index 07394f0c7..72fb4ef8e 100644 --- a/apps/api/plane/app/views/page/base.py +++ b/apps/api/plane/app/views/page/base.py @@ -101,9 +101,7 @@ class PageViewSet(BaseViewSet): .order_by("-is_favorite", "-created_at") .annotate( project=Exists( - ProjectPage.objects.filter( - page_id=OuterRef("id"), project_id=self.kwargs.get("project_id") - ) + ProjectPage.objects.filter(page_id=OuterRef("id"), project_id=self.kwargs.get("project_id")) ) ) .annotate( @@ -116,9 +114,7 @@ class PageViewSet(BaseViewSet): Value([], output_field=ArrayField(UUIDField())), ), 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())), ), ) @@ -149,30 +145,19 @@ class PageViewSet(BaseViewSet): def partial_update(self, request, slug, project_id, page_id): try: - page = Page.objects.get( - pk=page_id, workspace__slug=slug, projects__id=project_id - ) + page = Page.objects.get(pk=page_id, workspace__slug=slug, projects__id=project_id) if page.is_locked: - return Response( - {"error": "Page is locked"}, status=status.HTTP_400_BAD_REQUEST - ) + return Response({"error": "Page is locked"}, status=status.HTTP_400_BAD_REQUEST) parent = request.data.get("parent", None) if parent: - _ = Page.objects.get( - pk=parent, workspace__slug=slug, projects__id=project_id - ) + _ = Page.objects.get(pk=parent, workspace__slug=slug, projects__id=project_id) # Only update access if the page owner is the requesting user - if ( - page.access != request.data.get("access", page.access) - and page.owned_by_id != request.user.id - ): + if page.access != request.data.get("access", page.access) and page.owned_by_id != request.user.id: return Response( - { - "error": "Access cannot be updated since this page is owned by someone else" - }, + {"error": "Access cannot be updated since this page is owned by someone else"}, status=status.HTTP_400_BAD_REQUEST, ) @@ -195,9 +180,7 @@ class PageViewSet(BaseViewSet): return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) except Page.DoesNotExist: return Response( - { - "error": "Access cannot be updated since this page is owned by someone else" - }, + {"error": "Access cannot be updated since this page is owned by someone else"}, status=status.HTTP_400_BAD_REQUEST, ) @@ -228,13 +211,11 @@ class PageViewSet(BaseViewSet): ) if page is None: - return Response( - {"error": "Page not found"}, status=status.HTTP_404_NOT_FOUND - ) + return Response({"error": "Page not found"}, status=status.HTTP_404_NOT_FOUND) else: - issue_ids = PageLog.objects.filter( - page_id=page_id, entity_name="issue" - ).values_list("entity_identifier", flat=True) + issue_ids = PageLog.objects.filter(page_id=page_id, entity_name="issue").values_list( + "entity_identifier", flat=True + ) data = PageDetailSerializer(page).data data["issue_ids"] = issue_ids if track_visit: @@ -248,18 +229,14 @@ class PageViewSet(BaseViewSet): return Response(data, status=status.HTTP_200_OK) def lock(self, request, slug, project_id, page_id): - page = Page.objects.filter( - pk=page_id, workspace__slug=slug, projects__id=project_id - ).first() + page = Page.objects.filter(pk=page_id, workspace__slug=slug, projects__id=project_id).first() page.is_locked = True page.save() return Response(status=status.HTTP_204_NO_CONTENT) def unlock(self, request, slug, project_id, page_id): - page = Page.objects.filter( - pk=page_id, workspace__slug=slug, projects__id=project_id - ).first() + page = Page.objects.filter(pk=page_id, workspace__slug=slug, projects__id=project_id).first() page.is_locked = False page.save() @@ -268,19 +245,12 @@ class PageViewSet(BaseViewSet): def access(self, request, slug, project_id, page_id): access = request.data.get("access", 0) - page = Page.objects.filter( - pk=page_id, workspace__slug=slug, projects__id=project_id - ).first() + page = Page.objects.filter(pk=page_id, workspace__slug=slug, projects__id=project_id).first() # Only update access if the page owner is the requesting user - if ( - page.access != request.data.get("access", page.access) - and page.owned_by_id != request.user.id - ): + if page.access != request.data.get("access", page.access) and page.owned_by_id != request.user.id: return Response( - { - "error": "Access cannot be updated since this page is owned by someone else" - }, + {"error": "Access cannot be updated since this page is owned by someone else"}, status=status.HTTP_400_BAD_REQUEST, ) @@ -306,9 +276,7 @@ class PageViewSet(BaseViewSet): return Response(pages, status=status.HTTP_200_OK) def archive(self, request, slug, project_id, page_id): - page = Page.objects.get( - pk=page_id, workspace__slug=slug, projects__id=project_id - ) + page = Page.objects.get(pk=page_id, workspace__slug=slug, projects__id=project_id) # only the owner or admin can archive the page if ( @@ -334,9 +302,7 @@ class PageViewSet(BaseViewSet): return Response({"archived_at": str(datetime.now())}, status=status.HTTP_200_OK) def unarchive(self, request, slug, project_id, page_id): - page = Page.objects.get( - pk=page_id, workspace__slug=slug, projects__id=project_id - ) + page = Page.objects.get(pk=page_id, workspace__slug=slug, projects__id=project_id) # only the owner or admin can un archive the page if ( @@ -360,9 +326,7 @@ class PageViewSet(BaseViewSet): return Response(status=status.HTTP_204_NO_CONTENT) def destroy(self, request, slug, project_id, page_id): - page = Page.objects.get( - pk=page_id, workspace__slug=slug, projects__id=project_id - ) + page = Page.objects.get(pk=page_id, workspace__slug=slug, projects__id=project_id) if page.archived_at is None: return Response( @@ -385,9 +349,7 @@ class PageViewSet(BaseViewSet): ) # remove parent from all the children - _ = Page.objects.filter( - parent_id=page_id, projects__id=project_id, workspace__slug=slug - ).update(parent=None) + _ = Page.objects.filter(parent_id=page_id, projects__id=project_id, workspace__slug=slug).update(parent=None) page.delete() # Delete the user favorite page @@ -418,9 +380,7 @@ class PageViewSet(BaseViewSet): .filter(Q(owned_by=request.user) | Q(access=0)) .annotate( project=Exists( - ProjectPage.objects.filter( - page_id=OuterRef("id"), project_id=self.kwargs.get("project_id") - ) + ProjectPage.objects.filter(page_id=OuterRef("id"), project_id=self.kwargs.get("project_id")) ) ) .filter(project=True) @@ -453,11 +413,7 @@ class PageViewSet(BaseViewSet): output_field=IntegerField(), ) ), - archived_pages=Count( - Case( - When(archived_at__isnull=False, then=1), output_field=IntegerField() - ) - ), + archived_pages=Count(Case(When(archived_at__isnull=False, then=1), output_field=IntegerField())), ) return Response(stats, status=status.HTTP_200_OK) @@ -494,9 +450,7 @@ class PagesDescriptionViewSet(BaseViewSet): def retrieve(self, request, slug, project_id, page_id): page = ( - Page.objects.filter( - pk=page_id, workspace__slug=slug, projects__id=project_id - ) + Page.objects.filter(pk=page_id, workspace__slug=slug, projects__id=project_id) .filter(Q(owned_by=self.request.user) | Q(access=0)) .first() ) @@ -510,17 +464,13 @@ class PagesDescriptionViewSet(BaseViewSet): else: yield b"" - response = StreamingHttpResponse( - stream_data(), content_type="application/octet-stream" - ) + response = StreamingHttpResponse(stream_data(), content_type="application/octet-stream") response["Content-Disposition"] = 'attachment; filename="page_description.bin"' return response def partial_update(self, request, slug, project_id, page_id): page = ( - Page.objects.filter( - pk=page_id, workspace__slug=slug, projects__id=project_id - ) + Page.objects.filter(pk=page_id, workspace__slug=slug, projects__id=project_id) .filter(Q(owned_by=self.request.user) | Q(access=0)) .first() ) @@ -547,18 +497,14 @@ class PagesDescriptionViewSet(BaseViewSet): ) # Serialize the existing instance - existing_instance = json.dumps( - {"description_html": page.description_html}, cls=DjangoJSONEncoder - ) + existing_instance = json.dumps({"description_html": page.description_html}, cls=DjangoJSONEncoder) # Use serializer for validation and update serializer = PageBinaryUpdateSerializer(page, data=request.data, partial=True) if serializer.is_valid(): # Capture the page transaction if request.data.get("description_html"): - page_transaction.delay( - new_value=request.data, old_value=existing_instance, page_id=page_id - ) + page_transaction.delay(new_value=request.data, old_value=existing_instance, page_id=page_id) # Update the page using serializer updated_page = serializer.save() @@ -578,20 +524,14 @@ class PageDuplicateEndpoint(BaseAPIView): permission_classes = [ProjectPagePermission] def post(self, request, slug, project_id, page_id): - page = Page.objects.filter( - pk=page_id, workspace__slug=slug, projects__id=project_id - ).first() + page = Page.objects.filter(pk=page_id, workspace__slug=slug, projects__id=project_id).first() # check for permission if page.access == Page.PRIVATE_ACCESS and page.owned_by_id != request.user.id: - return Response( - {"error": "Permission denied"}, status=status.HTTP_403_FORBIDDEN - ) + return Response({"error": "Permission denied"}, status=status.HTTP_403_FORBIDDEN) # get all the project ids where page is present - project_ids = ProjectPage.objects.filter(page_id=page_id).values_list( - "project_id", flat=True - ) + project_ids = ProjectPage.objects.filter(page_id=page_id).values_list("project_id", flat=True) page.pk = None page.name = f"{page.name} (Copy)" @@ -610,9 +550,7 @@ class PageDuplicateEndpoint(BaseAPIView): updated_by_id=page.updated_by_id, ) - page_transaction.delay( - {"description_html": page.description_html}, None, page.id - ) + page_transaction.delay({"description_html": page.description_html}, None, page.id) # Copy the s3 objects uploaded in the page copy_s3_objects_of_description_and_assets.delay( @@ -627,9 +565,7 @@ class PageDuplicateEndpoint(BaseAPIView): Page.objects.filter(pk=page.id) .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())), ) ) diff --git a/apps/api/plane/app/views/page/version.py b/apps/api/plane/app/views/page/version.py index 15aa3a152..1b285c966 100644 --- a/apps/api/plane/app/views/page/version.py +++ b/apps/api/plane/app/views/page/version.py @@ -6,27 +6,22 @@ from rest_framework.response import Response from plane.db.models import PageVersion from ..base import BaseAPIView from plane.app.serializers import PageVersionSerializer, PageVersionDetailSerializer -from plane.app.permissions import allow_permission, ROLE from plane.app.permissions import ProjectPagePermission -class PageVersionEndpoint(BaseAPIView): +class PageVersionEndpoint(BaseAPIView): permission_classes = [ProjectPagePermission] def get(self, request, slug, project_id, page_id, pk=None): # Check if pk is provided if pk: # Return a single page version - page_version = PageVersion.objects.get( - workspace__slug=slug, page_id=page_id, pk=pk - ) + page_version = PageVersion.objects.get(workspace__slug=slug, page_id=page_id, pk=pk) # Serialize the page version serializer = PageVersionDetailSerializer(page_version) return Response(serializer.data, status=status.HTTP_200_OK) # Return all page versions - page_versions = PageVersion.objects.filter( - workspace__slug=slug, page_id=page_id - ) + page_versions = PageVersion.objects.filter(workspace__slug=slug, page_id=page_id) # Serialize the page versions serializer = PageVersionSerializer(page_versions, many=True) return Response(serializer.data, status=status.HTTP_200_OK) diff --git a/apps/api/plane/app/views/project/base.py b/apps/api/plane/app/views/project/base.py index d4eeca2f7..84b2a5629 100644 --- a/apps/api/plane/app/views/project/base.py +++ b/apps/api/plane/app/views/project/base.py @@ -58,9 +58,7 @@ class ProjectViewSet(BaseViewSet): super() .get_queryset() .filter(workspace__slug=self.kwargs.get("slug")) - .select_related( - "workspace", "workspace__owner", "default_assignee", "project_lead" - ) + .select_related("workspace", "workspace__owner", "default_assignee", "project_lead") .annotate( is_favorite=Exists( UserFavorite.objects.filter( @@ -98,9 +96,7 @@ class ProjectViewSet(BaseViewSet): .distinct() ) - @allow_permission( - allowed_roles=[ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST], level="WORKSPACE" - ) + @allow_permission(allowed_roles=[ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST], level="WORKSPACE") def list_detail(self, request, slug): fields = [field for field in request.GET.get("fields", "").split(",") if field] projects = self.get_queryset().order_by("sort_order", "name") @@ -134,19 +130,13 @@ class ProjectViewSet(BaseViewSet): order_by=request.GET.get("order_by", "-created_at"), request=request, queryset=(projects), - on_results=lambda projects: ProjectListSerializer( - projects, many=True - ).data, + on_results=lambda projects: ProjectListSerializer(projects, many=True).data, ) - projects = ProjectListSerializer( - projects, many=True, fields=fields if fields else None - ).data + projects = ProjectListSerializer(projects, many=True, fields=fields if fields else None).data return Response(projects, status=status.HTTP_200_OK) - @allow_permission( - allowed_roles=[ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST], level="WORKSPACE" - ) + @allow_permission(allowed_roles=[ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST], level="WORKSPACE") def list(self, request, slug): sort_order = ProjectMember.objects.filter( member=self.request.user, @@ -157,9 +147,7 @@ class ProjectViewSet(BaseViewSet): projects = ( Project.objects.filter(workspace__slug=self.kwargs.get("slug")) - .select_related( - "workspace", "workspace__owner", "default_assignee", "project_lead" - ) + .select_related("workspace", "workspace__owner", "default_assignee", "project_lead") .annotate( member_role=ProjectMember.objects.filter( project_id=OuterRef("pk"), @@ -219,9 +207,7 @@ class ProjectViewSet(BaseViewSet): ) return Response(projects, status=status.HTTP_200_OK) - @allow_permission( - allowed_roles=[ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST], level="WORKSPACE" - ) + @allow_permission(allowed_roles=[ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST], level="WORKSPACE") def retrieve(self, request, slug, pk): project = ( self.get_queryset() @@ -234,9 +220,7 @@ class ProjectViewSet(BaseViewSet): ).first() if project is None: - return Response( - {"error": "Project does not exist"}, status=status.HTTP_404_NOT_FOUND - ) + return Response({"error": "Project does not exist"}, status=status.HTTP_404_NOT_FOUND) recent_visited_task.delay( slug=slug, @@ -253,9 +237,7 @@ class ProjectViewSet(BaseViewSet): def create(self, request, slug): workspace = Workspace.objects.get(slug=slug) - serializer = ProjectSerializer( - data={**request.data}, context={"workspace_id": workspace.id} - ) + serializer = ProjectSerializer(data={**request.data}, context={"workspace_id": workspace.id}) if serializer.is_valid(): serializer.save() @@ -266,13 +248,11 @@ class ProjectViewSet(BaseViewSet): role=ROLE.ADMIN.value, ) # Also create the issue property for the user - _ = IssueUserProperty.objects.create( - project_id=serializer.data["id"], user=request.user - ) + _ = IssueUserProperty.objects.create(project_id=serializer.data["id"], user=request.user) - if serializer.data["project_lead"] is not None and str( - serializer.data["project_lead"] - ) != str(request.user.id): + if serializer.data["project_lead"] is not None and str(serializer.data["project_lead"]) != str( + request.user.id + ): ProjectMember.objects.create( project_id=serializer.data["id"], member_id=serializer.data["project_lead"], @@ -380,9 +360,7 @@ class ProjectViewSet(BaseViewSet): project = Project.objects.get(pk=pk) intake_view = request.data.get("inbox_view", project.intake_view) - current_instance = json.dumps( - ProjectSerializer(project).data, cls=DjangoJSONEncoder - ) + current_instance = json.dumps(ProjectSerializer(project).data, cls=DjangoJSONEncoder) if project.archived_at: return Response( {"error": "Archived projects cannot be updated"}, @@ -474,9 +452,7 @@ class ProjectArchiveUnarchiveEndpoint(BaseAPIView): project.archived_at = timezone.now() project.save() UserFavorite.objects.filter(workspace__slug=slug, project=project_id).delete() - return Response( - {"archived_at": str(project.archived_at)}, status=status.HTTP_200_OK - ) + return Response({"archived_at": str(project.archived_at)}, status=status.HTTP_200_OK) @allow_permission([ROLE.ADMIN, ROLE.MEMBER]) def delete(self, request, slug, project_id): @@ -492,26 +468,18 @@ class ProjectIdentifierEndpoint(BaseAPIView): name = request.GET.get("name", "").strip().upper() if name == "": - return Response( - {"error": "Name is required"}, status=status.HTTP_400_BAD_REQUEST - ) + return Response({"error": "Name is required"}, status=status.HTTP_400_BAD_REQUEST) - exists = ProjectIdentifier.objects.filter( - name=name, workspace__slug=slug - ).values("id", "name", "project") + exists = ProjectIdentifier.objects.filter(name=name, workspace__slug=slug).values("id", "name", "project") - return Response( - {"exists": len(exists), "identifiers": exists}, status=status.HTTP_200_OK - ) + return Response({"exists": len(exists), "identifiers": exists}, status=status.HTTP_200_OK) @allow_permission([ROLE.ADMIN, ROLE.MEMBER], level="WORKSPACE") def delete(self, request, slug): name = request.data.get("name", "").strip().upper() if name == "": - return Response( - {"error": "Name is required"}, status=status.HTTP_400_BAD_REQUEST - ) + return Response({"error": "Name is required"}, status=status.HTTP_400_BAD_REQUEST) if Project.objects.filter(identifier=name, workspace__slug=slug).exists(): return Response( @@ -528,9 +496,7 @@ class ProjectUserViewsEndpoint(BaseAPIView): def post(self, request, slug, project_id): project = Project.objects.get(pk=project_id, workspace__slug=slug) - project_member = ProjectMember.objects.filter( - member=request.user, project=project, is_active=True - ).first() + project_member = ProjectMember.objects.filter(member=request.user, project=project, is_active=True).first() if project_member is None: return Response({"error": "Forbidden"}, status=status.HTTP_403_FORBIDDEN) @@ -559,9 +525,7 @@ class ProjectFavoritesViewSet(BaseViewSet): .get_queryset() .filter(workspace__slug=self.kwargs.get("slug")) .filter(user=self.request.user) - .select_related( - "project", "project__project_lead", "project__default_assignee" - ) + .select_related("project", "project__project_lead", "project__default_assignee") .select_related("workspace", "workspace__owner") ) diff --git a/apps/api/plane/app/views/project/invite.py b/apps/api/plane/app/views/project/invite.py index c7ae8b19c..cc5b3f4b5 100644 --- a/apps/api/plane/app/views/project/invite.py +++ b/apps/api/plane/app/views/project/invite.py @@ -52,9 +52,7 @@ class ProjectInvitationsViewset(BaseViewSet): # Check if email is provided if not emails: - return Response( - {"error": "Emails are required"}, status=status.HTTP_400_BAD_REQUEST - ) + return Response({"error": "Emails are required"}, status=status.HTTP_400_BAD_REQUEST) for email in emails: workspace_role = WorkspaceMember.objects.filter( @@ -62,11 +60,7 @@ class ProjectInvitationsViewset(BaseViewSet): ).role if workspace_role in [5, 20] and workspace_role != email.get("role", 5): - return Response( - { - "error": "You cannot invite a user with different role than workspace role" - } - ) + return Response({"error": "You cannot invite a user with different role than workspace role"}) workspace = Workspace.objects.get(slug=slug) @@ -91,7 +85,7 @@ class ProjectInvitationsViewset(BaseViewSet): except ValidationError: return Response( { - "error": f"Invalid email - {email} provided a valid email address is required to send the invite" + "error": f"Invalid email - {email} provided a valid email address is required to send the invite" # noqa: E501 }, status=status.HTTP_400_BAD_REQUEST, ) @@ -112,9 +106,7 @@ class ProjectInvitationsViewset(BaseViewSet): request.user.email, ) - return Response( - {"message": "Email sent successfully"}, status=status.HTTP_200_OK - ) + return Response({"message": "Email sent successfully"}, status=status.HTTP_200_OK) class UserProjectInvitationsViewset(BaseViewSet): @@ -134,20 +126,13 @@ class UserProjectInvitationsViewset(BaseViewSet): project_ids = request.data.get("project_ids", []) # Get the workspace user role - workspace_member = WorkspaceMember.objects.get( - member=request.user, workspace__slug=slug, is_active=True - ) + workspace_member = WorkspaceMember.objects.get(member=request.user, workspace__slug=slug, is_active=True) # Get all the projects - projects = Project.objects.filter( - id__in=project_ids, workspace__slug=slug - ).only("id", "network") + projects = Project.objects.filter(id__in=project_ids, workspace__slug=slug).only("id", "network") # Check if user has permission to join each project for project in projects: - if ( - project.network == ProjectNetwork.SECRET.value - and workspace_member.role != ROLE.ADMIN.value - ): + if project.network == ProjectNetwork.SECRET.value and workspace_member.role != ROLE.ADMIN.value: return Response( {"error": "Only workspace admins can join private project"}, status=status.HTTP_403_FORBIDDEN, @@ -157,9 +142,9 @@ class UserProjectInvitationsViewset(BaseViewSet): workspace = workspace_member.workspace # If the user was already part of workspace - _ = ProjectMember.objects.filter( - workspace__slug=slug, project_id__in=project_ids, member=request.user - ).update(is_active=True) + _ = ProjectMember.objects.filter(workspace__slug=slug, project_id__in=project_ids, member=request.user).update( + is_active=True + ) ProjectMember.objects.bulk_create( [ @@ -188,18 +173,14 @@ class UserProjectInvitationsViewset(BaseViewSet): ignore_conflicts=True, ) - return Response( - {"message": "Projects joined successfully"}, status=status.HTTP_201_CREATED - ) + return Response({"message": "Projects joined successfully"}, status=status.HTTP_201_CREATED) class ProjectJoinEndpoint(BaseAPIView): permission_classes = [AllowAny] def post(self, request, slug, project_id, pk): - project_invite = ProjectMemberInvite.objects.get( - pk=pk, project_id=project_id, workspace__slug=slug - ) + project_invite = ProjectMemberInvite.objects.get(pk=pk, project_id=project_id, workspace__slug=slug) email = request.data.get("email", "") @@ -219,9 +200,7 @@ class ProjectJoinEndpoint(BaseAPIView): user = User.objects.filter(email=email).first() # Check if user is a part of workspace - workspace_member = WorkspaceMember.objects.filter( - workspace__slug=slug, member=user - ).first() + workspace_member = WorkspaceMember.objects.filter(workspace__slug=slug, member=user).first() # Add him to workspace if workspace_member is None: _ = WorkspaceMember.objects.create( @@ -266,8 +245,6 @@ class ProjectJoinEndpoint(BaseAPIView): ) def get(self, request, slug, project_id, pk): - project_invitation = ProjectMemberInvite.objects.get( - workspace__slug=slug, project_id=project_id, pk=pk - ) + project_invitation = ProjectMemberInvite.objects.get(workspace__slug=slug, project_id=project_id, pk=pk) serializer = ProjectMemberInviteSerializer(project_invitation) return Response(serializer.data, status=status.HTTP_200_OK) diff --git a/apps/api/plane/app/views/project/member.py b/apps/api/plane/app/views/project/member.py index 0b09c1366..8102809ac 100644 --- a/apps/api/plane/app/views/project/member.py +++ b/apps/api/plane/app/views/project/member.py @@ -57,9 +57,7 @@ class ProjectMemberViewSet(BaseViewSet): bulk_issue_props = [] # Create a dictionary of the member_id and their roles - member_roles = { - member.get("member_id"): member.get("role") for member in members - } + member_roles = {member.get("member_id"): member.get("role") for member in members} # check the workspace role of the new user for member in member_roles: @@ -68,17 +66,13 @@ class ProjectMemberViewSet(BaseViewSet): ).role if workspace_member_role in [20] and member_roles.get(member) in [5, 15]: return Response( - { - "error": "You cannot add a user with role lower than the workspace role" - }, + {"error": "You cannot add a user with role lower than the workspace role"}, status=status.HTTP_400_BAD_REQUEST, ) if workspace_member_role in [5] and member_roles.get(member) in [15, 20]: return Response( - { - "error": "You cannot add a user with role higher than the workspace role" - }, + {"error": "You cannot add a user with role higher than the workspace role"}, status=status.HTTP_400_BAD_REQUEST, ) @@ -92,9 +86,7 @@ class ProjectMemberViewSet(BaseViewSet): bulk_project_members.append(project_member) # Update the roles of the existing members - ProjectMember.objects.bulk_update( - bulk_project_members, ["is_active", "role"], batch_size=100 - ) + ProjectMember.objects.bulk_update(bulk_project_members, ["is_active", "role"], batch_size=100) # Get the list of project members of the requested workspace with the given slug project_members = ( @@ -134,13 +126,9 @@ class ProjectMemberViewSet(BaseViewSet): ) # Bulk create the project members and issue properties - project_members = ProjectMember.objects.bulk_create( - bulk_project_members, batch_size=10, ignore_conflicts=True - ) + project_members = ProjectMember.objects.bulk_create(bulk_project_members, batch_size=10, ignore_conflicts=True) - _ = IssueUserProperty.objects.bulk_create( - bulk_issue_props, batch_size=10, ignore_conflicts=True - ) + _ = IssueUserProperty.objects.bulk_create(bulk_issue_props, batch_size=10, ignore_conflicts=True) project_members = ProjectMember.objects.filter( project_id=project_id, @@ -172,16 +160,12 @@ class ProjectMemberViewSet(BaseViewSet): member__member_workspace__is_active=True, ).select_related("project", "member", "workspace") - serializer = ProjectMemberRoleSerializer( - project_members, fields=("id", "member", "role"), many=True - ) + serializer = ProjectMemberRoleSerializer(project_members, fields=("id", "member", "role"), many=True) return Response(serializer.data, status=status.HTTP_200_OK) @allow_permission([ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST]) def partial_update(self, request, slug, project_id, pk): - project_member = ProjectMember.objects.get( - pk=pk, workspace__slug=slug, project_id=project_id, is_active=True - ) + project_member = ProjectMember.objects.get(pk=pk, workspace__slug=slug, project_id=project_id, is_active=True) # Fetch the workspace role of the project member workspace_role = WorkspaceMember.objects.get( @@ -203,20 +187,15 @@ class ProjectMemberViewSet(BaseViewSet): is_active=True, ) - if workspace_role in [5] and int( - request.data.get("role", project_member.role) - ) in [15, 20]: + if workspace_role in [5] and int(request.data.get("role", project_member.role)) in [15, 20]: return Response( - { - "error": "You cannot add a user with role higher than the workspace role" - }, + {"error": "You cannot add a user with role higher than the workspace role"}, status=status.HTTP_400_BAD_REQUEST, ) if ( "role" in request.data - and int(request.data.get("role", project_member.role)) - > requested_project_member.role + and int(request.data.get("role", project_member.role)) > requested_project_member.role and not is_workspace_admin ): return Response( @@ -224,9 +203,7 @@ class ProjectMemberViewSet(BaseViewSet): status=status.HTTP_400_BAD_REQUEST, ) - serializer = ProjectMemberSerializer( - project_member, data=request.data, partial=True - ) + serializer = ProjectMemberSerializer(project_member, data=request.data, partial=True) if serializer.is_valid(): serializer.save() @@ -252,9 +229,7 @@ class ProjectMemberViewSet(BaseViewSet): # User cannot remove himself if str(project_member.id) == str(requesting_project_member.id): return Response( - { - "error": "You cannot remove yourself from the workspace. Please use leave workspace" - }, + {"error": "You cannot remove yourself from the workspace. Please use leave workspace"}, status=status.HTTP_400_BAD_REQUEST, ) # User cannot deactivate higher role @@ -287,7 +262,7 @@ class ProjectMemberViewSet(BaseViewSet): ): return Response( { - "error": "You cannot leave the project as your the only admin of the project you will have to either delete the project or create an another admin" + "error": "You cannot leave the project as your the only admin of the project you will have to either delete the project or create an another admin" # noqa: E501 }, status=status.HTTP_400_BAD_REQUEST, ) @@ -323,7 +298,5 @@ class UserProjectRolesEndpoint(BaseAPIView): member__member_workspace__is_active=True, ).values("project_id", "role") - project_members = { - str(member["project_id"]): member["role"] for member in project_members - } + project_members = {str(member["project_id"]): member["role"] for member in project_members} return Response(project_members, status=status.HTTP_200_OK) diff --git a/apps/api/plane/app/views/search/base.py b/apps/api/plane/app/views/search/base.py index b98e2855f..a598d1ee1 100644 --- a/apps/api/plane/app/views/search/base.py +++ b/apps/api/plane/app/views/search/base.py @@ -120,9 +120,7 @@ class GlobalSearchEndpoint(BaseAPIView): if workspace_search == "false" and project_id: cycles = cycles.filter(project_id=project_id) - return cycles.distinct().values( - "name", "id", "project_id", "project__identifier", "workspace__slug" - ) + return cycles.distinct().values("name", "id", "project_id", "project__identifier", "workspace__slug") def filter_modules(self, query, slug, project_id, workspace_search): fields = ["name"] @@ -141,9 +139,7 @@ class GlobalSearchEndpoint(BaseAPIView): if workspace_search == "false" and project_id: modules = modules.filter(project_id=project_id) - return modules.distinct().values( - "name", "id", "project_id", "project__identifier", "workspace__slug" - ) + return modules.distinct().values("name", "id", "project_id", "project__identifier", "workspace__slug") def filter_pages(self, query, slug, project_id, workspace_search): fields = ["name"] @@ -161,9 +157,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())), ) ) @@ -180,17 +174,13 @@ 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.distinct().values( - "name", "id", "project_ids", "project_identifiers", "workspace__slug" - ) + return pages.distinct().values("name", "id", "project_ids", "project_identifiers", "workspace__slug") def filter_views(self, query, slug, project_id, workspace_search): fields = ["name"] @@ -209,9 +199,7 @@ class GlobalSearchEndpoint(BaseAPIView): if workspace_search == "false" and project_id: issue_views = issue_views.filter(project_id=project_id) - return issue_views.distinct().values( - "name", "id", "project_id", "project__identifier", "workspace__slug" - ) + return issue_views.distinct().values("name", "id", "project_id", "project__identifier", "workspace__slug") def get(self, request, slug): query = request.query_params.get("search", False) @@ -308,9 +296,7 @@ class SearchEndpoint(BaseAPIView): if issue_id: issue_created_by = ( - Issue.objects.filter(id=issue_id) - .values_list("created_by_id", flat=True) - .first() + Issue.objects.filter(id=issue_id).values_list("created_by_id", flat=True).first() ) users = ( users.filter(Q(role__gt=10) | Q(member_id=issue_created_by)) @@ -344,15 +330,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) @@ -411,20 +394,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"), @@ -542,9 +521,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) @@ -558,15 +535,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) @@ -623,20 +597,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/search/issue.py b/apps/api/plane/app/views/search/issue.py index b3bce1eda..ac9d783a9 100644 --- a/apps/api/plane/app/views/search/issue.py +++ b/apps/api/plane/app/views/search/issue.py @@ -30,23 +30,17 @@ class IssueSearchEndpoint(BaseAPIView): return issues - def search_issues_and_excluding_parent( - self, issues: QuerySet, issue_id: str - ) -> QuerySet: + def search_issues_and_excluding_parent(self, issues: QuerySet, issue_id: str) -> QuerySet: """ Search issues and epics by query excluding the parent """ issue = Issue.issue_objects.filter(pk=issue_id).first() if issue: - issues = issues.filter( - ~Q(pk=issue_id), ~Q(pk=issue.parent_id), ~Q(parent_id=issue_id) - ) + issues = issues.filter(~Q(pk=issue_id), ~Q(pk=issue.parent_id), ~Q(parent_id=issue_id)) return issues - def filter_issues_excluding_related_issues( - self, issue_id: str, issues: QuerySet - ) -> QuerySet: + def filter_issues_excluding_related_issues(self, issue_id: str, issues: QuerySet) -> QuerySet: """ Filter issues excluding related issues """ @@ -81,18 +75,14 @@ class IssueSearchEndpoint(BaseAPIView): """ Exclude issues in cycles """ - issues = issues.exclude( - Q(issue_cycle__isnull=False) & Q(issue_cycle__deleted_at__isnull=True) - ) + issues = issues.exclude(Q(issue_cycle__isnull=False) & Q(issue_cycle__deleted_at__isnull=True)) return issues def exclude_issues_in_module(self, issues: QuerySet, module: str) -> QuerySet: """ Exclude issues in a module """ - issues = issues.exclude( - Q(issue_module__module=module) & Q(issue_module__deleted_at__isnull=True) - ) + issues = issues.exclude(Q(issue_module__module=module) & Q(issue_module__deleted_at__isnull=True)) return issues def filter_issues_without_target_date(self, issues: QuerySet) -> QuerySet: diff --git a/apps/api/plane/app/views/state/base.py b/apps/api/plane/app/views/state/base.py index b735659c5..de8d93953 100644 --- a/apps/api/plane/app/views/state/base.py +++ b/apps/api/plane/app/views/state/base.py @@ -57,9 +57,7 @@ class StateViewSet(BaseViewSet): @allow_permission([ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST]) def partial_update(self, request, slug, project_id, pk): try: - state = State.objects.get( - pk=pk, project_id=project_id, workspace__slug=slug - ) + state = State.objects.get(pk=pk, project_id=project_id, workspace__slug=slug) serializer = StateSerializer(state, data=request.data, partial=True) if serializer.is_valid(): serializer.save() @@ -103,20 +101,14 @@ class StateViewSet(BaseViewSet): @allow_permission([ROLE.ADMIN]) def mark_as_default(self, request, slug, project_id, pk): # Select all the states which are marked as default - _ = State.objects.filter( - workspace__slug=slug, project_id=project_id, default=True - ).update(default=False) - _ = State.objects.filter( - workspace__slug=slug, project_id=project_id, pk=pk - ).update(default=True) + _ = State.objects.filter(workspace__slug=slug, project_id=project_id, default=True).update(default=False) + _ = State.objects.filter(workspace__slug=slug, project_id=project_id, pk=pk).update(default=True) return Response(status=status.HTTP_204_NO_CONTENT) @invalidate_cache(path="workspaces/:slug/states/", url_params=True, user=False) @allow_permission([ROLE.ADMIN]) def destroy(self, request, slug, project_id, pk): - state = State.objects.get( - is_triage=False, pk=pk, project_id=project_id, workspace__slug=slug - ) + state = State.objects.get(is_triage=False, pk=pk, project_id=project_id, workspace__slug=slug) if state.default: return Response( diff --git a/apps/api/plane/app/views/timezone/base.py b/apps/api/plane/app/views/timezone/base.py index bb3f10c0b..3fa9efd09 100644 --- a/apps/api/plane/app/views/timezone/base.py +++ b/apps/api/plane/app/views/timezone/base.py @@ -187,10 +187,7 @@ class TimezoneEndpoint(APIView): total_seconds = int(current_utc_offset.total_seconds()) hours_offset = total_seconds // 3600 minutes_offset = abs(total_seconds % 3600) // 60 - offset = ( - f"{'+' if hours_offset >= 0 else '-'}" - f"{abs(hours_offset):02}:{minutes_offset:02}" - ) + offset = f"{'+' if hours_offset >= 0 else '-'}{abs(hours_offset):02}:{minutes_offset:02}" timezone_value = { "offset": int(current_offset), diff --git a/apps/api/plane/app/views/user/base.py b/apps/api/plane/app/views/user/base.py index 08389d50c..c26b63c13 100644 --- a/apps/api/plane/app/views/user/base.py +++ b/apps/api/plane/app/views/user/base.py @@ -63,9 +63,7 @@ class UserEndpoint(BaseViewSet): def retrieve_instance_admin(self, request): instance = Instance.objects.first() - is_admin = InstanceAdmin.objects.filter( - instance=instance, user=request.user - ).exists() + is_admin = InstanceAdmin.objects.filter(instance=instance, user=request.user).exists() return Response({"is_instance_admin": is_admin}, status=status.HTTP_200_OK) def partial_update(self, request, *args, **kwargs): @@ -78,18 +76,14 @@ class UserEndpoint(BaseViewSet): # Instance admin check if InstanceAdmin.objects.filter(user=user).exists(): return Response( - { - "error": "You cannot deactivate your account since you are an instance admin" - }, + {"error": "You cannot deactivate your account since you are an instance admin"}, status=status.HTTP_400_BAD_REQUEST, ) projects_to_deactivate = [] workspaces_to_deactivate = [] - projects = ProjectMember.objects.filter( - member=request.user, is_active=True - ).annotate( + projects = ProjectMember.objects.filter(member=request.user, is_active=True).annotate( other_admin_exists=Count( Case( When(Q(role=20, is_active=True) & ~Q(member=request.user), then=1), @@ -106,15 +100,11 @@ class UserEndpoint(BaseViewSet): projects_to_deactivate.append(project) else: return Response( - { - "error": "You cannot deactivate account as you are the only admin in some projects." - }, + {"error": "You cannot deactivate account as you are the only admin in some projects."}, status=status.HTTP_400_BAD_REQUEST, ) - workspaces = WorkspaceMember.objects.filter( - member=request.user, is_active=True - ).annotate( + workspaces = WorkspaceMember.objects.filter(member=request.user, is_active=True).annotate( other_admin_exists=Count( Case( When(Q(role=20, is_active=True) & ~Q(member=request.user), then=1), @@ -131,19 +121,13 @@ class UserEndpoint(BaseViewSet): workspaces_to_deactivate.append(workspace) else: return Response( - { - "error": "You cannot deactivate account as you are the only admin in some workspaces." - }, + {"error": "You cannot deactivate account as you are the only admin in some workspaces."}, status=status.HTTP_400_BAD_REQUEST, ) - ProjectMember.objects.bulk_update( - projects_to_deactivate, ["is_active"], batch_size=100 - ) + ProjectMember.objects.bulk_update(projects_to_deactivate, ["is_active"], batch_size=100) - WorkspaceMember.objects.bulk_update( - workspaces_to_deactivate, ["is_active"], batch_size=100 - ) + WorkspaceMember.objects.bulk_update(workspaces_to_deactivate, ["is_active"], batch_size=100) # Delete all workspace invites WorkspaceMemberInvite.objects.filter(email=user.email).delete() @@ -224,9 +208,7 @@ class UserActivityEndpoint(BaseAPIView, BasePaginator): order_by=request.GET.get("order_by", "-created_at"), request=request, queryset=queryset, - on_results=lambda issue_activities: IssueActivitySerializer( - issue_activities, many=True - ).data, + on_results=lambda issue_activities: IssueActivitySerializer(issue_activities, many=True).data, ) diff --git a/apps/api/plane/app/views/view/base.py b/apps/api/plane/app/views/view/base.py index 90196500c..98fe04c62 100644 --- a/apps/api/plane/app/views/view/base.py +++ b/apps/api/plane/app/views/view/base.py @@ -64,34 +64,22 @@ class WorkspaceViewViewSet(BaseViewSet): .distinct() ) - @allow_permission( - allowed_roles=[ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST], level="WORKSPACE" - ) + @allow_permission(allowed_roles=[ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST], level="WORKSPACE") def list(self, request, slug): queryset = self.get_queryset() fields = [field for field in request.GET.get("fields", "").split(",") if field] - if WorkspaceMember.objects.filter( - workspace__slug=slug, member=request.user, role=5, is_active=True - ).exists(): + if WorkspaceMember.objects.filter(workspace__slug=slug, member=request.user, role=5, is_active=True).exists(): queryset = queryset.filter(owned_by=request.user) - views = IssueViewSerializer( - queryset, many=True, fields=fields if fields else None - ).data + views = IssueViewSerializer(queryset, many=True, fields=fields if fields else None).data return Response(views, status=status.HTTP_200_OK) - @allow_permission( - allowed_roles=[], level="WORKSPACE", creator=True, model=IssueView - ) + @allow_permission(allowed_roles=[], level="WORKSPACE", creator=True, model=IssueView) def partial_update(self, request, slug, pk): with transaction.atomic(): - workspace_view = IssueView.objects.select_for_update().get( - pk=pk, workspace__slug=slug - ) + workspace_view = IssueView.objects.select_for_update().get(pk=pk, workspace__slug=slug) if workspace_view.is_locked: - return Response( - {"error": "view is locked"}, status=status.HTTP_400_BAD_REQUEST - ) + return Response({"error": "view is locked"}, status=status.HTTP_400_BAD_REQUEST) # Only update the view if owner is updating if workspace_view.owned_by_id != request.user.id: @@ -100,9 +88,7 @@ class WorkspaceViewViewSet(BaseViewSet): status=status.HTTP_400_BAD_REQUEST, ) - serializer = IssueViewSerializer( - workspace_view, data=request.data, partial=True - ) + serializer = IssueViewSerializer(workspace_view, data=request.data, partial=True) if serializer.is_valid(): serializer.save() @@ -121,9 +107,7 @@ class WorkspaceViewViewSet(BaseViewSet): ) return Response(serializer.data, status=status.HTTP_200_OK) - @allow_permission( - allowed_roles=[ROLE.ADMIN], level="WORKSPACE", creator=True, model=IssueView - ) + @allow_permission(allowed_roles=[ROLE.ADMIN], level="WORKSPACE", creator=True, model=IssueView) def destroy(self, request, slug, pk): workspace_view = IssueView.objects.get(pk=pk, workspace__slug=slug) @@ -177,9 +161,7 @@ class WorkspaceViewIssuesViewSet(BaseViewSet): return ( issues.annotate( cycle_id=Subquery( - CycleIssue.objects.filter( - issue=OuterRef("id"), deleted_at__isnull=True - ).values("cycle_id")[:1] + CycleIssue.objects.filter(issue=OuterRef("id"), deleted_at__isnull=True).values("cycle_id")[:1] ) ) .annotate( @@ -227,9 +209,7 @@ class WorkspaceViewIssuesViewSet(BaseViewSet): return Issue.issue_objects.filter(workspace__slug=self.kwargs.get("slug")) @method_decorator(gzip_page) - @allow_permission( - allowed_roles=[ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST], level="WORKSPACE" - ) + @allow_permission(allowed_roles=[ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST], level="WORKSPACE") def list(self, request, slug): issue_queryset = self.get_queryset() @@ -274,9 +254,7 @@ class IssueViewViewSet(BaseViewSet): model = IssueView def perform_create(self, serializer): - serializer.save( - project_id=self.kwargs.get("project_id"), owned_by=self.request.user - ) + serializer.save(project_id=self.kwargs.get("project_id"), owned_by=self.request.user) def get_queryset(self): subquery = UserFavorite.objects.filter( @@ -320,9 +298,7 @@ class IssueViewViewSet(BaseViewSet): ): queryset = queryset.filter(owned_by=request.user) fields = [field for field in request.GET.get("fields", "").split(",") if field] - views = IssueViewSerializer( - queryset, many=True, fields=fields if fields else None - ).data + views = IssueViewSerializer(queryset, many=True, fields=fields if fields else None).data return Response(views, status=status.HTTP_200_OK) @allow_permission(allowed_roles=[ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST]) @@ -363,14 +339,10 @@ class IssueViewViewSet(BaseViewSet): @allow_permission(allowed_roles=[], creator=True, model=IssueView) def partial_update(self, request, slug, project_id, pk): with transaction.atomic(): - issue_view = IssueView.objects.select_for_update().get( - pk=pk, workspace__slug=slug, project_id=project_id - ) + issue_view = IssueView.objects.select_for_update().get(pk=pk, workspace__slug=slug, project_id=project_id) if issue_view.is_locked: - return Response( - {"error": "view is locked"}, status=status.HTTP_400_BAD_REQUEST - ) + return Response({"error": "view is locked"}, status=status.HTTP_400_BAD_REQUEST) # Only update the view if owner is updating if issue_view.owned_by_id != request.user.id: @@ -379,9 +351,7 @@ class IssueViewViewSet(BaseViewSet): status=status.HTTP_400_BAD_REQUEST, ) - serializer = IssueViewSerializer( - issue_view, data=request.data, partial=True - ) + serializer = IssueViewSerializer(issue_view, data=request.data, partial=True) if serializer.is_valid(): serializer.save() @@ -390,9 +360,7 @@ class IssueViewViewSet(BaseViewSet): @allow_permission(allowed_roles=[ROLE.ADMIN], creator=True, model=IssueView) def destroy(self, request, slug, project_id, pk): - project_view = IssueView.objects.get( - pk=pk, project_id=project_id, workspace__slug=slug - ) + project_view = IssueView.objects.get(pk=pk, project_id=project_id, workspace__slug=slug) if ( ProjectMember.objects.filter( workspace__slug=slug, diff --git a/apps/api/plane/app/views/webhook/base.py b/apps/api/plane/app/views/webhook/base.py index 0ed4ba9e0..e857c3e08 100644 --- a/apps/api/plane/app/views/webhook/base.py +++ b/apps/api/plane/app/views/webhook/base.py @@ -18,9 +18,7 @@ class WebhookEndpoint(BaseAPIView): def post(self, request, slug): workspace = Workspace.objects.get(slug=slug) try: - serializer = WebhookSerializer( - data=request.data, context={"request": request} - ) + serializer = WebhookSerializer(data=request.data, context={"request": request}) if serializer.is_valid(): serializer.save(workspace_id=workspace.id) return Response(serializer.data, status=status.HTTP_201_CREATED) @@ -119,8 +117,6 @@ class WebhookSecretRegenerateEndpoint(BaseAPIView): class WebhookLogsEndpoint(BaseAPIView): @allow_permission(allowed_roles=[ROLE.ADMIN], level="WORKSPACE") def get(self, request, slug, webhook_id): - webhook_logs = WebhookLog.objects.filter( - workspace__slug=slug, webhook=webhook_id - ) + webhook_logs = WebhookLog.objects.filter(workspace__slug=slug, webhook=webhook_id) serializer = WebhookLogSerializer(webhook_logs, many=True) return Response(serializer.data, status=status.HTTP_200_OK) diff --git a/apps/api/plane/app/views/workspace/base.py b/apps/api/plane/app/views/workspace/base.py index 027d829a9..c27b7adbb 100644 --- a/apps/api/plane/app/views/workspace/base.py +++ b/apps/api/plane/app/views/workspace/base.py @@ -57,9 +57,7 @@ class WorkSpaceViewSet(BaseViewSet): def get_queryset(self): member_count = ( - WorkspaceMember.objects.filter( - workspace=OuterRef("id"), member__is_bot=False, is_active=True - ) + WorkspaceMember.objects.filter(workspace=OuterRef("id"), member__is_bot=False, is_active=True) .order_by() .annotate(count=Func(F("id"), function="Count")) .values("count") @@ -126,9 +124,7 @@ class WorkSpaceViewSet(BaseViewSet): ) # Get total members and role - total_members = WorkspaceMember.objects.filter( - workspace_id=serializer.data["id"] - ).count() + total_members = WorkspaceMember.objects.filter(workspace_id=serializer.data["id"]).count() data = serializer.data data["total_members"] = total_members data["role"] = 20 @@ -179,31 +175,25 @@ class UserWorkSpacesEndpoint(BaseAPIView): def get(self, request): fields = [field for field in request.GET.get("fields", "").split(",") if field] member_count = ( - WorkspaceMember.objects.filter( - workspace=OuterRef("id"), member__is_bot=False, is_active=True - ) + WorkspaceMember.objects.filter(workspace=OuterRef("id"), member__is_bot=False, is_active=True) .order_by() .annotate(count=Func(F("id"), function="Count")) .values("count") ) - role = WorkspaceMember.objects.filter( - workspace=OuterRef("id"), member=request.user, is_active=True - ).values("role") + role = WorkspaceMember.objects.filter(workspace=OuterRef("id"), member=request.user, is_active=True).values( + "role" + ) workspace = ( Workspace.objects.prefetch_related( Prefetch( "workspace_member", - queryset=WorkspaceMember.objects.filter( - member=request.user, is_active=True - ), + queryset=WorkspaceMember.objects.filter(member=request.user, is_active=True), ) ) .annotate(role=role, total_members=member_count) - .filter( - workspace_member__member=request.user, workspace_member__is_active=True - ) + .filter(workspace_member__member=request.user, workspace_member__is_active=True) .distinct() ) @@ -226,10 +216,7 @@ class WorkSpaceAvailabilityCheckEndpoint(BaseAPIView): status=status.HTTP_400_BAD_REQUEST, ) - workspace = ( - Workspace.objects.filter(slug=slug).exists() - or slug in RESTRICTED_WORKSPACE_SLUGS - ) + workspace = Workspace.objects.filter(slug=slug).exists() or slug in RESTRICTED_WORKSPACE_SLUGS return Response({"status": not workspace}, status=status.HTTP_200_OK) @@ -268,9 +255,7 @@ class UserWorkspaceDashboardEndpoint(BaseAPIView): .order_by("week_in_month") ) - assigned_issues = Issue.issue_objects.filter( - workspace__slug=slug, assignees__in=[request.user] - ).count() + assigned_issues = Issue.issue_objects.filter(workspace__slug=slug, assignees__in=[request.user]).count() pending_issues_count = Issue.issue_objects.filter( ~Q(state__group__in=["completed", "cancelled"]), @@ -283,18 +268,14 @@ class UserWorkspaceDashboardEndpoint(BaseAPIView): ).count() issues_due_week = ( - Issue.issue_objects.filter( - workspace__slug=slug, assignees__in=[request.user] - ) + Issue.issue_objects.filter(workspace__slug=slug, assignees__in=[request.user]) .annotate(target_week=ExtractWeek("target_date")) .filter(target_week=timezone.now().date().isocalendar()[1]) .count() ) state_distribution = ( - Issue.issue_objects.filter( - workspace__slug=slug, assignees__in=[request.user] - ) + Issue.issue_objects.filter(workspace__slug=slug, assignees__in=[request.user]) .annotate(state_group=F("state__group")) .values("state_group") .annotate(state_count=Count("state_group")) @@ -363,9 +344,7 @@ class ExportWorkspaceUserActivityEndpoint(BaseAPIView): def post(self, request, slug, user_id): if not request.data.get("date"): - return Response( - {"error": "Date is required"}, status=status.HTTP_400_BAD_REQUEST - ) + return Response({"error": "Date is required"}, status=status.HTTP_400_BAD_REQUEST) user_activities = IssueActivity.objects.filter( ~Q(field__in=["comment", "vote", "reaction", "draft"]), @@ -403,7 +382,5 @@ class ExportWorkspaceUserActivityEndpoint(BaseAPIView): ] csv_buffer = self.generate_csv_from_rows([header] + rows) response = HttpResponse(csv_buffer.getvalue(), content_type="text/csv") - response["Content-Disposition"] = ( - 'attachment; filename="workspace-user-activity.csv"' - ) + response["Content-Disposition"] = 'attachment; filename="workspace-user-activity.csv"' return response diff --git a/apps/api/plane/app/views/workspace/draft.py b/apps/api/plane/app/views/workspace/draft.py index e4b032725..c89fe4a73 100644 --- a/apps/api/plane/app/views/workspace/draft.py +++ b/apps/api/plane/app/views/workspace/draft.py @@ -49,9 +49,9 @@ class WorkspaceDraftIssueViewSet(BaseViewSet): .prefetch_related("assignees", "labels", "draft_issue_module__module") .annotate( cycle_id=Subquery( - DraftIssueCycle.objects.filter( - draft_issue=OuterRef("id"), deleted_at__isnull=True - ).values("cycle_id")[:1] + DraftIssueCycle.objects.filter(draft_issue=OuterRef("id"), deleted_at__isnull=True).values( + "cycle_id" + )[:1] ) ) .annotate( @@ -59,10 +59,7 @@ class WorkspaceDraftIssueViewSet(BaseViewSet): ArrayAgg( "labels__id", distinct=True, - filter=Q( - ~Q(labels__id__isnull=True) - & (Q(draft_label_issue__deleted_at__isnull=True)) - ), + filter=Q(~Q(labels__id__isnull=True) & (Q(draft_label_issue__deleted_at__isnull=True))), ), Value([], output_field=ArrayField(UUIDField())), ), @@ -94,14 +91,10 @@ class WorkspaceDraftIssueViewSet(BaseViewSet): ).distinct() @method_decorator(gzip_page) - @allow_permission( - allowed_roles=[ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST], level="WORKSPACE" - ) + @allow_permission(allowed_roles=[ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST], level="WORKSPACE") def list(self, request, slug): filters = issue_filters(request.query_params, "GET") - issues = ( - self.get_queryset().filter(created_by=request.user).order_by("-created_at") - ) + issues = self.get_queryset().filter(created_by=request.user).order_by("-created_at") issues = issues.filter(**filters) # List Paginate @@ -111,9 +104,7 @@ class WorkspaceDraftIssueViewSet(BaseViewSet): on_results=lambda issues: DraftIssueSerializer(issues, many=True).data, ) - @allow_permission( - allowed_roles=[ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST], level="WORKSPACE" - ) + @allow_permission(allowed_roles=[ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST], level="WORKSPACE") def create(self, request, slug): workspace = Workspace.objects.get(slug=slug) @@ -168,9 +159,7 @@ class WorkspaceDraftIssueViewSet(BaseViewSet): issue = self.get_queryset().filter(pk=pk, created_by=request.user).first() if not issue: - return Response( - {"error": "Issue not found"}, status=status.HTTP_404_NOT_FOUND - ) + return Response({"error": "Issue not found"}, status=status.HTTP_404_NOT_FOUND) project_id = request.data.get("project_id", issue.project_id) @@ -190,9 +179,7 @@ class WorkspaceDraftIssueViewSet(BaseViewSet): return Response(status=status.HTTP_204_NO_CONTENT) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) - @allow_permission( - allowed_roles=[ROLE.ADMIN], creator=True, model=Issue, level="WORKSPACE" - ) + @allow_permission(allowed_roles=[ROLE.ADMIN], creator=True, model=Issue, level="WORKSPACE") def retrieve(self, request, slug, pk=None): issue = self.get_queryset().filter(pk=pk, created_by=request.user).first() @@ -205,9 +192,7 @@ class WorkspaceDraftIssueViewSet(BaseViewSet): serializer = DraftIssueDetailSerializer(issue) return Response(serializer.data, status=status.HTTP_200_OK) - @allow_permission( - allowed_roles=[ROLE.ADMIN], creator=True, model=DraftIssue, level="WORKSPACE" - ) + @allow_permission(allowed_roles=[ROLE.ADMIN], creator=True, model=DraftIssue, level="WORKSPACE") def destroy(self, request, slug, pk=None): draft_issue = DraftIssue.objects.get(workspace__slug=slug, pk=pk) draft_issue.delete() @@ -266,9 +251,7 @@ class WorkspaceDraftIssueViewSet(BaseViewSet): current_instance=json.dumps( { "updated_cycle_issues": None, - "created_cycle_issues": serializers.serialize( - "json", [created_records] - ), + "created_cycle_issues": serializers.serialize("json", [created_records]), } ), epoch=int(timezone.now().timestamp()), diff --git a/apps/api/plane/app/views/workspace/estimate.py b/apps/api/plane/app/views/workspace/estimate.py index 8b0981f9e..8cba3d170 100644 --- a/apps/api/plane/app/views/workspace/estimate.py +++ b/apps/api/plane/app/views/workspace/estimate.py @@ -16,9 +16,9 @@ class WorkspaceEstimatesEndpoint(BaseAPIView): @cache_response(60 * 60 * 2) def get(self, request, slug): - estimate_ids = Project.objects.filter( - workspace__slug=slug, estimate__isnull=False - ).values_list("estimate_id", flat=True) + estimate_ids = Project.objects.filter(workspace__slug=slug, estimate__isnull=False).values_list( + "estimate_id", flat=True + ) estimates = ( Estimate.objects.filter(pk__in=estimate_ids, workspace__slug=slug) .prefetch_related("points") diff --git a/apps/api/plane/app/views/workspace/favorite.py b/apps/api/plane/app/views/workspace/favorite.py index ee126fa5b..8a8bfed6c 100644 --- a/apps/api/plane/app/views/workspace/favorite.py +++ b/apps/api/plane/app/views/workspace/favorite.py @@ -19,9 +19,7 @@ class WorkspaceFavoriteEndpoint(BaseAPIView): @allow_permission(allowed_roles=[ROLE.ADMIN, ROLE.MEMBER], level="WORKSPACE") def get(self, request, slug): # the second filter is to check if the user is a member of the project - favorites = UserFavorite.objects.filter( - user=request.user, workspace__slug=slug, parent__isnull=True - ).filter( + favorites = UserFavorite.objects.filter(user=request.user, workspace__slug=slug, parent__isnull=True).filter( Q(project__isnull=True) & ~Q(entity_type="page") | ( Q(project__isnull=False) @@ -62,15 +60,11 @@ class WorkspaceFavoriteEndpoint(BaseAPIView): return Response(serializer.data, status=status.HTTP_200_OK) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) except IntegrityError: - return Response( - {"error": "Favorite already exists"}, status=status.HTTP_400_BAD_REQUEST - ) + return Response({"error": "Favorite already exists"}, status=status.HTTP_400_BAD_REQUEST) @allow_permission(allowed_roles=[ROLE.ADMIN, ROLE.MEMBER], level="WORKSPACE") def patch(self, request, slug, favorite_id): - favorite = UserFavorite.objects.get( - user=request.user, workspace__slug=slug, pk=favorite_id - ) + favorite = UserFavorite.objects.get(user=request.user, workspace__slug=slug, pk=favorite_id) serializer = UserFavoriteSerializer(favorite, data=request.data, partial=True) if serializer.is_valid(): serializer.save() @@ -79,9 +73,7 @@ class WorkspaceFavoriteEndpoint(BaseAPIView): @allow_permission(allowed_roles=[ROLE.ADMIN, ROLE.MEMBER], level="WORKSPACE") def delete(self, request, slug, favorite_id): - favorite = UserFavorite.objects.get( - user=request.user, workspace__slug=slug, pk=favorite_id - ) + favorite = UserFavorite.objects.get(user=request.user, workspace__slug=slug, pk=favorite_id) favorite.delete(soft=False) return Response(status=status.HTTP_204_NO_CONTENT) @@ -89,9 +81,7 @@ class WorkspaceFavoriteEndpoint(BaseAPIView): class WorkspaceFavoriteGroupEndpoint(BaseAPIView): @allow_permission(allowed_roles=[ROLE.ADMIN, ROLE.MEMBER], level="WORKSPACE") def get(self, request, slug, favorite_id): - favorites = UserFavorite.objects.filter( - user=request.user, workspace__slug=slug, parent_id=favorite_id - ).filter( + favorites = UserFavorite.objects.filter(user=request.user, workspace__slug=slug, parent_id=favorite_id).filter( Q(project__isnull=True) | ( Q(project__isnull=False) diff --git a/apps/api/plane/app/views/workspace/home.py b/apps/api/plane/app/views/workspace/home.py index 5ee9b0a39..731164eb1 100644 --- a/apps/api/plane/app/views/workspace/home.py +++ b/apps/api/plane/app/views/workspace/home.py @@ -20,9 +20,7 @@ class WorkspaceHomePreferenceViewSet(BaseAPIView): def get(self, request, slug): workspace = Workspace.objects.get(slug=slug) - get_preference = WorkspaceHomePreference.objects.filter( - user=request.user, workspace_id=workspace.id - ) + get_preference = WorkspaceHomePreference.objects.filter(user=request.user, workspace_id=workspace.id) create_preference_keys = [] @@ -55,9 +53,7 @@ class WorkspaceHomePreferenceViewSet(BaseAPIView): ) sort_order_counter += 1 - preference = WorkspaceHomePreference.objects.filter( - user=request.user, workspace_id=workspace.id - ) + preference = WorkspaceHomePreference.objects.filter(user=request.user, workspace_id=workspace.id) return Response( preference.values("key", "is_enabled", "config", "sort_order"), @@ -66,20 +62,14 @@ class WorkspaceHomePreferenceViewSet(BaseAPIView): @allow_permission([ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST], level="WORKSPACE") def patch(self, request, slug, key): - preference = WorkspaceHomePreference.objects.filter( - key=key, workspace__slug=slug, user=request.user - ).first() + preference = WorkspaceHomePreference.objects.filter(key=key, workspace__slug=slug, user=request.user).first() if preference: - serializer = WorkspaceHomePreferenceSerializer( - preference, data=request.data, partial=True - ) + serializer = WorkspaceHomePreferenceSerializer(preference, data=request.data, partial=True) if serializer.is_valid(): serializer.save() return Response(serializer.data, status=status.HTTP_200_OK) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) - return Response( - {"detail": "Preference not found"}, status=status.HTTP_400_BAD_REQUEST - ) + return Response({"detail": "Preference not found"}, status=status.HTTP_400_BAD_REQUEST) diff --git a/apps/api/plane/app/views/workspace/invite.py b/apps/api/plane/app/views/workspace/invite.py index 84ef7c361..48bcf7eba 100644 --- a/apps/api/plane/app/views/workspace/invite.py +++ b/apps/api/plane/app/views/workspace/invite.py @@ -50,23 +50,13 @@ class WorkspaceInvitationsViewset(BaseViewSet): emails = request.data.get("emails", []) # Check if email is provided if not emails: - return Response( - {"error": "Emails are required"}, status=status.HTTP_400_BAD_REQUEST - ) + return Response({"error": "Emails are required"}, status=status.HTTP_400_BAD_REQUEST) # check for role level of the requesting user - requesting_user = WorkspaceMember.objects.get( - workspace__slug=slug, member=request.user, is_active=True - ) + requesting_user = WorkspaceMember.objects.get(workspace__slug=slug, member=request.user, is_active=True) # Check if any invited user has an higher role - if len( - [ - email - for email in emails - if int(email.get("role", 5)) > requesting_user.role - ] - ): + if len([email for email in emails if int(email.get("role", 5)) > requesting_user.role]): return Response( {"error": "You cannot invite a user with higher role"}, status=status.HTTP_400_BAD_REQUEST, @@ -86,9 +76,7 @@ class WorkspaceInvitationsViewset(BaseViewSet): return Response( { "error": "Some users are already member of workspace", - "workspace_users": WorkSpaceMemberSerializer( - workspace_members, many=True - ).data, + "workspace_users": WorkSpaceMemberSerializer(workspace_members, many=True).data, }, status=status.HTTP_400_BAD_REQUEST, ) @@ -113,7 +101,7 @@ class WorkspaceInvitationsViewset(BaseViewSet): except ValidationError: return Response( { - "error": f"Invalid email - {email} provided a valid email address is required to send the invite" + "error": f"Invalid email - {email} provided a valid email address is required to send the invite" # noqa: E501 }, status=status.HTTP_400_BAD_REQUEST, ) @@ -134,14 +122,10 @@ class WorkspaceInvitationsViewset(BaseViewSet): request.user.email, ) - return Response( - {"message": "Emails sent successfully"}, status=status.HTTP_200_OK - ) + return Response({"message": "Emails sent successfully"}, status=status.HTTP_200_OK) def destroy(self, request, slug, pk): - workspace_member_invite = WorkspaceMemberInvite.objects.get( - pk=pk, workspace__slug=slug - ) + workspace_member_invite = WorkspaceMemberInvite.objects.get(pk=pk, workspace__slug=slug) workspace_member_invite.delete() return Response(status=status.HTTP_204_NO_CONTENT) @@ -160,9 +144,7 @@ class WorkspaceJoinEndpoint(BaseAPIView): ) @invalidate_cache(path="/api/users/me/settings/", multiple=True) def post(self, request, slug, pk): - workspace_invite = WorkspaceMemberInvite.objects.get( - pk=pk, workspace__slug=slug - ) + workspace_invite = WorkspaceMemberInvite.objects.get(pk=pk, workspace__slug=slug) email = request.data.get("email", "") @@ -235,9 +217,7 @@ class WorkspaceJoinEndpoint(BaseAPIView): ) def get(self, request, slug, pk): - workspace_invitation = WorkspaceMemberInvite.objects.get( - workspace__slug=slug, pk=pk - ) + workspace_invitation = WorkspaceMemberInvite.objects.get(workspace__slug=slug, pk=pk) serializer = WorkSpaceMemberInviteSerializer(workspace_invitation) return Response(serializer.data, status=status.HTTP_200_OK) @@ -248,10 +228,7 @@ class UserWorkspaceInvitationsViewSet(BaseViewSet): def get_queryset(self): return self.filter_queryset( - super() - .get_queryset() - .filter(email=self.request.user.email) - .select_related("workspace") + super().get_queryset().filter(email=self.request.user.email).select_related("workspace") ) @invalidate_cache(path="/api/workspaces/", user=False) @@ -271,9 +248,9 @@ class UserWorkspaceInvitationsViewSet(BaseViewSet): multiple=True, ) # Update the WorkspaceMember for this specific invitation - WorkspaceMember.objects.filter( - workspace_id=invitation.workspace_id, member=request.user - ).update(is_active=True, role=invitation.role) + WorkspaceMember.objects.filter(workspace_id=invitation.workspace_id, member=request.user).update( + is_active=True, role=invitation.role + ) # Bulk create the user for all the workspaces WorkspaceMember.objects.bulk_create( diff --git a/apps/api/plane/app/views/workspace/member.py b/apps/api/plane/app/views/workspace/member.py index 84985cec3..d81a647f6 100644 --- a/apps/api/plane/app/views/workspace/member.py +++ b/apps/api/plane/app/views/workspace/member.py @@ -38,24 +38,16 @@ class WorkSpaceMemberViewSet(BaseViewSet): .select_related("member", "member__avatar_asset") ) - @allow_permission( - allowed_roles=[ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST], level="WORKSPACE" - ) + @allow_permission(allowed_roles=[ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST], level="WORKSPACE") def list(self, request, slug): - workspace_member = WorkspaceMember.objects.get( - member=request.user, workspace__slug=slug, is_active=True - ) + workspace_member = WorkspaceMember.objects.get(member=request.user, workspace__slug=slug, is_active=True) # Get all active workspace members workspace_members = self.get_queryset() if workspace_member.role > 5: - serializer = WorkspaceMemberAdminSerializer( - workspace_members, fields=("id", "member", "role"), many=True - ) + serializer = WorkspaceMemberAdminSerializer(workspace_members, fields=("id", "member", "role"), many=True) else: - serializer = WorkSpaceMemberSerializer( - workspace_members, fields=("id", "member", "role"), many=True - ) + serializer = WorkSpaceMemberSerializer(workspace_members, fields=("id", "member", "role"), many=True) return Response(serializer.data, status=status.HTTP_200_OK) @allow_permission(allowed_roles=[ROLE.ADMIN], level="WORKSPACE") @@ -71,13 +63,9 @@ class WorkSpaceMemberViewSet(BaseViewSet): # If a user is moved to a guest role he can't have any other role in projects if "role" in request.data and int(request.data.get("role")) == 5: - ProjectMember.objects.filter( - workspace__slug=slug, member_id=workspace_member.member_id - ).update(role=5) + ProjectMember.objects.filter(workspace__slug=slug, member_id=workspace_member.member_id).update(role=5) - serializer = WorkSpaceMemberSerializer( - workspace_member, data=request.data, partial=True - ) + serializer = WorkSpaceMemberSerializer(workspace_member, data=request.data, partial=True) if serializer.is_valid(): serializer.save() @@ -98,9 +86,7 @@ class WorkSpaceMemberViewSet(BaseViewSet): if str(workspace_member.id) == str(requesting_workspace_member.id): return Response( - { - "error": "You cannot remove yourself from the workspace. Please use leave workspace" - }, + {"error": "You cannot remove yourself from the workspace. Please use leave workspace"}, status=status.HTTP_400_BAD_REQUEST, ) @@ -126,7 +112,7 @@ class WorkSpaceMemberViewSet(BaseViewSet): ): return Response( { - "error": "User is a part of some projects where they are the only admin, they should either leave that project or promote another user to admin." + "error": "User is a part of some projects where they are the only admin, they should either leave that project or promote another user to admin." # noqa: E501 }, status=status.HTTP_400_BAD_REQUEST, ) @@ -148,25 +134,18 @@ class WorkSpaceMemberViewSet(BaseViewSet): ) @invalidate_cache(path="/api/users/me/settings/") @invalidate_cache(path="api/users/me/workspaces/", user=False, multiple=True) - @allow_permission( - allowed_roles=[ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST], level="WORKSPACE" - ) + @allow_permission(allowed_roles=[ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST], level="WORKSPACE") def leave(self, request, slug): - workspace_member = WorkspaceMember.objects.get( - workspace__slug=slug, member=request.user, is_active=True - ) + workspace_member = WorkspaceMember.objects.get(workspace__slug=slug, member=request.user, is_active=True) # Check if the leaving user is the only admin of the workspace if ( workspace_member.role == 20 - and not WorkspaceMember.objects.filter( - workspace__slug=slug, role=20, is_active=True - ).count() - > 1 + and not WorkspaceMember.objects.filter(workspace__slug=slug, role=20, is_active=True).count() > 1 ): return Response( { - "error": "You cannot leave the workspace as you are the only admin of the workspace you will have to either delete the workspace or promote another user to admin." + "error": "You cannot leave the workspace as you are the only admin of the workspace you will have to either delete the workspace or promote another user to admin." # noqa: E501 }, status=status.HTTP_400_BAD_REQUEST, ) @@ -187,7 +166,7 @@ class WorkSpaceMemberViewSet(BaseViewSet): ): return Response( { - "error": "You are a part of some projects where you are the only admin, you should either leave the project or promote another user to admin." + "error": "You are a part of some projects where you are the only admin, you should either leave the project or promote another user to admin." # noqa: E501 }, status=status.HTTP_400_BAD_REQUEST, ) @@ -205,9 +184,7 @@ class WorkSpaceMemberViewSet(BaseViewSet): class WorkspaceMemberUserViewsEndpoint(BaseAPIView): def post(self, request, slug): - workspace_member = WorkspaceMember.objects.get( - workspace__slug=slug, member=request.user, is_active=True - ) + workspace_member = WorkspaceMember.objects.get(workspace__slug=slug, member=request.user, is_active=True) workspace_member.view_props = request.data.get("view_props", {}) workspace_member.save() @@ -219,23 +196,15 @@ class WorkspaceMemberUserEndpoint(BaseAPIView): def get(self, request, slug): draft_issue_count = ( - DraftIssue.objects.filter( - created_by=request.user, workspace_id=OuterRef("workspace_id") - ) + DraftIssue.objects.filter(created_by=request.user, workspace_id=OuterRef("workspace_id")) .values("workspace_id") .annotate(count=Count("id")) .values("count") ) workspace_member = ( - WorkspaceMember.objects.filter( - member=request.user, workspace__slug=slug, is_active=True - ) - .annotate( - draft_issue_count=Coalesce( - Subquery(draft_issue_count, output_field=IntegerField()), 0 - ) - ) + WorkspaceMember.objects.filter(member=request.user, workspace__slug=slug, is_active=True) + .annotate(draft_issue_count=Coalesce(Subquery(draft_issue_count, output_field=IntegerField()), 0)) .first() ) serializer = WorkspaceMemberMeSerializer(workspace_member) diff --git a/apps/api/plane/app/views/workspace/quick_link.py b/apps/api/plane/app/views/workspace/quick_link.py index 104ca00d2..82c104573 100644 --- a/apps/api/plane/app/views/workspace/quick_link.py +++ b/apps/api/plane/app/views/workspace/quick_link.py @@ -28,48 +28,34 @@ class QuickLinkViewSet(BaseViewSet): @allow_permission([ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST], level="WORKSPACE") def partial_update(self, request, slug, pk): - quick_link = WorkspaceUserLink.objects.filter( - pk=pk, workspace__slug=slug, owner=request.user - ).first() + quick_link = WorkspaceUserLink.objects.filter(pk=pk, workspace__slug=slug, owner=request.user).first() if quick_link: - serializer = WorkspaceUserLinkSerializer( - quick_link, data=request.data, partial=True - ) + serializer = WorkspaceUserLinkSerializer(quick_link, data=request.data, partial=True) if serializer.is_valid(): serializer.save() return Response(serializer.data, status=status.HTTP_200_OK) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) - return Response( - {"detail": "Quick link not found."}, status=status.HTTP_404_NOT_FOUND - ) + return Response({"detail": "Quick link not found."}, status=status.HTTP_404_NOT_FOUND) @allow_permission([ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST], level="WORKSPACE") def retrieve(self, request, slug, pk): try: - quick_link = WorkspaceUserLink.objects.get( - pk=pk, workspace__slug=slug, owner=request.user - ) + quick_link = WorkspaceUserLink.objects.get(pk=pk, workspace__slug=slug, owner=request.user) serializer = WorkspaceUserLinkSerializer(quick_link) return Response(serializer.data, status=status.HTTP_200_OK) except WorkspaceUserLink.DoesNotExist: - return Response( - {"error": "Quick link not found."}, status=status.HTTP_404_NOT_FOUND - ) + return Response({"error": "Quick link not found."}, status=status.HTTP_404_NOT_FOUND) @allow_permission([ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST], level="WORKSPACE") def destroy(self, request, slug, pk): - quick_link = WorkspaceUserLink.objects.get( - pk=pk, workspace__slug=slug, owner=request.user - ) + quick_link = WorkspaceUserLink.objects.get(pk=pk, workspace__slug=slug, owner=request.user) quick_link.delete() return Response(status=status.HTTP_204_NO_CONTENT) @allow_permission([ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST], level="WORKSPACE") def list(self, request, slug): - quick_links = WorkspaceUserLink.objects.filter( - workspace__slug=slug, owner=request.user - ) + quick_links = WorkspaceUserLink.objects.filter(workspace__slug=slug, owner=request.user) serializer = WorkspaceUserLinkSerializer(quick_links, many=True) return Response(serializer.data, status=status.HTTP_200_OK) diff --git a/apps/api/plane/app/views/workspace/recent_visit.py b/apps/api/plane/app/views/workspace/recent_visit.py index e1c50c8b6..0d9c1ef9b 100644 --- a/apps/api/plane/app/views/workspace/recent_visit.py +++ b/apps/api/plane/app/views/workspace/recent_visit.py @@ -19,18 +19,14 @@ class UserRecentVisitViewSet(BaseViewSet): @allow_permission([ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST], level="WORKSPACE") def list(self, request, slug): - user_recent_visits = UserRecentVisit.objects.filter( - workspace__slug=slug, user=request.user - ) + user_recent_visits = UserRecentVisit.objects.filter(workspace__slug=slug, user=request.user) entity_name = request.query_params.get("entity_name") if entity_name: user_recent_visits = user_recent_visits.filter(entity_name=entity_name) - user_recent_visits = user_recent_visits.filter( - entity_name__in=["issue", "page", "project"] - ) + user_recent_visits = user_recent_visits.filter(entity_name__in=["issue", "page", "project"]) serializer = WorkspaceRecentVisitSerializer(user_recent_visits[:20], many=True) return Response(serializer.data, status=status.HTTP_200_OK) diff --git a/apps/api/plane/app/views/workspace/sticky.py b/apps/api/plane/app/views/workspace/sticky.py index 8b9654716..8ab6c5c98 100644 --- a/apps/api/plane/app/views/workspace/sticky.py +++ b/apps/api/plane/app/views/workspace/sticky.py @@ -24,9 +24,7 @@ class WorkspaceStickyViewSet(BaseViewSet): .distinct() ) - @allow_permission( - allowed_roles=[ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST], level="WORKSPACE" - ) + @allow_permission(allowed_roles=[ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST], level="WORKSPACE") def create(self, request, slug): workspace = Workspace.objects.get(slug=slug) serializer = StickySerializer(data=request.data) @@ -35,9 +33,7 @@ class WorkspaceStickyViewSet(BaseViewSet): return Response(serializer.data, status=status.HTTP_201_CREATED) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) - @allow_permission( - allowed_roles=[ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST], level="WORKSPACE" - ) + @allow_permission(allowed_roles=[ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST], level="WORKSPACE") def list(self, request, slug): query = request.query_params.get("query", False) stickies = self.get_queryset().order_by("-sort_order") diff --git a/apps/api/plane/app/views/workspace/user.py b/apps/api/plane/app/views/workspace/user.py index 72233e151..0d4f152ee 100644 --- a/apps/api/plane/app/views/workspace/user.py +++ b/apps/api/plane/app/views/workspace/user.py @@ -101,9 +101,7 @@ class WorkspaceUserProfileIssuesEndpoint(BaseAPIView): return ( issues.annotate( cycle_id=Subquery( - CycleIssue.objects.filter( - issue=OuterRef("id"), deleted_at__isnull=True - ).values("cycle_id")[:1] + CycleIssue.objects.filter(issue=OuterRef("id"), deleted_at__isnull=True).values("cycle_id")[:1] ) ) .annotate( @@ -136,9 +134,7 @@ class WorkspaceUserProfileIssuesEndpoint(BaseAPIView): order_by_param = request.GET.get("order_by", "-created_at") issue_queryset = Issue.issue_objects.filter( id__in=Issue.issue_objects.filter( - Q(assignees__in=[user_id]) - | Q(created_by_id=user_id) - | Q(issue_subscribers__subscriber_id=user_id), + Q(assignees__in=[user_id]) | Q(created_by_id=user_id) | Q(issue_subscribers__subscriber_id=user_id), workspace__slug=slug, ).values_list("id", flat=True), workspace__slug=slug, @@ -168,9 +164,7 @@ class WorkspaceUserProfileIssuesEndpoint(BaseAPIView): sub_group_by = request.GET.get("sub_group_by", False) # issue queryset - issue_queryset = issue_queryset_grouper( - queryset=issue_queryset, group_by=group_by, sub_group_by=sub_group_by - ) + issue_queryset = issue_queryset_grouper(queryset=issue_queryset, group_by=group_by, sub_group_by=sub_group_by) if group_by: if sub_group_by: @@ -247,9 +241,7 @@ class WorkspaceUserProfileIssuesEndpoint(BaseAPIView): request=request, queryset=issue_queryset, total_count_queryset=total_issue_queryset, - on_results=lambda issues: issue_on_results( - group_by=group_by, issues=issues, sub_group_by=sub_group_by - ), + on_results=lambda issues: issue_on_results(group_by=group_by, issues=issues, sub_group_by=sub_group_by), ) @@ -257,19 +249,11 @@ class WorkspaceUserPropertiesEndpoint(BaseAPIView): permission_classes = [WorkspaceViewerPermission] def patch(self, request, slug): - workspace_properties = WorkspaceUserProperties.objects.get( - user=request.user, workspace__slug=slug - ) + workspace_properties = WorkspaceUserProperties.objects.get(user=request.user, workspace__slug=slug) - workspace_properties.filters = request.data.get( - "filters", workspace_properties.filters - ) - workspace_properties.rich_filters = request.data.get( - "rich_filters", workspace_properties.rich_filters - ) - workspace_properties.display_filters = request.data.get( - "display_filters", workspace_properties.display_filters - ) + workspace_properties.filters = request.data.get("filters", workspace_properties.filters) + workspace_properties.rich_filters = request.data.get("rich_filters", workspace_properties.rich_filters) + workspace_properties.display_filters = request.data.get("display_filters", workspace_properties.display_filters) workspace_properties.display_properties = request.data.get( "display_properties", workspace_properties.display_properties ) @@ -398,9 +382,7 @@ class WorkspaceUserActivityEndpoint(BaseAPIView): order_by=request.GET.get("order_by", "-created_at"), request=request, queryset=queryset, - on_results=lambda issue_activities: IssueActivitySerializer( - issue_activities, many=True - ).data, + on_results=lambda issue_activities: IssueActivitySerializer(issue_activities, many=True).data, ) @@ -410,10 +392,7 @@ class WorkspaceUserProfileStatsEndpoint(BaseAPIView): state_distribution = ( Issue.issue_objects.filter( - ( - Q(assignees__in=[user_id]) - & Q(issue_assignee__deleted_at__isnull=True) - ), + (Q(assignees__in=[user_id]) & Q(issue_assignee__deleted_at__isnull=True)), workspace__slug=slug, project__project_projectmember__member=request.user, project__project_projectmember__is_active=True, @@ -429,10 +408,7 @@ class WorkspaceUserProfileStatsEndpoint(BaseAPIView): priority_distribution = ( Issue.issue_objects.filter( - ( - Q(assignees__in=[user_id]) - & Q(issue_assignee__deleted_at__isnull=True) - ), + (Q(assignees__in=[user_id]) & Q(issue_assignee__deleted_at__isnull=True)), workspace__slug=slug, project__project_projectmember__member=request.user, project__project_projectmember__is_active=True, @@ -443,10 +419,7 @@ class WorkspaceUserProfileStatsEndpoint(BaseAPIView): .filter(priority_count__gte=1) .annotate( priority_order=Case( - *[ - When(priority=p, then=Value(i)) - for i, p in enumerate(priority_order) - ], + *[When(priority=p, then=Value(i)) for i, p in enumerate(priority_order)], default=Value(len(priority_order)), output_field=IntegerField(), ) @@ -467,10 +440,7 @@ class WorkspaceUserProfileStatsEndpoint(BaseAPIView): assigned_issues_count = ( Issue.issue_objects.filter( - ( - Q(assignees__in=[user_id]) - & Q(issue_assignee__deleted_at__isnull=True) - ), + (Q(assignees__in=[user_id]) & Q(issue_assignee__deleted_at__isnull=True)), workspace__slug=slug, project__project_projectmember__member=request.user, project__project_projectmember__is_active=True, @@ -482,10 +452,7 @@ class WorkspaceUserProfileStatsEndpoint(BaseAPIView): pending_issues_count = ( Issue.issue_objects.filter( ~Q(state__group__in=["completed", "cancelled"]), - ( - Q(assignees__in=[user_id]) - & Q(issue_assignee__deleted_at__isnull=True) - ), + (Q(assignees__in=[user_id]) & Q(issue_assignee__deleted_at__isnull=True)), workspace__slug=slug, project__project_projectmember__member=request.user, project__project_projectmember__is_active=True, @@ -496,10 +463,7 @@ class WorkspaceUserProfileStatsEndpoint(BaseAPIView): completed_issues_count = ( Issue.issue_objects.filter( - ( - Q(assignees__in=[user_id]) - & Q(issue_assignee__deleted_at__isnull=True) - ), + (Q(assignees__in=[user_id]) & Q(issue_assignee__deleted_at__isnull=True)), workspace__slug=slug, state__group="completed", project__project_projectmember__member=request.user, diff --git a/apps/api/plane/app/views/workspace/user_preference.py b/apps/api/plane/app/views/workspace/user_preference.py index 8bcf6b309..30c6ab97a 100644 --- a/apps/api/plane/app/views/workspace/user_preference.py +++ b/apps/api/plane/app/views/workspace/user_preference.py @@ -22,9 +22,7 @@ class WorkspaceUserPreferenceViewSet(BaseAPIView): def get(self, request, slug): workspace = Workspace.objects.get(slug=slug) - get_preference = WorkspaceUserPreference.objects.filter( - user=request.user, workspace_id=workspace.id - ) + get_preference = WorkspaceUserPreference.objects.filter(user=request.user, workspace_id=workspace.id) create_preference_keys = [] @@ -49,9 +47,7 @@ class WorkspaceUserPreferenceViewSet(BaseAPIView): ) preferences = ( - WorkspaceUserPreference.objects.filter( - user=request.user, workspace_id=workspace.id - ) + WorkspaceUserPreference.objects.filter(user=request.user, workspace_id=workspace.id) .order_by("sort_order") .values("key", "is_pinned", "sort_order") ) @@ -70,20 +66,14 @@ class WorkspaceUserPreferenceViewSet(BaseAPIView): @allow_permission([ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST], level="WORKSPACE") def patch(self, request, slug, key): - preference = WorkspaceUserPreference.objects.filter( - key=key, workspace__slug=slug, user=request.user - ).first() + preference = WorkspaceUserPreference.objects.filter(key=key, workspace__slug=slug, user=request.user).first() if preference: - serializer = WorkspaceUserPreferenceSerializer( - preference, data=request.data, partial=True - ) + serializer = WorkspaceUserPreferenceSerializer(preference, data=request.data, partial=True) if serializer.is_valid(): serializer.save() return Response(serializer.data, status=status.HTTP_200_OK) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) - return Response( - {"detail": "Preference not found"}, status=status.HTTP_404_NOT_FOUND - ) + return Response({"detail": "Preference not found"}, status=status.HTTP_404_NOT_FOUND) diff --git a/apps/api/plane/authentication/adapter/base.py b/apps/api/plane/authentication/adapter/base.py index b28735120..bbb50eb76 100644 --- a/apps/api/plane/authentication/adapter/base.py +++ b/apps/api/plane/authentication/adapter/base.py @@ -91,10 +91,7 @@ class Adapter: ) # Check if sign up is disabled and invite is present or not - if ( - ENABLE_SIGNUP == "0" - and not WorkspaceMemberInvite.objects.filter(email=email).exists() - ): + if ENABLE_SIGNUP == "0" and not WorkspaceMemberInvite.objects.filter(email=email).exists(): # Raise exception raise AuthenticationException( error_code=AUTHENTICATION_ERROR_CODES["SIGNUP_DISABLED"], diff --git a/apps/api/plane/authentication/adapter/oauth.py b/apps/api/plane/authentication/adapter/oauth.py index e89383837..ed1201097 100644 --- a/apps/api/plane/authentication/adapter/oauth.py +++ b/apps/api/plane/authentication/adapter/oauth.py @@ -73,9 +73,7 @@ class OauthAdapter(Adapter): return response.json() except requests.RequestException: code = self.authentication_error_code() - raise AuthenticationException( - error_code=AUTHENTICATION_ERROR_CODES[code], error_message=str(code) - ) + raise AuthenticationException(error_code=AUTHENTICATION_ERROR_CODES[code], error_message=str(code)) def get_user_response(self): try: @@ -85,9 +83,7 @@ class OauthAdapter(Adapter): return response.json() except requests.RequestException: code = self.authentication_error_code() - raise AuthenticationException( - error_code=AUTHENTICATION_ERROR_CODES[code], error_message=str(code) - ) + raise AuthenticationException(error_code=AUTHENTICATION_ERROR_CODES[code], error_message=str(code)) def set_user_data(self, data): self.user_data = data @@ -104,12 +100,8 @@ class OauthAdapter(Adapter): if account: account.access_token = self.token_data.get("access_token") account.refresh_token = self.token_data.get("refresh_token", None) - account.access_token_expired_at = self.token_data.get( - "access_token_expired_at" - ) - account.refresh_token_expired_at = self.token_data.get( - "refresh_token_expired_at" - ) + account.access_token_expired_at = self.token_data.get("access_token_expired_at") + account.refresh_token_expired_at = self.token_data.get("refresh_token_expired_at") account.last_connected_at = timezone.now() account.id_token = self.token_data.get("id_token", "") account.save() @@ -118,17 +110,11 @@ class OauthAdapter(Adapter): Account.objects.create( user=user, provider=self.provider, - provider_account_id=self.user_data.get("user", {}).get( - "provider_id" - ), + provider_account_id=self.user_data.get("user", {}).get("provider_id"), access_token=self.token_data.get("access_token"), refresh_token=self.token_data.get("refresh_token", None), - access_token_expired_at=self.token_data.get( - "access_token_expired_at" - ), - refresh_token_expired_at=self.token_data.get( - "refresh_token_expired_at" - ), + access_token_expired_at=self.token_data.get("access_token_expired_at"), + refresh_token_expired_at=self.token_data.get("refresh_token_expired_at"), last_connected_at=timezone.now(), id_token=self.token_data.get("id_token", ""), ) diff --git a/apps/api/plane/authentication/middleware/session.py b/apps/api/plane/authentication/middleware/session.py index 822c88316..c367a15d3 100644 --- a/apps/api/plane/authentication/middleware/session.py +++ b/apps/api/plane/authentication/middleware/session.py @@ -37,11 +37,7 @@ class SessionMiddleware(MiddlewareMixin): # First check if we need to delete this cookie. # The session should be deleted only if the session is entirely empty. is_admin_path = "instances" in request.path - cookie_name = ( - settings.ADMIN_SESSION_COOKIE_NAME - if is_admin_path - else settings.SESSION_COOKIE_NAME - ) + cookie_name = settings.ADMIN_SESSION_COOKIE_NAME if is_admin_path else settings.SESSION_COOKIE_NAME if cookie_name in request.COOKIES and empty: response.delete_cookie( diff --git a/apps/api/plane/authentication/provider/credentials/email.py b/apps/api/plane/authentication/provider/credentials/email.py index 4b8ae0595..c3d19a80e 100644 --- a/apps/api/plane/authentication/provider/credentials/email.py +++ b/apps/api/plane/authentication/provider/credentials/email.py @@ -31,9 +31,7 @@ class EmailProvider(CredentialAdapter): if ENABLE_EMAIL_PASSWORD == "0": raise AuthenticationException( - error_code=AUTHENTICATION_ERROR_CODES[ - "EMAIL_PASSWORD_AUTHENTICATION_DISABLED" - ], + error_code=AUTHENTICATION_ERROR_CODES["EMAIL_PASSWORD_AUTHENTICATION_DISABLED"], error_message="EMAIL_PASSWORD_AUTHENTICATION_DISABLED", ) @@ -74,16 +72,10 @@ class EmailProvider(CredentialAdapter): if not user.check_password(self.code): raise AuthenticationException( error_message=( - "AUTHENTICATION_FAILED_SIGN_UP" - if self.is_signup - else "AUTHENTICATION_FAILED_SIGN_IN" + "AUTHENTICATION_FAILED_SIGN_UP" if self.is_signup else "AUTHENTICATION_FAILED_SIGN_IN" ), error_code=AUTHENTICATION_ERROR_CODES[ - ( - "AUTHENTICATION_FAILED_SIGN_UP" - if self.is_signup - else "AUTHENTICATION_FAILED_SIGN_IN" - ) + ("AUTHENTICATION_FAILED_SIGN_UP" if self.is_signup else "AUTHENTICATION_FAILED_SIGN_IN") ], payload={"email": self.key}, ) diff --git a/apps/api/plane/authentication/provider/credentials/magic_code.py b/apps/api/plane/authentication/provider/credentials/magic_code.py index 4fe8924f3..3f03572a4 100644 --- a/apps/api/plane/authentication/provider/credentials/magic_code.py +++ b/apps/api/plane/authentication/provider/credentials/magic_code.py @@ -72,17 +72,13 @@ class MagicCodeProvider(CredentialAdapter): email = str(self.key).replace("magic_", "", 1) if User.objects.filter(email=email).exists(): raise AuthenticationException( - error_code=AUTHENTICATION_ERROR_CODES[ - "EMAIL_CODE_ATTEMPT_EXHAUSTED_SIGN_IN" - ], + error_code=AUTHENTICATION_ERROR_CODES["EMAIL_CODE_ATTEMPT_EXHAUSTED_SIGN_IN"], error_message="EMAIL_CODE_ATTEMPT_EXHAUSTED_SIGN_IN", payload={"email": str(email)}, ) else: raise AuthenticationException( - error_code=AUTHENTICATION_ERROR_CODES[ - "EMAIL_CODE_ATTEMPT_EXHAUSTED_SIGN_UP" - ], + error_code=AUTHENTICATION_ERROR_CODES["EMAIL_CODE_ATTEMPT_EXHAUSTED_SIGN_UP"], error_message="EMAIL_CODE_ATTEMPT_EXHAUSTED_SIGN_UP", payload={"email": self.key}, ) @@ -128,17 +124,13 @@ class MagicCodeProvider(CredentialAdapter): email = str(self.key).replace("magic_", "", 1) if User.objects.filter(email=email).exists(): raise AuthenticationException( - error_code=AUTHENTICATION_ERROR_CODES[ - "INVALID_MAGIC_CODE_SIGN_IN" - ], + error_code=AUTHENTICATION_ERROR_CODES["INVALID_MAGIC_CODE_SIGN_IN"], error_message="INVALID_MAGIC_CODE_SIGN_IN", payload={"email": str(email)}, ) else: raise AuthenticationException( - error_code=AUTHENTICATION_ERROR_CODES[ - "INVALID_MAGIC_CODE_SIGN_UP" - ], + error_code=AUTHENTICATION_ERROR_CODES["INVALID_MAGIC_CODE_SIGN_UP"], error_message="INVALID_MAGIC_CODE_SIGN_UP", payload={"email": str(email)}, ) diff --git a/apps/api/plane/authentication/provider/oauth/github.py b/apps/api/plane/authentication/provider/oauth/github.py index ecf7ed183..54c48018e 100644 --- a/apps/api/plane/authentication/provider/oauth/github.py +++ b/apps/api/plane/authentication/provider/oauth/github.py @@ -26,23 +26,21 @@ class GitHubOAuthProvider(OauthAdapter): organization_scope = "read:org" def __init__(self, request, code=None, state=None, callback=None): - GITHUB_CLIENT_ID, GITHUB_CLIENT_SECRET, GITHUB_ORGANIZATION_ID = ( - get_configuration_value( - [ - { - "key": "GITHUB_CLIENT_ID", - "default": os.environ.get("GITHUB_CLIENT_ID"), - }, - { - "key": "GITHUB_CLIENT_SECRET", - "default": os.environ.get("GITHUB_CLIENT_SECRET"), - }, - { - "key": "GITHUB_ORGANIZATION_ID", - "default": os.environ.get("GITHUB_ORGANIZATION_ID"), - }, - ] - ) + GITHUB_CLIENT_ID, GITHUB_CLIENT_SECRET, GITHUB_ORGANIZATION_ID = get_configuration_value( + [ + { + "key": "GITHUB_CLIENT_ID", + "default": os.environ.get("GITHUB_CLIENT_ID"), + }, + { + "key": "GITHUB_CLIENT_SECRET", + "default": os.environ.get("GITHUB_CLIENT_SECRET"), + }, + { + "key": "GITHUB_ORGANIZATION_ID", + "default": os.environ.get("GITHUB_ORGANIZATION_ID"), + }, + ] ) if not (GITHUB_CLIENT_ID and GITHUB_CLIENT_SECRET): @@ -87,24 +85,18 @@ class GitHubOAuthProvider(OauthAdapter): "code": self.code, "redirect_uri": self.redirect_uri, } - token_response = self.get_user_token( - data=data, headers={"Accept": "application/json"} - ) + token_response = self.get_user_token(data=data, headers={"Accept": "application/json"}) super().set_token_data( { "access_token": token_response.get("access_token"), "refresh_token": token_response.get("refresh_token", None), "access_token_expired_at": ( - datetime.fromtimestamp( - token_response.get("expires_in"), tz=pytz.utc - ) + datetime.fromtimestamp(token_response.get("expires_in"), tz=pytz.utc) if token_response.get("expires_in") 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 ), @@ -117,9 +109,7 @@ class GitHubOAuthProvider(OauthAdapter): # Github does not provide email in user response emails_url = "https://api.github.com/user/emails" emails_response = requests.get(emails_url, headers=headers).json() - email = next( - (email["email"] for email in emails_response if email["primary"]), None - ) + email = next((email["email"] for email in emails_response if email["primary"]), None) return email except requests.RequestException: raise AuthenticationException( diff --git a/apps/api/plane/authentication/provider/oauth/gitlab.py b/apps/api/plane/authentication/provider/oauth/gitlab.py index df6fb7c44..de4a3515e 100644 --- a/apps/api/plane/authentication/provider/oauth/gitlab.py +++ b/apps/api/plane/authentication/provider/oauth/gitlab.py @@ -80,26 +80,21 @@ class GitLabOAuthProvider(OauthAdapter): "redirect_uri": self.redirect_uri, "grant_type": "authorization_code", } - token_response = self.get_user_token( - data=data, headers={"Accept": "application/json"} - ) + token_response = self.get_user_token(data=data, headers={"Accept": "application/json"}) super().set_token_data( { "access_token": token_response.get("access_token"), "refresh_token": token_response.get("refresh_token", None), "access_token_expired_at": ( datetime.fromtimestamp( - token_response.get("created_at") - + token_response.get("expires_in"), + token_response.get("created_at") + token_response.get("expires_in"), tz=pytz.utc, ) if token_response.get("expires_in") 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 ), diff --git a/apps/api/plane/authentication/provider/oauth/google.py b/apps/api/plane/authentication/provider/oauth/google.py index d3f683619..41293782f 100644 --- a/apps/api/plane/authentication/provider/oauth/google.py +++ b/apps/api/plane/authentication/provider/oauth/google.py @@ -53,9 +53,7 @@ class GoogleOAuthProvider(OauthAdapter): "prompt": "consent", "state": state, } - auth_url = ( - f"https://accounts.google.com/o/oauth2/v2/auth?{urlencode(url_params)}" - ) + auth_url = f"https://accounts.google.com/o/oauth2/v2/auth?{urlencode(url_params)}" super().__init__( request, @@ -85,16 +83,12 @@ class GoogleOAuthProvider(OauthAdapter): "access_token": token_response.get("access_token"), "refresh_token": token_response.get("refresh_token", None), "access_token_expired_at": ( - datetime.fromtimestamp( - token_response.get("expires_in"), tz=pytz.utc - ) + datetime.fromtimestamp(token_response.get("expires_in"), tz=pytz.utc) if token_response.get("expires_in") 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 ), diff --git a/apps/api/plane/authentication/rate_limit.py b/apps/api/plane/authentication/rate_limit.py index 744bd38fe..09c3381ce 100644 --- a/apps/api/plane/authentication/rate_limit.py +++ b/apps/api/plane/authentication/rate_limit.py @@ -21,6 +21,4 @@ class AuthenticationThrottle(AnonRateThrottle): error_message="RATE_LIMIT_EXCEEDED", ) except AuthenticationException as e: - return Response( - e.get_error_dict(), status=status.HTTP_429_TOO_MANY_REQUESTS - ) + return Response(e.get_error_dict(), status=status.HTTP_429_TOO_MANY_REQUESTS) diff --git a/apps/api/plane/authentication/utils/login.py b/apps/api/plane/authentication/utils/login.py index e9437ae44..fe6fdad93 100644 --- a/apps/api/plane/authentication/utils/login.py +++ b/apps/api/plane/authentication/utils/login.py @@ -17,9 +17,7 @@ def user_login(request, user, is_app=False, is_admin=False, is_space=False): device_info = { "user_agent": request.META.get("HTTP_USER_AGENT", ""), "ip_address": get_client_ip(request=request), - "domain": base_host( - request=request, is_app=is_app, is_admin=is_admin, is_space=is_space - ), + "domain": base_host(request=request, is_app=is_app, is_admin=is_admin, is_space=is_space), } request.session["device_info"] = device_info request.session.save() diff --git a/apps/api/plane/authentication/utils/redirection_path.py b/apps/api/plane/authentication/utils/redirection_path.py index 459ad7434..82139b821 100644 --- a/apps/api/plane/authentication/utils/redirection_path.py +++ b/apps/api/plane/authentication/utils/redirection_path.py @@ -26,9 +26,7 @@ def get_redirection_path(user): return f"{workspace.slug}" fallback_workspace = ( - Workspace.objects.filter( - workspace_member__member_id=user.id, workspace_member__is_active=True - ) + Workspace.objects.filter(workspace_member__member_id=user.id, workspace_member__is_active=True) .order_by("created_at") .first() ) diff --git a/apps/api/plane/authentication/utils/workspace_project_join.py b/apps/api/plane/authentication/utils/workspace_project_join.py index 4544d9998..bd5ad8501 100644 --- a/apps/api/plane/authentication/utils/workspace_project_join.py +++ b/apps/api/plane/authentication/utils/workspace_project_join.py @@ -11,9 +11,7 @@ def process_workspace_project_invitations(user): """This function takes in User and adds him to all workspace and projects that the user has accepted invited of""" # Check if user has any accepted invites for workspace and add them to workspace - workspace_member_invites = WorkspaceMemberInvite.objects.filter( - email=user.email, accepted=True - ) + workspace_member_invites = WorkspaceMemberInvite.objects.filter(email=user.email, accepted=True) WorkspaceMember.objects.bulk_create( [ @@ -38,20 +36,14 @@ def process_workspace_project_invitations(user): ] # Check if user has any project invites - project_member_invites = ProjectMemberInvite.objects.filter( - email=user.email, accepted=True - ) + project_member_invites = ProjectMemberInvite.objects.filter(email=user.email, accepted=True) # Add user to workspace WorkspaceMember.objects.bulk_create( [ WorkspaceMember( workspace_id=project_member_invite.workspace_id, - role=( - project_member_invite.role - if project_member_invite.role in [5, 15] - else 15 - ), + role=(project_member_invite.role if project_member_invite.role in [5, 15] else 15), member=user, created_by_id=project_member_invite.created_by_id, ) @@ -65,11 +57,7 @@ def process_workspace_project_invitations(user): [ ProjectMember( workspace_id=project_member_invite.workspace_id, - role=( - project_member_invite.role - if project_member_invite.role in [5, 15] - else 15 - ), + role=(project_member_invite.role if project_member_invite.role in [5, 15] else 15), member=user, created_by_id=project_member_invite.created_by_id, ) diff --git a/apps/api/plane/authentication/views/app/check.py b/apps/api/plane/authentication/views/app/check.py index 0ad1db61f..10457b45a 100644 --- a/apps/api/plane/authentication/views/app/check.py +++ b/apps/api/plane/authentication/views/app/check.py @@ -83,9 +83,7 @@ class EmailCheckEndpoint(APIView): "existing": True, "status": ( "MAGIC_CODE" - if existing_user.is_password_autoset - and smtp_configured - and is_magic_login_enabled + if existing_user.is_password_autoset and smtp_configured and is_magic_login_enabled else "CREDENTIAL" ), }, @@ -95,11 +93,7 @@ class EmailCheckEndpoint(APIView): return Response( { "existing": False, - "status": ( - "MAGIC_CODE" - if smtp_configured and is_magic_login_enabled - else "CREDENTIAL" - ), + "status": ("MAGIC_CODE" if smtp_configured and is_magic_login_enabled else "CREDENTIAL"), }, status=status.HTTP_200_OK, ) diff --git a/apps/api/plane/authentication/views/app/email.py b/apps/api/plane/authentication/views/app/email.py index 417e7b40e..864ff102b 100644 --- a/apps/api/plane/authentication/views/app/email.py +++ b/apps/api/plane/authentication/views/app/email.py @@ -47,9 +47,7 @@ class SignInAuthEndpoint(View): if not email or not password: # Redirection params exc = AuthenticationException( - error_code=AUTHENTICATION_ERROR_CODES[ - "REQUIRED_EMAIL_PASSWORD_SIGN_IN" - ], + error_code=AUTHENTICATION_ERROR_CODES["REQUIRED_EMAIL_PASSWORD_SIGN_IN"], error_message="REQUIRED_EMAIL_PASSWORD_SIGN_IN", payload={"email": str(email)}, ) @@ -155,9 +153,7 @@ class SignUpAuthEndpoint(View): if not email or not password: # Redirection params exc = AuthenticationException( - error_code=AUTHENTICATION_ERROR_CODES[ - "REQUIRED_EMAIL_PASSWORD_SIGN_UP" - ], + error_code=AUTHENTICATION_ERROR_CODES["REQUIRED_EMAIL_PASSWORD_SIGN_UP"], error_message="REQUIRED_EMAIL_PASSWORD_SIGN_UP", payload={"email": str(email)}, ) diff --git a/apps/api/plane/authentication/views/app/github.py b/apps/api/plane/authentication/views/app/github.py index 35c4d2121..4720fc7da 100644 --- a/apps/api/plane/authentication/views/app/github.py +++ b/apps/api/plane/authentication/views/app/github.py @@ -18,6 +18,7 @@ from plane.authentication.adapter.error import ( ) from plane.utils.path_validator import get_safe_redirect_url + class GitHubOauthInitiateEndpoint(View): def get(self, request): # Get host and next path @@ -35,9 +36,7 @@ class GitHubOauthInitiateEndpoint(View): ) params = exc.get_error_dict() url = get_safe_redirect_url( - base_url=base_host(request=request, is_app=True), - next_path=next_path, - params=params + base_url=base_host(request=request, is_app=True), next_path=next_path, params=params ) return HttpResponseRedirect(url) try: @@ -49,9 +48,7 @@ class GitHubOauthInitiateEndpoint(View): except AuthenticationException as e: params = e.get_error_dict() url = get_safe_redirect_url( - base_url=base_host(request=request, is_app=True), - next_path=next_path, - params=params + base_url=base_host(request=request, is_app=True), next_path=next_path, params=params ) return HttpResponseRedirect(url) @@ -69,9 +66,7 @@ class GitHubCallbackEndpoint(View): ) params = exc.get_error_dict() url = get_safe_redirect_url( - base_url=base_host(request=request, is_app=True), - next_path=next_path, - params=params + base_url=base_host(request=request, is_app=True), next_path=next_path, params=params ) return HttpResponseRedirect(url) @@ -82,16 +77,12 @@ class GitHubCallbackEndpoint(View): ) params = exc.get_error_dict() url = get_safe_redirect_url( - base_url=base_host(request=request, is_app=True), - next_path=next_path, - params=params + base_url=base_host(request=request, is_app=True), next_path=next_path, params=params ) return HttpResponseRedirect(url) try: - provider = GitHubOAuthProvider( - request=request, code=code, callback=post_user_auth_workflow - ) + provider = GitHubOAuthProvider(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) @@ -101,17 +92,11 @@ class GitHubCallbackEndpoint(View): path = get_redirection_path(user=user) # Get the safe redirect URL - url = get_safe_redirect_url( - base_url=base_host(request=request, is_app=True), - next_path=path, - params={} - ) + url = get_safe_redirect_url(base_url=base_host(request=request, is_app=True), next_path=path, params={}) return HttpResponseRedirect(url) except AuthenticationException as e: params = e.get_error_dict() url = get_safe_redirect_url( - base_url=base_host(request=request, is_app=True), - next_path=next_path, - params=params + base_url=base_host(request=request, is_app=True), next_path=next_path, params=params ) return HttpResponseRedirect(url) diff --git a/apps/api/plane/authentication/views/app/gitlab.py b/apps/api/plane/authentication/views/app/gitlab.py index b2e5da80f..665af00c1 100644 --- a/apps/api/plane/authentication/views/app/gitlab.py +++ b/apps/api/plane/authentication/views/app/gitlab.py @@ -36,9 +36,7 @@ class GitLabOauthInitiateEndpoint(View): ) params = exc.get_error_dict() url = get_safe_redirect_url( - base_url=base_host(request=request, is_app=True), - next_path=next_path, - params=params + base_url=base_host(request=request, is_app=True), next_path=next_path, params=params ) return HttpResponseRedirect(url) try: @@ -50,9 +48,7 @@ class GitLabOauthInitiateEndpoint(View): except AuthenticationException as e: params = e.get_error_dict() url = get_safe_redirect_url( - base_url=base_host(request=request, is_app=True), - next_path=next_path, - params=params + base_url=base_host(request=request, is_app=True), next_path=next_path, params=params ) return HttpResponseRedirect(url) @@ -70,9 +66,7 @@ class GitLabCallbackEndpoint(View): ) params = exc.get_error_dict() url = get_safe_redirect_url( - base_url=base_host(request=request, is_app=True), - next_path=next_path, - params=params + base_url=base_host(request=request, is_app=True), next_path=next_path, params=params ) return HttpResponseRedirect(url) @@ -83,16 +77,12 @@ class GitLabCallbackEndpoint(View): ) params = exc.get_error_dict() url = get_safe_redirect_url( - base_url=base_host(request=request, is_app=True), - next_path=next_path, - params=params + base_url=base_host(request=request, is_app=True), next_path=next_path, params=params ) return HttpResponseRedirect(url) try: - provider = GitLabOAuthProvider( - request=request, code=code, callback=post_user_auth_workflow - ) + provider = GitLabOAuthProvider(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) @@ -103,17 +93,11 @@ class GitLabCallbackEndpoint(View): else: path = get_redirection_path(user=user) # redirect to referer path - url = get_safe_redirect_url( - base_url=base_host(request=request, is_app=True), - next_path=path, - params={} - ) + url = get_safe_redirect_url(base_url=base_host(request=request, is_app=True), next_path=path, params={}) return HttpResponseRedirect(url) except AuthenticationException as e: params = e.get_error_dict() url = get_safe_redirect_url( - base_url=base_host(request=request, is_app=True), - next_path=next_path, - params=params + base_url=base_host(request=request, is_app=True), next_path=next_path, params=params ) return HttpResponseRedirect(url) diff --git a/apps/api/plane/authentication/views/app/google.py b/apps/api/plane/authentication/views/app/google.py index cfa409ae5..0ee81c768 100644 --- a/apps/api/plane/authentication/views/app/google.py +++ b/apps/api/plane/authentication/views/app/google.py @@ -36,9 +36,7 @@ class GoogleOauthInitiateEndpoint(View): ) params = exc.get_error_dict() url = get_safe_redirect_url( - base_url=base_host(request=request, is_app=True), - next_path=next_path, - params=params + base_url=base_host(request=request, is_app=True), next_path=next_path, params=params ) return HttpResponseRedirect(url) @@ -51,9 +49,7 @@ class GoogleOauthInitiateEndpoint(View): except AuthenticationException as e: params = e.get_error_dict() url = get_safe_redirect_url( - base_url=base_host(request=request, is_app=True), - next_path=next_path, - params=params + base_url=base_host(request=request, is_app=True), next_path=next_path, params=params ) return HttpResponseRedirect(url) @@ -71,9 +67,7 @@ class GoogleCallbackEndpoint(View): ) params = exc.get_error_dict() url = get_safe_redirect_url( - base_url=base_host(request=request, is_app=True), - next_path=next_path, - params=params + base_url=base_host(request=request, is_app=True), next_path=next_path, params=params ) return HttpResponseRedirect(url) if not code: @@ -83,15 +77,11 @@ class GoogleCallbackEndpoint(View): ) params = exc.get_error_dict() url = get_safe_redirect_url( - base_url=base_host(request=request, is_app=True), - next_path=next_path, - params=params + base_url=base_host(request=request, is_app=True), next_path=next_path, params=params ) return HttpResponseRedirect(url) try: - provider = GoogleOAuthProvider( - request=request, code=code, callback=post_user_auth_workflow - ) + provider = GoogleOAuthProvider(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) @@ -100,17 +90,11 @@ class GoogleCallbackEndpoint(View): path = next_path else: path = get_redirection_path(user=user) - url = get_safe_redirect_url( - base_url=base_host(request=request, is_app=True), - next_path=path, - params={} - ) + url = get_safe_redirect_url(base_url=base_host(request=request, is_app=True), next_path=path, params={}) return HttpResponseRedirect(url) except AuthenticationException as e: params = e.get_error_dict() url = get_safe_redirect_url( - base_url=base_host(request=request, is_app=True), - next_path=next_path, - params=params + base_url=base_host(request=request, is_app=True), next_path=next_path, params=params ) return HttpResponseRedirect(url) diff --git a/apps/api/plane/authentication/views/app/magic.py b/apps/api/plane/authentication/views/app/magic.py index 694fca6cb..518a5cdea 100644 --- a/apps/api/plane/authentication/views/app/magic.py +++ b/apps/api/plane/authentication/views/app/magic.py @@ -63,9 +63,7 @@ class MagicSignInEndpoint(View): if code == "" or email == "": exc = AuthenticationException( - error_code=AUTHENTICATION_ERROR_CODES[ - "MAGIC_SIGN_IN_EMAIL_CODE_REQUIRED" - ], + error_code=AUTHENTICATION_ERROR_CODES["MAGIC_SIGN_IN_EMAIL_CODE_REQUIRED"], error_message="MAGIC_SIGN_IN_EMAIL_CODE_REQUIRED", ) params = exc.get_error_dict() @@ -108,11 +106,7 @@ class MagicSignInEndpoint(View): path = "/" else: # Get the redirection path - path = ( - str(next_path) - if next_path - else str(get_redirection_path(user=user)) - ) + path = str(next_path) if next_path else str(get_redirection_path(user=user)) # redirect to referer path url = get_safe_redirect_url( base_url=base_host(request=request, is_app=True), @@ -140,9 +134,7 @@ class MagicSignUpEndpoint(View): if code == "" or email == "": exc = AuthenticationException( - error_code=AUTHENTICATION_ERROR_CODES[ - "MAGIC_SIGN_UP_EMAIL_CODE_REQUIRED" - ], + error_code=AUTHENTICATION_ERROR_CODES["MAGIC_SIGN_UP_EMAIL_CODE_REQUIRED"], error_message="MAGIC_SIGN_UP_EMAIL_CODE_REQUIRED", ) params = exc.get_error_dict() diff --git a/apps/api/plane/authentication/views/app/password_management.py b/apps/api/plane/authentication/views/app/password_management.py index bbc5658c4..de0baa71b 100644 --- a/apps/api/plane/authentication/views/app/password_management.py +++ b/apps/api/plane/authentication/views/app/password_management.py @@ -55,9 +55,7 @@ class ForgotPasswordEndpoint(APIView): ) return Response(exc.get_error_dict(), status=status.HTTP_400_BAD_REQUEST) - (EMAIL_HOST,) = get_configuration_value( - [{"key": "EMAIL_HOST", "default": os.environ.get("EMAIL_HOST")}] - ) + (EMAIL_HOST,) = get_configuration_value([{"key": "EMAIL_HOST", "default": os.environ.get("EMAIL_HOST")}]) if not (EMAIL_HOST): exc = AuthenticationException( @@ -82,9 +80,7 @@ class ForgotPasswordEndpoint(APIView): uidb64, token = generate_password_token(user=user) current_site = base_host(request=request, is_app=True) # send the forgot password email - forgot_password.delay( - user.first_name, user.email, uidb64, token, current_site - ) + forgot_password.delay(user.first_name, user.email, uidb64, token, current_site) return Response( {"message": "Check your email to reset your password"}, status=status.HTTP_200_OK, diff --git a/apps/api/plane/authentication/views/common.py b/apps/api/plane/authentication/views/common.py index ab60e6d04..c5dd1714c 100644 --- a/apps/api/plane/authentication/views/common.py +++ b/apps/api/plane/authentication/views/common.py @@ -53,9 +53,7 @@ class ChangePasswordEndpoint(APIView): error_message="MISSING_PASSWORD", payload={"error": "Old password is missing"}, ) - return Response( - exc.get_error_dict(), status=status.HTTP_400_BAD_REQUEST - ) + return Response(exc.get_error_dict(), status=status.HTTP_400_BAD_REQUEST) # Get the new password new_password = request.data.get("new_password", False) @@ -91,9 +89,7 @@ class ChangePasswordEndpoint(APIView): user.is_password_autoset = False user.save() user_login(user=user, request=request, is_app=True) - return Response( - {"message": "Password updated successfully"}, status=status.HTTP_200_OK - ) + return Response({"message": "Password updated successfully"}, status=status.HTTP_200_OK) class SetUserPasswordEndpoint(APIView): @@ -107,9 +103,7 @@ class SetUserPasswordEndpoint(APIView): exc = AuthenticationException( error_code=AUTHENTICATION_ERROR_CODES["PASSWORD_ALREADY_SET"], error_message="PASSWORD_ALREADY_SET", - payload={ - "error": "Your password is already set please change your password from profile" - }, + payload={"error": "Your password is already set please change your password from profile"}, ) return Response(exc.get_error_dict(), status=status.HTTP_400_BAD_REQUEST) diff --git a/apps/api/plane/authentication/views/space/check.py b/apps/api/plane/authentication/views/space/check.py index c8a4539b7..95a5e68df 100644 --- a/apps/api/plane/authentication/views/space/check.py +++ b/apps/api/plane/authentication/views/space/check.py @@ -81,9 +81,7 @@ class EmailCheckSpaceEndpoint(APIView): "existing": True, "status": ( "MAGIC_CODE" - if existing_user.is_password_autoset - and smtp_configured - and is_magic_login_enabled + if existing_user.is_password_autoset and smtp_configured and is_magic_login_enabled else "CREDENTIAL" ), }, @@ -93,11 +91,7 @@ class EmailCheckSpaceEndpoint(APIView): return Response( { "existing": False, - "status": ( - "MAGIC_CODE" - if smtp_configured and is_magic_login_enabled - else "CREDENTIAL" - ), + "status": ("MAGIC_CODE" if smtp_configured and is_magic_login_enabled else "CREDENTIAL"), }, status=status.HTTP_200_OK, ) diff --git a/apps/api/plane/authentication/views/space/email.py b/apps/api/plane/authentication/views/space/email.py index afba06ddc..3d092591a 100644 --- a/apps/api/plane/authentication/views/space/email.py +++ b/apps/api/plane/authentication/views/space/email.py @@ -31,9 +31,7 @@ class SignInAuthSpaceEndpoint(View): ) params = exc.get_error_dict() url = get_safe_redirect_url( - base_url=base_host(request=request, is_space=True), - next_path=next_path, - params=params + base_url=base_host(request=request, is_space=True), next_path=next_path, params=params ) return HttpResponseRedirect(url) @@ -44,17 +42,13 @@ class SignInAuthSpaceEndpoint(View): ## Raise exception if any of the above are missing if not email or not password: exc = AuthenticationException( - error_code=AUTHENTICATION_ERROR_CODES[ - "REQUIRED_EMAIL_PASSWORD_SIGN_IN" - ], + error_code=AUTHENTICATION_ERROR_CODES["REQUIRED_EMAIL_PASSWORD_SIGN_IN"], error_message="REQUIRED_EMAIL_PASSWORD_SIGN_IN", payload={"email": str(email)}, ) params = exc.get_error_dict() url = get_safe_redirect_url( - base_url=base_host(request=request, is_space=True), - next_path=next_path, - params=params + base_url=base_host(request=request, is_space=True), next_path=next_path, params=params ) return HttpResponseRedirect(url) @@ -70,9 +64,7 @@ class SignInAuthSpaceEndpoint(View): ) params = exc.get_error_dict() url = get_safe_redirect_url( - base_url=base_host(request=request, is_space=True), - next_path=next_path, - params=params + base_url=base_host(request=request, is_space=True), next_path=next_path, params=params ) return HttpResponseRedirect(url) @@ -87,16 +79,12 @@ class SignInAuthSpaceEndpoint(View): ) params = exc.get_error_dict() url = get_safe_redirect_url( - base_url=base_host(request=request, is_space=True), - next_path=next_path, - params=params + base_url=base_host(request=request, is_space=True), next_path=next_path, params=params ) return HttpResponseRedirect(url) try: - provider = EmailProvider( - request=request, key=email, code=password, is_signup=False - ) + provider = EmailProvider(request=request, key=email, code=password, is_signup=False) user = provider.authenticate() # Login the user and record his device info user_login(request=request, user=user, is_space=True) @@ -110,9 +98,7 @@ class SignInAuthSpaceEndpoint(View): except AuthenticationException as e: params = e.get_error_dict() url = get_safe_redirect_url( - base_url=base_host(request=request, is_space=True), - next_path=next_path, - params=params + base_url=base_host(request=request, is_space=True), next_path=next_path, params=params ) return HttpResponseRedirect(url) @@ -130,9 +116,7 @@ class SignUpAuthSpaceEndpoint(View): ) params = exc.get_error_dict() url = get_safe_redirect_url( - base_url=base_host(request=request, is_space=True), - next_path=next_path, - params=params + base_url=base_host(request=request, is_space=True), next_path=next_path, params=params ) return HttpResponseRedirect(url) @@ -142,17 +126,13 @@ class SignUpAuthSpaceEndpoint(View): if not email or not password: # Redirection params exc = AuthenticationException( - error_code=AUTHENTICATION_ERROR_CODES[ - "REQUIRED_EMAIL_PASSWORD_SIGN_UP" - ], + error_code=AUTHENTICATION_ERROR_CODES["REQUIRED_EMAIL_PASSWORD_SIGN_UP"], error_message="REQUIRED_EMAIL_PASSWORD_SIGN_UP", payload={"email": str(email)}, ) params = exc.get_error_dict() url = get_safe_redirect_url( - base_url=base_host(request=request, is_space=True), - next_path=next_path, - params=params + base_url=base_host(request=request, is_space=True), next_path=next_path, params=params ) return HttpResponseRedirect(url) # Validate the email @@ -168,9 +148,7 @@ class SignUpAuthSpaceEndpoint(View): ) params = exc.get_error_dict() url = get_safe_redirect_url( - base_url=base_host(request=request, is_space=True), - next_path=next_path, - params=params + base_url=base_host(request=request, is_space=True), next_path=next_path, params=params ) return HttpResponseRedirect(url) @@ -185,16 +163,12 @@ class SignUpAuthSpaceEndpoint(View): ) params = exc.get_error_dict() url = get_safe_redirect_url( - base_url=base_host(request=request, is_space=True), - next_path=next_path, - params=params + base_url=base_host(request=request, is_space=True), next_path=next_path, params=params ) return HttpResponseRedirect(url) try: - provider = EmailProvider( - request=request, key=email, code=password, is_signup=True - ) + provider = EmailProvider(request=request, key=email, code=password, is_signup=True) user = provider.authenticate() # Login the user and record his device info user_login(request=request, user=user, is_space=True) @@ -208,8 +182,6 @@ class SignUpAuthSpaceEndpoint(View): except AuthenticationException as e: params = e.get_error_dict() url = get_safe_redirect_url( - base_url=base_host(request=request, is_space=True), - next_path=next_path, - params=params + base_url=base_host(request=request, is_space=True), next_path=next_path, params=params ) return HttpResponseRedirect(url) diff --git a/apps/api/plane/authentication/views/space/github.py b/apps/api/plane/authentication/views/space/github.py index 1a7d51d66..f12498d3b 100644 --- a/apps/api/plane/authentication/views/space/github.py +++ b/apps/api/plane/authentication/views/space/github.py @@ -32,9 +32,7 @@ class GitHubOauthInitiateSpaceEndpoint(View): ) params = exc.get_error_dict() url = get_safe_redirect_url( - base_url=base_host(request=request, is_space=True), - next_path=next_path, - params=params + base_url=base_host(request=request, is_space=True), next_path=next_path, params=params ) return HttpResponseRedirect(url) @@ -47,9 +45,7 @@ class GitHubOauthInitiateSpaceEndpoint(View): except AuthenticationException as e: params = e.get_error_dict() url = get_safe_redirect_url( - base_url=base_host(request=request, is_space=True), - next_path=next_path, - params=params + base_url=base_host(request=request, is_space=True), next_path=next_path, params=params ) return HttpResponseRedirect(url) @@ -68,9 +64,7 @@ class GitHubCallbackSpaceEndpoint(View): ) params = exc.get_error_dict() url = get_safe_redirect_url( - base_url=base_host(request=request, is_space=True), - next_path=next_path, - params=params + base_url=base_host(request=request, is_space=True), next_path=next_path, params=params ) return HttpResponseRedirect(url) @@ -81,9 +75,7 @@ class GitHubCallbackSpaceEndpoint(View): ) params = exc.get_error_dict() url = get_safe_redirect_url( - base_url=base_host(request=request, is_space=True), - next_path=next_path, - params=params + base_url=base_host(request=request, is_space=True), next_path=next_path, params=params ) return HttpResponseRedirect(url) @@ -95,7 +87,7 @@ class GitHubCallbackSpaceEndpoint(View): # Process workspace and project invitations # redirect to referer path next_path = validate_next_path(next_path=next_path) - + url = f"{base_host(request=request, is_space=True).rstrip('/')}{next_path}" if url_has_allowed_host_and_scheme(url, allowed_hosts=get_allowed_hosts()): return HttpResponseRedirect(url) @@ -104,8 +96,6 @@ class GitHubCallbackSpaceEndpoint(View): except AuthenticationException as e: params = e.get_error_dict() url = get_safe_redirect_url( - base_url=base_host(request=request, is_space=True), - next_path=next_path, - params=params + base_url=base_host(request=request, is_space=True), next_path=next_path, params=params ) return HttpResponseRedirect(url) diff --git a/apps/api/plane/authentication/views/space/gitlab.py b/apps/api/plane/authentication/views/space/gitlab.py index 26ed17f32..498916b34 100644 --- a/apps/api/plane/authentication/views/space/gitlab.py +++ b/apps/api/plane/authentication/views/space/gitlab.py @@ -18,7 +18,6 @@ from plane.authentication.adapter.error import ( from plane.utils.path_validator import get_safe_redirect_url, validate_next_path, get_allowed_hosts - class GitLabOauthInitiateSpaceEndpoint(View): def get(self, request): # Get host and next path @@ -34,9 +33,7 @@ class GitLabOauthInitiateSpaceEndpoint(View): ) params = exc.get_error_dict() url = get_safe_redirect_url( - base_url=base_host(request=request, is_space=True), - next_path=next_path, - params=params + base_url=base_host(request=request, is_space=True), next_path=next_path, params=params ) return HttpResponseRedirect(url) @@ -49,9 +46,7 @@ class GitLabOauthInitiateSpaceEndpoint(View): except AuthenticationException as e: params = e.get_error_dict() url = get_safe_redirect_url( - base_url=base_host(request=request, is_space=True), - next_path=next_path, - params=params + base_url=base_host(request=request, is_space=True), next_path=next_path, params=params ) return HttpResponseRedirect(url) @@ -70,9 +65,7 @@ class GitLabCallbackSpaceEndpoint(View): ) params = exc.get_error_dict() url = get_safe_redirect_url( - base_url=base_host(request=request, is_space=True), - next_path=next_path, - params=params + base_url=base_host(request=request, is_space=True), next_path=next_path, params=params ) return HttpResponseRedirect(url) @@ -83,9 +76,7 @@ class GitLabCallbackSpaceEndpoint(View): ) params = exc.get_error_dict() url = get_safe_redirect_url( - base_url=base_host(request=request, is_space=True), - next_path=next_path, - params=params + base_url=base_host(request=request, is_space=True), next_path=next_path, params=params ) return HttpResponseRedirect(url) @@ -106,8 +97,6 @@ class GitLabCallbackSpaceEndpoint(View): except AuthenticationException as e: params = e.get_error_dict() url = get_safe_redirect_url( - base_url=base_host(request=request, is_space=True), - next_path=next_path, - params=params + base_url=base_host(request=request, is_space=True), next_path=next_path, params=params ) return HttpResponseRedirect(url) diff --git a/apps/api/plane/authentication/views/space/google.py b/apps/api/plane/authentication/views/space/google.py index 617216d64..0f02c1f93 100644 --- a/apps/api/plane/authentication/views/space/google.py +++ b/apps/api/plane/authentication/views/space/google.py @@ -32,9 +32,7 @@ class GoogleOauthInitiateSpaceEndpoint(View): ) params = exc.get_error_dict() url = get_safe_redirect_url( - base_url=base_host(request=request, is_space=True), - next_path=next_path, - params=params + base_url=base_host(request=request, is_space=True), next_path=next_path, params=params ) return HttpResponseRedirect(url) @@ -47,9 +45,7 @@ class GoogleOauthInitiateSpaceEndpoint(View): except AuthenticationException as e: params = e.get_error_dict() url = get_safe_redirect_url( - base_url=base_host(request=request, is_space=True), - next_path=next_path, - params=params + base_url=base_host(request=request, is_space=True), next_path=next_path, params=params ) return HttpResponseRedirect(url) @@ -68,9 +64,7 @@ class GoogleCallbackSpaceEndpoint(View): ) params = exc.get_error_dict() url = get_safe_redirect_url( - base_url=base_host(request=request, is_space=True), - next_path=next_path, - params=params + base_url=base_host(request=request, is_space=True), next_path=next_path, params=params ) return HttpResponseRedirect(url) if not code: @@ -80,9 +74,7 @@ class GoogleCallbackSpaceEndpoint(View): ) params = exc.get_error_dict() url = get_safe_redirect_url( - base_url=base_host(request=request, is_space=True), - next_path=next_path, - params=params + base_url=base_host(request=request, is_space=True), next_path=next_path, params=params ) return HttpResponseRedirect(url) try: @@ -101,8 +93,6 @@ class GoogleCallbackSpaceEndpoint(View): except AuthenticationException as e: params = e.get_error_dict() url = get_safe_redirect_url( - base_url=base_host(request=request, is_space=True), - next_path=next_path, - params=params + base_url=base_host(request=request, is_space=True), next_path=next_path, params=params ) return HttpResponseRedirect(url) diff --git a/apps/api/plane/authentication/views/space/magic.py b/apps/api/plane/authentication/views/space/magic.py index 5052d40c0..df940b327 100644 --- a/apps/api/plane/authentication/views/space/magic.py +++ b/apps/api/plane/authentication/views/space/magic.py @@ -58,9 +58,7 @@ class MagicSignInSpaceEndpoint(View): if code == "" or email == "": exc = AuthenticationException( - error_code=AUTHENTICATION_ERROR_CODES[ - "MAGIC_SIGN_IN_EMAIL_CODE_REQUIRED" - ], + error_code=AUTHENTICATION_ERROR_CODES["MAGIC_SIGN_IN_EMAIL_CODE_REQUIRED"], error_message="MAGIC_SIGN_IN_EMAIL_CODE_REQUIRED", ) params = exc.get_error_dict() @@ -88,9 +86,7 @@ class MagicSignInSpaceEndpoint(View): # Active User try: - provider = MagicCodeProvider( - request=request, key=f"magic_{email}", code=code - ) + provider = MagicCodeProvider(request=request, key=f"magic_{email}", code=code) user = provider.authenticate() # Login the user and record his device info user_login(request=request, user=user, is_space=True) @@ -121,9 +117,7 @@ class MagicSignUpSpaceEndpoint(View): if code == "" or email == "": exc = AuthenticationException( - error_code=AUTHENTICATION_ERROR_CODES[ - "MAGIC_SIGN_UP_EMAIL_CODE_REQUIRED" - ], + error_code=AUTHENTICATION_ERROR_CODES["MAGIC_SIGN_UP_EMAIL_CODE_REQUIRED"], error_message="MAGIC_SIGN_UP_EMAIL_CODE_REQUIRED", ) params = exc.get_error_dict() @@ -150,9 +144,7 @@ class MagicSignUpSpaceEndpoint(View): return HttpResponseRedirect(url) try: - provider = MagicCodeProvider( - request=request, key=f"magic_{email}", code=code - ) + provider = MagicCodeProvider(request=request, key=f"magic_{email}", code=code) user = provider.authenticate() # Login the user and record his device info user_login(request=request, user=user, is_space=True) diff --git a/apps/api/plane/authentication/views/space/password_management.py b/apps/api/plane/authentication/views/space/password_management.py index bff3e3485..12cc88f63 100644 --- a/apps/api/plane/authentication/views/space/password_management.py +++ b/apps/api/plane/authentication/views/space/password_management.py @@ -92,9 +92,7 @@ class ForgotPasswordSpaceEndpoint(APIView): uidb64, token = generate_password_token(user=user) current_site = base_host(request=request, is_space=True) # send the forgot password email - forgot_password.delay( - user.first_name, user.email, uidb64, token, current_site - ) + forgot_password.delay(user.first_name, user.email, uidb64, token, current_site) return Response( {"message": "Check your email to reset your password"}, status=status.HTTP_200_OK, @@ -130,7 +128,7 @@ class ResetPasswordSpaceEndpoint(View): error_code=AUTHENTICATION_ERROR_CODES["INVALID_PASSWORD"], error_message="INVALID_PASSWORD", ) - url = f"{base_host(request=request, is_space=True)}/accounts/reset-password/?{urlencode(exc.get_error_dict())}" + url = f"{base_host(request=request, is_space=True)}/accounts/reset-password/?{urlencode(exc.get_error_dict())}" # noqa: E501 return HttpResponseRedirect(url) # Check the password complexity @@ -140,7 +138,7 @@ class ResetPasswordSpaceEndpoint(View): error_code=AUTHENTICATION_ERROR_CODES["INVALID_PASSWORD"], error_message="INVALID_PASSWORD", ) - url = f"{base_host(request=request, is_space=True)}/accounts/reset-password/?{urlencode(exc.get_error_dict())}" + url = f"{base_host(request=request, is_space=True)}/accounts/reset-password/?{urlencode(exc.get_error_dict())}" # noqa: E501 return HttpResponseRedirect(url) # set_password also hashes the password that the user will get @@ -154,5 +152,5 @@ class ResetPasswordSpaceEndpoint(View): error_code=AUTHENTICATION_ERROR_CODES["EXPIRED_PASSWORD_TOKEN"], error_message="EXPIRED_PASSWORD_TOKEN", ) - url = f"{base_host(request=request, is_space=True)}/accounts/reset-password/?{urlencode(exc.get_error_dict())}" + url = f"{base_host(request=request, is_space=True)}/accounts/reset-password/?{urlencode(exc.get_error_dict())}" # noqa: E501 return HttpResponseRedirect(url) diff --git a/apps/api/plane/authentication/views/space/signout.py b/apps/api/plane/authentication/views/space/signout.py index 613f705ad..aa890f978 100644 --- a/apps/api/plane/authentication/views/space/signout.py +++ b/apps/api/plane/authentication/views/space/signout.py @@ -22,14 +22,8 @@ class SignOutAuthSpaceEndpoint(View): user.save() # Log the user out logout(request) - url = get_safe_redirect_url( - base_url=base_host(request=request, is_space=True), - next_path=next_path - ) + url = get_safe_redirect_url(base_url=base_host(request=request, is_space=True), next_path=next_path) return HttpResponseRedirect(url) except Exception: - url = get_safe_redirect_url( - base_url=base_host(request=request, is_space=True), - next_path=next_path - ) + url = get_safe_redirect_url(base_url=base_host(request=request, is_space=True), next_path=next_path) return HttpResponseRedirect(url) diff --git a/apps/api/plane/bgtasks/analytic_plot_export.py b/apps/api/plane/bgtasks/analytic_plot_export.py index 0f07ccc85..845fb50dd 100644 --- a/apps/api/plane/bgtasks/analytic_plot_export.py +++ b/apps/api/plane/bgtasks/analytic_plot_export.py @@ -87,10 +87,7 @@ def get_assignee_details(slug, filters): """Fetch assignee details if required.""" return ( Issue.issue_objects.filter( - Q( - Q(assignees__avatar__isnull=False) - | Q(assignees__avatar_asset__isnull=False) - ), + Q(Q(assignees__avatar__isnull=False) | Q(assignees__avatar_asset__isnull=False)), workspace__slug=slug, **filters, ) @@ -195,11 +192,7 @@ def generate_segmented_rows( cycle_details, module_details, ): - segment_zero = list( - set( - item.get("segment") for sublist in distribution.values() for item in sublist - ) - ) + segment_zero = list(set(item.get("segment") for sublist in distribution.values() for item in sublist)) segmented = segment @@ -221,38 +214,26 @@ def generate_segmented_rows( if x_axis == ASSIGNEE_ID: assignee = next( - ( - user - for user in assignee_details - if str(user[ASSIGNEE_ID]) == str(item) - ), + (user for user in assignee_details if str(user[ASSIGNEE_ID]) == str(item)), None, ) if assignee: - generated_row[0] = ( - f"{assignee['assignees__first_name']} {assignee['assignees__last_name']}" - ) + generated_row[0] = f"{assignee['assignees__first_name']} {assignee['assignees__last_name']}" if x_axis == LABEL_ID: - label = next( - (lab for lab in label_details if str(lab[LABEL_ID]) == str(item)), None - ) + label = next((lab for lab in label_details if str(lab[LABEL_ID]) == str(item)), None) if label: generated_row[0] = f"{label['labels__name']}" if x_axis == STATE_ID: - state = next( - (sta for sta in state_details if str(sta[STATE_ID]) == str(item)), None - ) + state = next((sta for sta in state_details if str(sta[STATE_ID]) == str(item)), None) if state: generated_row[0] = f"{state['state__name']}" if x_axis == CYCLE_ID: - cycle = next( - (cyc for cyc in cycle_details if str(cyc[CYCLE_ID]) == str(item)), None - ) + cycle = next((cyc for cyc in cycle_details if str(cyc[CYCLE_ID]) == str(item)), None) if cycle: generated_row[0] = f"{cycle['issue_cycle__cycle__name']}" @@ -271,47 +252,33 @@ def generate_segmented_rows( if segmented == ASSIGNEE_ID: for index, segm in enumerate(row_zero[2:]): assignee = next( - ( - user - for user in assignee_details - if str(user[ASSIGNEE_ID]) == str(segm) - ), + (user for user in assignee_details if str(user[ASSIGNEE_ID]) == str(segm)), None, ) if assignee: - row_zero[index + 2] = ( - f"{assignee['assignees__first_name']} {assignee['assignees__last_name']}" - ) + row_zero[index + 2] = f"{assignee['assignees__first_name']} {assignee['assignees__last_name']}" if segmented == LABEL_ID: for index, segm in enumerate(row_zero[2:]): - label = next( - (lab for lab in label_details if str(lab[LABEL_ID]) == str(segm)), None - ) + label = next((lab for lab in label_details if str(lab[LABEL_ID]) == str(segm)), None) if label: row_zero[index + 2] = label["labels__name"] if segmented == STATE_ID: for index, segm in enumerate(row_zero[2:]): - state = next( - (sta for sta in state_details if str(sta[STATE_ID]) == str(segm)), None - ) + state = next((sta for sta in state_details if str(sta[STATE_ID]) == str(segm)), None) if state: row_zero[index + 2] = state["state__name"] if segmented == MODULE_ID: for index, segm in enumerate(row_zero[2:]): - module = next( - (mod for mod in label_details if str(mod[MODULE_ID]) == str(segm)), None - ) + module = next((mod for mod in label_details if str(mod[MODULE_ID]) == str(segm)), None) if module: row_zero[index + 2] = module["issue_module__module__name"] if segmented == CYCLE_ID: for index, segm in enumerate(row_zero[2:]): - cycle = next( - (cyc for cyc in cycle_details if str(cyc[CYCLE_ID]) == str(segm)), None - ) + cycle = next((cyc for cyc in cycle_details if str(cyc[CYCLE_ID]) == str(segm)), None) if cycle: row_zero[index + 2] = cycle["issue_cycle__cycle__name"] @@ -335,38 +302,26 @@ def generate_non_segmented_rows( if x_axis == ASSIGNEE_ID: assignee = next( - ( - user - for user in assignee_details - if str(user[ASSIGNEE_ID]) == str(item) - ), + (user for user in assignee_details if str(user[ASSIGNEE_ID]) == str(item)), None, ) if assignee: - row[0] = ( - f"{assignee['assignees__first_name']} {assignee['assignees__last_name']}" - ) + row[0] = f"{assignee['assignees__first_name']} {assignee['assignees__last_name']}" if x_axis == LABEL_ID: - label = next( - (lab for lab in label_details if str(lab[LABEL_ID]) == str(item)), None - ) + label = next((lab for lab in label_details if str(lab[LABEL_ID]) == str(item)), None) if label: row[0] = f"{label['labels__name']}" if x_axis == STATE_ID: - state = next( - (sta for sta in state_details if str(sta[STATE_ID]) == str(item)), None - ) + state = next((sta for sta in state_details if str(sta[STATE_ID]) == str(item)), None) if state: row[0] = f"{state['state__name']}" if x_axis == CYCLE_ID: - cycle = next( - (cyc for cyc in cycle_details if str(cyc[CYCLE_ID]) == str(item)), None - ) + cycle = next((cyc for cyc in cycle_details if str(cyc[CYCLE_ID]) == str(item)), None) if cycle: row[0] = f"{cycle['issue_cycle__cycle__name']}" @@ -396,40 +351,20 @@ def analytic_export_task(email, data, slug): y_axis = data.get("y_axis", False) segment = data.get("segment", False) - distribution = build_graph_plot( - queryset, x_axis=x_axis, y_axis=y_axis, segment=segment - ) + distribution = build_graph_plot(queryset, x_axis=x_axis, y_axis=y_axis, segment=segment) key = "count" if y_axis == "issue_count" else "estimate" assignee_details = ( - get_assignee_details(slug, filters) - if x_axis == ASSIGNEE_ID or segment == ASSIGNEE_ID - else {} + get_assignee_details(slug, filters) if x_axis == ASSIGNEE_ID or segment == ASSIGNEE_ID else {} ) - label_details = ( - get_label_details(slug, filters) - if x_axis == LABEL_ID or segment == LABEL_ID - else {} - ) + label_details = get_label_details(slug, filters) if x_axis == LABEL_ID or segment == LABEL_ID else {} - state_details = ( - get_state_details(slug, filters) - if x_axis == STATE_ID or segment == STATE_ID - else {} - ) + state_details = get_state_details(slug, filters) if x_axis == STATE_ID or segment == STATE_ID else {} - cycle_details = ( - get_cycle_details(slug, filters) - if x_axis == CYCLE_ID or segment == CYCLE_ID - else {} - ) + cycle_details = get_cycle_details(slug, filters) if x_axis == CYCLE_ID or segment == CYCLE_ID else {} - module_details = ( - get_module_details(slug, filters) - if x_axis == MODULE_ID or segment == MODULE_ID - else {} - ) + module_details = get_module_details(slug, filters) if x_axis == MODULE_ID or segment == MODULE_ID else {} if segment: rows = generate_segmented_rows( diff --git a/apps/api/plane/bgtasks/cleanup_task.py b/apps/api/plane/bgtasks/cleanup_task.py index e5cacd637..8623c9646 100644 --- a/apps/api/plane/bgtasks/cleanup_task.py +++ b/apps/api/plane/bgtasks/cleanup_task.py @@ -61,9 +61,7 @@ def flush_to_mongo_and_delete( logger.debug("No records to flush - buffer is empty") return - logger.info( - f"Starting batch flush: {len(buffer)} records, {len(ids_to_delete)} IDs to delete" - ) + logger.info(f"Starting batch flush: {len(buffer)} records, {len(ids_to_delete)} IDs to delete") mongo_archival_failed = False @@ -83,9 +81,7 @@ def flush_to_mongo_and_delete( # Delete from PostgreSQL - delete() returns (count, {model: count}) delete_result = model.all_objects.filter(id__in=ids_to_delete).delete() - deleted_count = ( - delete_result[0] if delete_result and isinstance(delete_result, tuple) else 0 - ) + deleted_count = delete_result[0] if delete_result and isinstance(delete_result, tuple) else 0 logger.info(f"Batch flush completed: {deleted_count} records deleted") @@ -193,9 +189,7 @@ def transform_email_log(record: Dict) -> Dict: "entity_identifier": str(record["entity_identifier"]), "entity_name": record["entity_name"], "data": record["data"], - "processed_at": ( - str(record["processed_at"]) if record.get("processed_at") else None - ), + "processed_at": (str(record["processed_at"]) if record.get("processed_at") else None), "sent_at": str(record["sent_at"]) if record.get("sent_at") else None, "entity": record["entity"], "old_value": str(record["old_value"]), @@ -220,9 +214,7 @@ def transform_page_version(record: Dict) -> Dict: "created_by_id": str(record["created_by_id"]), "updated_by_id": str(record["updated_by_id"]), "deleted_at": str(record["deleted_at"]) if record.get("deleted_at") else None, - "last_saved_at": ( - str(record["last_saved_at"]) if record.get("last_saved_at") else None - ), + "last_saved_at": (str(record["last_saved_at"]) if record.get("last_saved_at") else None), } @@ -237,9 +229,7 @@ def transform_issue_description_version(record: Dict) -> Dict: "created_by_id": str(record["created_by_id"]), "updated_by_id": str(record["updated_by_id"]), "owned_by_id": str(record["owned_by_id"]), - "last_saved_at": ( - str(record["last_saved_at"]) if record.get("last_saved_at") else None - ), + "last_saved_at": (str(record["last_saved_at"]) if record.get("last_saved_at") else None), "description_binary": record["description_binary"], "description_html": record["description_html"], "description_stripped": record["description_stripped"], diff --git a/apps/api/plane/bgtasks/copy_s3_object.py b/apps/api/plane/bgtasks/copy_s3_object.py index c8d9fc480..e7ef09e35 100644 --- a/apps/api/plane/bgtasks/copy_s3_object.py +++ b/apps/api/plane/bgtasks/copy_s3_object.py @@ -25,9 +25,7 @@ def get_entity_id_field(entity_type, entity_id): FileAsset.EntityTypeContext.ISSUE_DESCRIPTION: {"issue_id": entity_id}, FileAsset.EntityTypeContext.PAGE_DESCRIPTION: {"page_id": entity_id}, FileAsset.EntityTypeContext.COMMENT_DESCRIPTION: {"comment_id": entity_id}, - FileAsset.EntityTypeContext.DRAFT_ISSUE_DESCRIPTION: { - "draft_issue_id": entity_id - }, + FileAsset.EntityTypeContext.DRAFT_ISSUE_DESCRIPTION: {"draft_issue_id": entity_id}, } return entity_mapping.get(entity_type, {}) @@ -87,14 +85,10 @@ def copy_assets(entity, entity_identifier, project_id, asset_ids, user_id): duplicated_assets = [] workspace = entity.workspace storage = S3Storage() - original_assets = FileAsset.objects.filter( - workspace=workspace, project_id=project_id, id__in=asset_ids - ) + original_assets = FileAsset.objects.filter(workspace=workspace, project_id=project_id, id__in=asset_ids) for original_asset in original_assets: - 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"), @@ -118,17 +112,13 @@ def copy_assets(entity, entity_identifier, project_id, asset_ids, user_id): } ) if duplicated_assets: - FileAsset.objects.filter( - pk__in=[item["new_asset_id"] for item in duplicated_assets] - ).update(is_uploaded=True) + FileAsset.objects.filter(pk__in=[item["new_asset_id"] for item in duplicated_assets]).update(is_uploaded=True) return duplicated_assets @shared_task -def copy_s3_objects_of_description_and_assets( - entity_name, entity_identifier, project_id, slug, user_id -): +def copy_s3_objects_of_description_and_assets(entity_name, entity_identifier, project_id, slug, user_id): """ Step 1: Extract asset ids from the description_html of the entity Step 2: Duplicate the assets @@ -144,9 +134,7 @@ def copy_s3_objects_of_description_and_assets( entity = model_class.objects.get(id=entity_identifier) asset_ids = extract_asset_ids(entity.description_html, "image-component") - duplicated_assets = copy_assets( - entity, entity_identifier, project_id, asset_ids, user_id - ) + duplicated_assets = copy_assets(entity, entity_identifier, project_id, asset_ids, user_id) updated_html = update_description(entity, duplicated_assets, "image-component") @@ -154,9 +142,7 @@ def copy_s3_objects_of_description_and_assets( if external_data: entity.description = external_data.get("description") - entity.description_binary = base64.b64decode( - external_data.get("description_binary") - ) + entity.description_binary = base64.b64decode(external_data.get("description_binary")) entity.save() return diff --git a/apps/api/plane/bgtasks/deletion_task.py b/apps/api/plane/bgtasks/deletion_task.py index ef57873cf..932a1fce0 100644 --- a/apps/api/plane/bgtasks/deletion_task.py +++ b/apps/api/plane/bgtasks/deletion_task.py @@ -26,9 +26,7 @@ def soft_delete_related_objects(app_label, model_name, instance_pk, using=None): # Get all related fields that are reverse relationships all_related = [ - f - for f in instance._meta.get_fields() - if (f.one_to_many or f.one_to_one) and f.auto_created and not f.concrete + f for f in instance._meta.get_fields() if (f.one_to_many or f.one_to_one) and f.auto_created and not f.concrete ] # Handle each related field @@ -40,11 +38,7 @@ def soft_delete_related_objects(app_label, model_name, instance_pk, using=None): continue # Get the on_delete behavior name - on_delete_name = ( - relation.on_delete.__name__ - if hasattr(relation.on_delete, "__name__") - else "" - ) + on_delete_name = relation.on_delete.__name__ if hasattr(relation.on_delete, "__name__") else "" if on_delete_name == "DO_NOTHING": continue @@ -82,9 +76,7 @@ def soft_delete_related_objects(app_label, model_name, instance_pk, using=None): ) else: # Handle other relationships - related_queryset = getattr(instance, related_name)( - manager="objects" - ).all() + related_queryset = getattr(instance, related_name)(manager="objects").all() for related_obj in related_queryset: if hasattr(related_obj, "deleted_at"): @@ -139,85 +131,49 @@ def hard_delete(): days = settings.HARD_DELETE_AFTER_DAYS # check delete workspace - _ = Workspace.all_objects.filter( - deleted_at__lt=timezone.now() - timezone.timedelta(days=days) - ).delete() + _ = Workspace.all_objects.filter(deleted_at__lt=timezone.now() - timezone.timedelta(days=days)).delete() # check delete project - _ = Project.all_objects.filter( - deleted_at__lt=timezone.now() - timezone.timedelta(days=days) - ).delete() + _ = Project.all_objects.filter(deleted_at__lt=timezone.now() - timezone.timedelta(days=days)).delete() # check delete cycle - _ = Cycle.all_objects.filter( - deleted_at__lt=timezone.now() - timezone.timedelta(days=days) - ).delete() + _ = Cycle.all_objects.filter(deleted_at__lt=timezone.now() - timezone.timedelta(days=days)).delete() # check delete module - _ = Module.all_objects.filter( - deleted_at__lt=timezone.now() - timezone.timedelta(days=days) - ).delete() + _ = Module.all_objects.filter(deleted_at__lt=timezone.now() - timezone.timedelta(days=days)).delete() # check delete issue - _ = Issue.all_objects.filter( - deleted_at__lt=timezone.now() - timezone.timedelta(days=days) - ).delete() + _ = Issue.all_objects.filter(deleted_at__lt=timezone.now() - timezone.timedelta(days=days)).delete() # check delete page - _ = Page.all_objects.filter( - deleted_at__lt=timezone.now() - timezone.timedelta(days=days) - ).delete() + _ = Page.all_objects.filter(deleted_at__lt=timezone.now() - timezone.timedelta(days=days)).delete() # check delete view - _ = IssueView.all_objects.filter( - deleted_at__lt=timezone.now() - timezone.timedelta(days=days) - ).delete() + _ = IssueView.all_objects.filter(deleted_at__lt=timezone.now() - timezone.timedelta(days=days)).delete() # check delete label - _ = Label.all_objects.filter( - deleted_at__lt=timezone.now() - timezone.timedelta(days=days) - ).delete() + _ = Label.all_objects.filter(deleted_at__lt=timezone.now() - timezone.timedelta(days=days)).delete() # check delete state - _ = State.all_objects.filter( - deleted_at__lt=timezone.now() - timezone.timedelta(days=days) - ).delete() + _ = State.all_objects.filter(deleted_at__lt=timezone.now() - timezone.timedelta(days=days)).delete() - _ = IssueActivity.all_objects.filter( - deleted_at__lt=timezone.now() - timezone.timedelta(days=days) - ).delete() + _ = IssueActivity.all_objects.filter(deleted_at__lt=timezone.now() - timezone.timedelta(days=days)).delete() - _ = IssueComment.all_objects.filter( - deleted_at__lt=timezone.now() - timezone.timedelta(days=days) - ).delete() + _ = IssueComment.all_objects.filter(deleted_at__lt=timezone.now() - timezone.timedelta(days=days)).delete() - _ = IssueLink.all_objects.filter( - deleted_at__lt=timezone.now() - timezone.timedelta(days=days) - ).delete() + _ = IssueLink.all_objects.filter(deleted_at__lt=timezone.now() - timezone.timedelta(days=days)).delete() - _ = IssueReaction.all_objects.filter( - deleted_at__lt=timezone.now() - timezone.timedelta(days=days) - ).delete() + _ = IssueReaction.all_objects.filter(deleted_at__lt=timezone.now() - timezone.timedelta(days=days)).delete() - _ = UserFavorite.all_objects.filter( - deleted_at__lt=timezone.now() - timezone.timedelta(days=days) - ).delete() + _ = UserFavorite.all_objects.filter(deleted_at__lt=timezone.now() - timezone.timedelta(days=days)).delete() - _ = ModuleIssue.all_objects.filter( - deleted_at__lt=timezone.now() - timezone.timedelta(days=days) - ).delete() + _ = ModuleIssue.all_objects.filter(deleted_at__lt=timezone.now() - timezone.timedelta(days=days)).delete() - _ = CycleIssue.all_objects.filter( - deleted_at__lt=timezone.now() - timezone.timedelta(days=days) - ).delete() + _ = CycleIssue.all_objects.filter(deleted_at__lt=timezone.now() - timezone.timedelta(days=days)).delete() - _ = Estimate.all_objects.filter( - deleted_at__lt=timezone.now() - timezone.timedelta(days=days) - ).delete() + _ = Estimate.all_objects.filter(deleted_at__lt=timezone.now() - timezone.timedelta(days=days)).delete() - _ = EstimatePoint.all_objects.filter( - deleted_at__lt=timezone.now() - timezone.timedelta(days=days) - ).delete() + _ = EstimatePoint.all_objects.filter(deleted_at__lt=timezone.now() - timezone.timedelta(days=days)).delete() # at last, check for every thing which ever is left and delete it # Get all Django models @@ -228,8 +184,6 @@ def hard_delete(): # Check if the model has a 'deleted_at' field if hasattr(model, "deleted_at"): # Get all instances where 'deleted_at' is greater than 30 days ago - _ = model.all_objects.filter( - deleted_at__lt=timezone.now() - timezone.timedelta(days=days) - ).delete() + _ = model.all_objects.filter(deleted_at__lt=timezone.now() - timezone.timedelta(days=days)).delete() return diff --git a/apps/api/plane/bgtasks/dummy_data_task.py b/apps/api/plane/bgtasks/dummy_data_task.py index 03ac55b4c..3220ef0c0 100644 --- a/apps/api/plane/bgtasks/dummy_data_task.py +++ b/apps/api/plane/bgtasks/dummy_data_task.py @@ -44,9 +44,7 @@ def create_project(workspace, user_id): project = Project.objects.create( workspace=workspace, name=f"{name}_{unique_id}", - identifier=name[ - : random.randint(2, 12 if len(name) - 1 >= 12 else len(name) - 1) - ].upper(), + identifier=name[: random.randint(2, 12 if len(name) - 1 >= 12 else len(name) - 1)].upper(), created_by_id=user_id, intake_view=True, ) @@ -163,17 +161,11 @@ def create_cycles(workspace, project, user_id, cycle_count): ) # Ensure end_date is strictly after start_date if start_date is not None - while start_date is not None and ( - end_date <= start_date or (start_date, end_date) in used_date_ranges - ): + while start_date is not None and (end_date <= start_date or (start_date, end_date) in used_date_ranges): end_date = fake.date_this_year() # Add the unique date range to the set - ( - used_date_ranges.add((start_date, end_date)) - if (end_date is not None and start_date is not None) - else None - ) + (used_date_ranges.add((start_date, end_date)) if (end_date is not None and start_date is not None) else None) # Append the cycle with unique date range cycles.append( @@ -244,10 +236,7 @@ def create_pages(workspace, project, user_id, pages_count): pages = Page.objects.bulk_create(pages, ignore_conflicts=True) # Add Page to project ProjectPage.objects.bulk_create( - [ - ProjectPage(page=page, project=project, workspace=workspace) - for page in pages - ], + [ProjectPage(page=page, project=project, workspace=workspace) for page in pages], batch_size=1000, ) @@ -264,14 +253,10 @@ def create_page_labels(workspace, project, user_id, pages_count): bulk_page_labels = [] for page in pages: for label in random.sample(list(labels), random.randint(0, len(labels) - 1)): - bulk_page_labels.append( - PageLabel(page_id=page, label_id=label, workspace=workspace) - ) + bulk_page_labels.append(PageLabel(page_id=page, label_id=label, workspace=workspace)) # Page labels - PageLabel.objects.bulk_create( - bulk_page_labels, batch_size=1000, ignore_conflicts=True - ) + PageLabel.objects.bulk_create(bulk_page_labels, batch_size=1000, ignore_conflicts=True) def create_issues(workspace, project, user_id, issue_count): @@ -279,20 +264,14 @@ def create_issues(workspace, project, user_id, issue_count): Faker.seed(0) states = ( - State.objects.filter(workspace=workspace, project=project) - .exclude(group="Triage") - .values_list("id", flat=True) + State.objects.filter(workspace=workspace, project=project).exclude(group="Triage").values_list("id", flat=True) ) - creators = ProjectMember.objects.filter( - workspace=workspace, project=project - ).values_list("member_id", flat=True) + creators = ProjectMember.objects.filter(workspace=workspace, project=project).values_list("member_id", flat=True) issues = [] # Get the maximum sequence_id - last_id = IssueSequence.objects.filter(project=project).aggregate( - largest=Max("sequence") - )["largest"] + last_id = IssueSequence.objects.filter(project=project).aggregate(largest=Max("sequence"))["largest"] last_id = 1 if last_id is None else last_id + 1 @@ -301,9 +280,7 @@ def create_issues(workspace, project, user_id, issue_count): project=project, state_id=states[random.randint(0, len(states) - 1)] ).aggregate(largest=Max("sort_order"))["largest"] - largest_sort_order = ( - 65535 if largest_sort_order is None else largest_sort_order + 10000 - ) + largest_sort_order = 65535 if largest_sort_order is None else largest_sort_order + 10000 for _ in range(0, issue_count): start_date = [None, fake.date_this_year()][random.randint(0, 1)] @@ -329,9 +306,7 @@ def create_issues(workspace, project, user_id, issue_count): sort_order=largest_sort_order, start_date=start_date, target_date=end_date, - priority=["urgent", "high", "medium", "low", "none"][ - random.randint(0, 4) - ], + priority=["urgent", "high", "medium", "low", "none"][random.randint(0, 4)], created_by_id=creators[random.randint(0, len(creators) - 1)], ) ) @@ -375,20 +350,14 @@ def create_issues(workspace, project, user_id, issue_count): def create_intake_issues(workspace, project, user_id, intake_issue_count): issues = create_issues(workspace, project, user_id, intake_issue_count) - intake, create = Intake.objects.get_or_create( - name="Intake", project=project, is_default=True - ) + intake, create = Intake.objects.get_or_create(name="Intake", project=project, is_default=True) IntakeIssue.objects.bulk_create( [ IntakeIssue( issue=issue, intake=intake, status=(status := [-2, -1, 0, 1, 2][random.randint(0, 4)]), - snoozed_till=( - datetime.now() + timedelta(days=random.randint(1, 30)) - if status == 0 - else None - ), + snoozed_till=(datetime.now() + timedelta(days=random.randint(1, 30)) if status == 0 else None), source=SourceType.IN_APP, workspace=workspace, project=project, @@ -402,12 +371,8 @@ def create_intake_issues(workspace, project, user_id, intake_issue_count): def create_issue_parent(workspace, project, user_id, issue_count): parent_count = issue_count / 4 - parent_issues = Issue.objects.filter(project=project).values_list("id", flat=True)[ - : int(parent_count) - ] - sub_issues = Issue.objects.filter(project=project).exclude(pk__in=parent_issues)[ - : int(issue_count / 2) - ] + parent_issues = Issue.objects.filter(project=project).values_list("id", flat=True)[: int(parent_count)] + sub_issues = Issue.objects.filter(project=project).exclude(pk__in=parent_issues)[: int(issue_count / 2)] bulk_sub_issues = [] for sub_issue in sub_issues: @@ -418,9 +383,7 @@ def create_issue_parent(workspace, project, user_id, issue_count): def create_issue_assignees(workspace, project, user_id, issue_count): # assignees - assignees = ProjectMember.objects.filter(project=project).values_list( - "member_id", flat=True - ) + assignees = ProjectMember.objects.filter(project=project).values_list("member_id", flat=True) issues = random.sample( list(Issue.objects.filter(project=project).values_list("id", flat=True)), int(issue_count / 2), @@ -429,9 +392,7 @@ def create_issue_assignees(workspace, project, user_id, issue_count): # Bulk issue bulk_issue_assignees = [] for issue in issues: - for assignee in random.sample( - list(assignees), random.randint(0, len(assignees) - 1) - ): + for assignee in random.sample(list(assignees), random.randint(0, len(assignees) - 1)): bulk_issue_assignees.append( IssueAssignee( issue_id=issue, @@ -442,9 +403,7 @@ def create_issue_assignees(workspace, project, user_id, issue_count): ) # Issue assignees - IssueAssignee.objects.bulk_create( - bulk_issue_assignees, batch_size=1000, ignore_conflicts=True - ) + IssueAssignee.objects.bulk_create(bulk_issue_assignees, batch_size=1000, ignore_conflicts=True) def create_issue_labels(workspace, project, user_id, issue_count): @@ -464,16 +423,10 @@ def create_issue_labels(workspace, project, user_id, issue_count): for issue in issues: random.shuffle(shuffled_labels) for label in random.sample(shuffled_labels, random.randint(0, 5)): - bulk_issue_labels.append( - IssueLabel( - issue_id=issue, label_id=label, project=project, workspace=workspace - ) - ) + bulk_issue_labels.append(IssueLabel(issue_id=issue, label_id=label, project=project, workspace=workspace)) # Issue labels - IssueLabel.objects.bulk_create( - bulk_issue_labels, batch_size=1000, ignore_conflicts=True - ) + IssueLabel.objects.bulk_create(bulk_issue_labels, batch_size=1000, ignore_conflicts=True) def create_cycle_issues(workspace, project, user_id, issue_count): @@ -488,16 +441,10 @@ def create_cycle_issues(workspace, project, user_id, issue_count): bulk_cycle_issues = [] for issue in issues: cycle = cycles[random.randint(0, len(cycles) - 1)] - bulk_cycle_issues.append( - CycleIssue( - cycle_id=cycle, issue_id=issue, project=project, workspace=workspace - ) - ) + bulk_cycle_issues.append(CycleIssue(cycle_id=cycle, issue_id=issue, project=project, workspace=workspace)) # Issue assignees - CycleIssue.objects.bulk_create( - bulk_cycle_issues, batch_size=1000, ignore_conflicts=True - ) + CycleIssue.objects.bulk_create(bulk_cycle_issues, batch_size=1000, ignore_conflicts=True) def create_module_issues(workspace, project, user_id, issue_count): @@ -527,9 +474,7 @@ def create_module_issues(workspace, project, user_id, issue_count): ) ) # Issue assignees - ModuleIssue.objects.bulk_create( - bulk_module_issues, batch_size=1000, ignore_conflicts=True - ) + ModuleIssue.objects.bulk_create(bulk_module_issues, batch_size=1000, ignore_conflicts=True) @shared_task @@ -561,29 +506,19 @@ def create_dummy_data( create_labels(workspace=workspace, project=project, user_id=user_id) # create cycles - create_cycles( - workspace=workspace, project=project, user_id=user_id, cycle_count=cycle_count - ) + create_cycles(workspace=workspace, project=project, user_id=user_id, cycle_count=cycle_count) # create modules - create_modules( - workspace=workspace, project=project, user_id=user_id, module_count=module_count - ) + create_modules(workspace=workspace, project=project, user_id=user_id, module_count=module_count) # create pages - create_pages( - workspace=workspace, project=project, user_id=user_id, pages_count=pages_count - ) + create_pages(workspace=workspace, project=project, user_id=user_id, pages_count=pages_count) # create page labels - create_page_labels( - workspace=workspace, project=project, user_id=user_id, pages_count=pages_count - ) + create_page_labels(workspace=workspace, project=project, user_id=user_id, pages_count=pages_count) # create issues - create_issues( - workspace=workspace, project=project, user_id=user_id, issue_count=issue_count - ) + create_issues(workspace=workspace, project=project, user_id=user_id, issue_count=issue_count) # create intake issues create_intake_issues( @@ -594,28 +529,18 @@ def create_dummy_data( ) # create issue parent - create_issue_parent( - workspace=workspace, project=project, user_id=user_id, issue_count=issue_count - ) + create_issue_parent(workspace=workspace, project=project, user_id=user_id, issue_count=issue_count) # create issue assignees - create_issue_assignees( - workspace=workspace, project=project, user_id=user_id, issue_count=issue_count - ) + create_issue_assignees(workspace=workspace, project=project, user_id=user_id, issue_count=issue_count) # create issue labels - create_issue_labels( - workspace=workspace, project=project, user_id=user_id, issue_count=issue_count - ) + create_issue_labels(workspace=workspace, project=project, user_id=user_id, issue_count=issue_count) # create cycle issues - create_cycle_issues( - workspace=workspace, project=project, user_id=user_id, issue_count=issue_count - ) + create_cycle_issues(workspace=workspace, project=project, user_id=user_id, issue_count=issue_count) # create module issues - create_module_issues( - workspace=workspace, project=project, user_id=user_id, issue_count=issue_count - ) + create_module_issues(workspace=workspace, project=project, user_id=user_id, issue_count=issue_count) return diff --git a/apps/api/plane/bgtasks/email_notification_task.py b/apps/api/plane/bgtasks/email_notification_task.py index 141bb2f71..1402adc41 100644 --- a/apps/api/plane/bgtasks/email_notification_task.py +++ b/apps/api/plane/bgtasks/email_notification_task.py @@ -42,42 +42,27 @@ def release_lock(lock_id): @shared_task def stack_email_notification(): # get all email notifications - email_notifications = ( - EmailNotificationLog.objects.filter(processed_at__isnull=True) - .order_by("receiver") - .values() - ) + email_notifications = EmailNotificationLog.objects.filter(processed_at__isnull=True).order_by("receiver").values() # Create the below format for each of the issues # {"issue_id" : { "actor_id1": [ { data }, { data } ], "actor_id2": [ { data }, { data } ] }} # Convert to unique receivers list - receivers = list( - set( - [ - str(notification.get("receiver_id")) - for notification in email_notifications - ] - ) - ) + receivers = list(set([str(notification.get("receiver_id")) for notification in email_notifications])) processed_notifications = [] # Loop through all the issues to create the emails for receiver_id in receivers: # Notification triggered for the receiver receiver_notifications = [ - notification - for notification in email_notifications - if str(notification.get("receiver_id")) == receiver_id + notification for notification in email_notifications if str(notification.get("receiver_id")) == receiver_id ] # create payload for all issues payload = {} email_notification_ids = [] for receiver_notification in receiver_notifications: - payload.setdefault( - receiver_notification.get("entity_identifier"), {} - ).setdefault(str(receiver_notification.get("triggered_by_id")), []).append( - receiver_notification.get("data") - ) + payload.setdefault(receiver_notification.get("entity_identifier"), {}).setdefault( + str(receiver_notification.get("triggered_by_id")), [] + ).append(receiver_notification.get("data")) # append processed notifications processed_notifications.append(receiver_notification.get("id")) email_notification_ids.append(receiver_notification.get("id")) @@ -92,9 +77,7 @@ def stack_email_notification(): ) # Update the email notification log - EmailNotificationLog.objects.filter(pk__in=processed_notifications).update( - processed_at=timezone.now() - ) + EmailNotificationLog.objects.filter(pk__in=processed_notifications).update(processed_at=timezone.now()) def create_payload(notification_data): @@ -115,10 +98,7 @@ def create_payload(notification_data): .setdefault(field, {}) .setdefault("old_value", []) .append(old_value) - if old_value - not in data.setdefault(actor_id, {}) - .setdefault(field, {}) - .get("old_value", []) + if old_value not in data.setdefault(actor_id, {}).setdefault(field, {}).get("old_value", []) else None ) @@ -129,18 +109,15 @@ def create_payload(notification_data): .setdefault(field, {}) .setdefault("new_value", []) .append(new_value) - if new_value - not in data.setdefault(actor_id, {}) - .setdefault(field, {}) - .get("new_value", []) + if new_value not in data.setdefault(actor_id, {}).setdefault(field, {}).get("new_value", []) else None ) if not data.get("actor_id", {}).get("activity_time", False): data[actor_id]["activity_time"] = str( - datetime.fromisoformat( - issue_activity.get("activity_time").rstrip("Z") - ).strftime("%Y-%m-%d %H:%M:%S") + datetime.fromisoformat(issue_activity.get("activity_time").rstrip("Z")).strftime( + "%Y-%m-%d %H:%M:%S" + ) ) return data @@ -169,9 +146,7 @@ def process_html_content(content): @shared_task -def send_email_notification( - issue_id, notification_data, receiver_id, email_notification_ids -): +def send_email_notification(issue_id, notification_data, receiver_id, email_notification_ids): # Convert UUIDs to a sorted, concatenated string sorted_ids = sorted(email_notification_ids) ids_str = "_".join(str(id) for id in sorted_ids) @@ -225,12 +200,8 @@ def send_email_notification( } ) if mention: - mention["new_value"] = process_html_content( - mention.get("new_value") - ) - mention["old_value"] = process_html_content( - mention.get("old_value") - ) + mention["new_value"] = process_html_content(mention.get("new_value")) + mention["old_value"] = process_html_content(mention.get("old_value")) comments.append( { "actor_comments": mention, @@ -243,9 +214,7 @@ def send_email_notification( ) activity_time = changes.pop("activity_time") # Parse the input string into a datetime object - formatted_time = datetime.strptime( - activity_time, "%Y-%m-%d %H:%M:%S" - ).strftime("%H:%M %p") + formatted_time = datetime.strptime(activity_time, "%Y-%m-%d %H:%M:%S").strftime("%H:%M %p") if changes: template_data.append( @@ -275,20 +244,18 @@ def send_email_notification( "issue": { "issue_identifier": f"{str(issue.project.identifier)}-{str(issue.sequence_id)}", "name": issue.name, - "issue_url": f"{base_api}/{str(issue.project.workspace.slug)}/projects/{str(issue.project.id)}/issues/{str(issue.id)}", + "issue_url": f"{base_api}/{str(issue.project.workspace.slug)}/projects/{str(issue.project.id)}/issues/{str(issue.id)}", # noqa: E501 }, "receiver": {"email": receiver.email}, - "issue_url": f"{base_api}/{str(issue.project.workspace.slug)}/projects/{str(issue.project.id)}/issues/{str(issue.id)}", - "project_url": f"{base_api}/{str(issue.project.workspace.slug)}/projects/{str(issue.project.id)}/issues/", + "issue_url": f"{base_api}/{str(issue.project.workspace.slug)}/projects/{str(issue.project.id)}/issues/{str(issue.id)}", # noqa: E501 + "project_url": f"{base_api}/{str(issue.project.workspace.slug)}/projects/{str(issue.project.id)}/issues/", # noqa: E501 "workspace": str(issue.project.workspace.slug), "project": str(issue.project.name), "user_preference": f"{base_api}/{str(issue.project.workspace.slug)}/settings/account/notifications/", "comments": comments, "entity_type": "issue", } - html_content = render_to_string( - "emails/notifications/issue-updates.html", context - ) + html_content = render_to_string("emails/notifications/issue-updates.html", context) text_content = strip_tags(html_content) try: @@ -313,9 +280,7 @@ def send_email_notification( logging.getLogger("plane.worker").info("Email Sent Successfully") # Update the logs - EmailNotificationLog.objects.filter( - pk__in=email_notification_ids - ).update(sent_at=timezone.now()) + EmailNotificationLog.objects.filter(pk__in=email_notification_ids).update(sent_at=timezone.now()) # release the lock release_lock(lock_id=lock_id) diff --git a/apps/api/plane/bgtasks/export_task.py b/apps/api/plane/bgtasks/export_task.py index 4d7fcd5ff..fc41d7bbf 100644 --- a/apps/api/plane/bgtasks/export_task.py +++ b/apps/api/plane/bgtasks/export_task.py @@ -93,15 +93,11 @@ def create_zip_file(files: List[tuple[str, str | bytes]]) -> io.BytesIO: # TODO: Change the upload_to_s3 function to use the new storage method with entry in file asset table -def upload_to_s3( - zip_file: io.BytesIO, workspace_id: UUID, token_id: str, slug: str -) -> None: +def upload_to_s3(zip_file: io.BytesIO, workspace_id: UUID, token_id: str, slug: str) -> None: """ Upload a ZIP file to S3 and generate a presigned URL. """ - file_name = ( - f"{workspace_id}/export-{slug}-{token_id[:6]}-{str(timezone.now().date())}.zip" - ) + file_name = f"{workspace_id}/export-{slug}-{token_id[:6]}-{str(timezone.now().date())}.zip" expires_in = 7 * 24 * 60 * 60 if settings.USE_MINIO: @@ -122,7 +118,7 @@ def upload_to_s3( # Generate presigned url for the uploaded file with different base presign_s3 = boto3.client( "s3", - endpoint_url=f"{settings.AWS_S3_URL_PROTOCOL}//{str(settings.AWS_S3_CUSTOM_DOMAIN).replace('/uploads', '')}/", + endpoint_url=f"{settings.AWS_S3_URL_PROTOCOL}//{str(settings.AWS_S3_CUSTOM_DOMAIN).replace('/uploads', '')}/", # noqa: E501 aws_access_key_id=settings.AWS_ACCESS_KEY_ID, aws_secret_access_key=settings.AWS_SECRET_ACCESS_KEY, config=Config(signature_version="s3v4"), @@ -260,11 +256,7 @@ def update_json_row(rows: List[dict], row: dict) -> None: Update the json row with the new assignee and label. """ matched_index = next( - ( - index - for index, existing_row in enumerate(rows) - if existing_row["ID"] == row["ID"] - ), + (index for index, existing_row in enumerate(rows) if existing_row["ID"] == row["ID"]), None, ) @@ -275,13 +267,9 @@ def update_json_row(rows: List[dict], row: dict) -> None: ) assignee, label = row["Assignee"], row["Labels"] - if assignee is not None and ( - existing_assignees is None or label not in existing_assignees - ): + if assignee is not None and (existing_assignees is None or label not in existing_assignees): rows[matched_index]["Assignee"] += f", {assignee}" - if label is not None and ( - existing_labels is None or label not in existing_labels - ): + if label is not None and (existing_labels is None or label not in existing_labels): rows[matched_index]["Labels"] += f", {label}" else: rows.append(row) @@ -300,13 +288,9 @@ def update_table_row(rows: List[List[str]], row: List[str]) -> None: existing_assignees, existing_labels = rows[matched_index][7:9] assignee, label = row[7:9] - if assignee is not None and ( - existing_assignees is None or label not in existing_assignees - ): + if assignee is not None and (existing_assignees is None or label not in existing_assignees): rows[matched_index][8] += f", {assignee}" - if label is not None and ( - existing_labels is None or label not in existing_labels - ): + if label is not None and (existing_labels is None or label not in existing_labels): rows[matched_index][8] += f", {label}" else: rows.append(row) @@ -465,9 +449,7 @@ def issue_export_task( "updated_at": issue.updated_at, "completed_at": issue.completed_at, "archived_at": issue.archived_at, - "module_name": [ - module.module.name for module in issue.issue_module.all() - ], + "module_name": [module.module.name for module in issue.issue_module.all()], "created_by": get_created_by(issue), "labels": [label.name for label in issue.label_details], "comments": [ @@ -478,14 +460,9 @@ def issue_export_task( } for comment in issue.issue_comments.all() ], - "estimate": issue.estimate_point.value - if issue.estimate_point and issue.estimate_point.value - else "", + "estimate": issue.estimate_point.value if issue.estimate_point and issue.estimate_point.value else "", "link": [link.url for link in issue.issue_link.all()], - "assignees": [ - f"{assignee.first_name} {assignee.last_name}" - for assignee in issue.assignee_details - ], + "assignees": [f"{assignee.first_name} {assignee.last_name}" for assignee in issue.assignee_details], "subscribers_count": issue.issue_subscribers.count(), "attachment_count": len(attachments), "attachment_links": [ diff --git a/apps/api/plane/bgtasks/file_asset_task.py b/apps/api/plane/bgtasks/file_asset_task.py index b7b05df3b..d6eccf735 100644 --- a/apps/api/plane/bgtasks/file_asset_task.py +++ b/apps/api/plane/bgtasks/file_asset_task.py @@ -17,9 +17,6 @@ from plane.db.models import FileAsset def delete_unuploaded_file_asset(): """This task deletes unuploaded file assets older than a certain number of days.""" FileAsset.objects.filter( - Q( - created_at__lt=timezone.now() - - timedelta(days=int(os.environ.get("UNUPLOADED_ASSET_DELETE_DAYS", "7"))) - ) + Q(created_at__lt=timezone.now() - timedelta(days=int(os.environ.get("UNUPLOADED_ASSET_DELETE_DAYS", "7")))) & Q(is_uploaded=False) ).delete() diff --git a/apps/api/plane/bgtasks/forgot_password_task.py b/apps/api/plane/bgtasks/forgot_password_task.py index 4e80f2cc1..ffaba9937 100644 --- a/apps/api/plane/bgtasks/forgot_password_task.py +++ b/apps/api/plane/bgtasks/forgot_password_task.py @@ -18,9 +18,7 @@ from plane.utils.exception_logger import log_exception @shared_task def forgot_password(first_name, email, uidb64, token, current_site): try: - relative_link = ( - f"/accounts/reset-password/?uidb64={uidb64}&token={token}&email={email}" - ) + relative_link = f"/accounts/reset-password/?uidb64={uidb64}&token={token}&email={email}" abs_url = str(current_site) + relative_link ( diff --git a/apps/api/plane/bgtasks/issue_activities_task.py b/apps/api/plane/bgtasks/issue_activities_task.py index 4845223e7..a886305fd 100644 --- a/apps/api/plane/bgtasks/issue_activities_task.py +++ b/apps/api/plane/bgtasks/issue_activities_task.py @@ -81,14 +81,8 @@ def track_description( issue_activities, epoch, ): - if current_instance.get("description_html") != requested_data.get( - "description_html" - ): - last_activity = ( - IssueActivity.objects.filter(issue_id=issue_id) - .order_by("-created_at") - .first() - ) + if current_instance.get("description_html") != requested_data.get("description_html"): + last_activity = IssueActivity.objects.filter(issue_id=issue_id).order_by("-created_at").first() if ( last_activity is not None and last_activity.field == "description" @@ -124,12 +118,8 @@ def track_parent( issue_activities, epoch, ): - current_parent_id = current_instance.get("parent_id") or current_instance.get( - "parent" - ) - requested_parent_id = requested_data.get("parent_id") or requested_data.get( - "parent" - ) + current_parent_id = current_instance.get("parent_id") or current_instance.get("parent") + requested_parent_id = requested_data.get("parent_id") or requested_data.get("parent") # Validate UUIDs before database queries if current_parent_id is not None and not is_valid_uuid(current_parent_id): @@ -138,17 +128,8 @@ def track_parent( return if current_parent_id != requested_parent_id: - old_parent = ( - Issue.objects.filter(pk=current_parent_id).first() - if current_parent_id is not None - else None - ) - new_parent = ( - Issue.objects.filter(pk=requested_parent_id).first() - if requested_parent_id is not None - else None - ) - + old_parent = Issue.objects.filter(pk=current_parent_id).first() if current_parent_id is not None else None + new_parent = Issue.objects.filter(pk=requested_parent_id).first() if requested_parent_id is not None else None issue_activities.append( IssueActivity( @@ -156,14 +137,10 @@ def track_parent( actor_id=actor_id, verb="updated", old_value=( - f"{old_parent.project.identifier}-{old_parent.sequence_id}" - if old_parent is not None - else "" + f"{old_parent.project.identifier}-{old_parent.sequence_id}" if old_parent is not None else "" ), new_value=( - f"{new_parent.project.identifier}-{new_parent.sequence_id}" - if new_parent is not None - else "" + f"{new_parent.project.identifier}-{new_parent.sequence_id}" if new_parent is not None else "" ), field="parent", project_id=project_id, @@ -263,15 +240,9 @@ def track_target_date( actor_id=actor_id, verb="updated", old_value=( - current_instance.get("target_date") - if current_instance.get("target_date") is not None - else "" - ), - new_value=( - requested_data.get("target_date") - if requested_data.get("target_date") is not None - else "" + current_instance.get("target_date") if current_instance.get("target_date") is not None else "" ), + new_value=(requested_data.get("target_date") if requested_data.get("target_date") is not None else ""), field="target_date", project_id=project_id, workspace_id=workspace_id, @@ -299,15 +270,9 @@ def track_start_date( actor_id=actor_id, verb="updated", old_value=( - current_instance.get("start_date") - if current_instance.get("start_date") is not None - else "" - ), - new_value=( - requested_data.get("start_date") - if requested_data.get("start_date") is not None - else "" + current_instance.get("start_date") if current_instance.get("start_date") is not None else "" ), + new_value=(requested_data.get("start_date") if requested_data.get("start_date") is not None else ""), field="start_date", project_id=project_id, workspace_id=workspace_id, @@ -328,7 +293,6 @@ def track_labels( issue_activities, epoch, ): - # Labels requested_labels = extract_ids(requested_data, "label_ids", "labels") current_labels = extract_ids(current_instance, "label_ids", "labels") @@ -437,9 +401,7 @@ def track_assignees( ) # Create assignees subscribers to the issue and ignore if already - IssueSubscriber.objects.bulk_create( - bulk_subscribers, batch_size=10, ignore_conflicts=True - ) + IssueSubscriber.objects.bulk_create(bulk_subscribers, batch_size=10, ignore_conflicts=True) for dropped_assignee in dropped_assginees: # validate uuids @@ -476,16 +438,12 @@ def track_estimate_points( ): if current_instance.get("estimate_point") != requested_data.get("estimate_point"): old_estimate = ( - EstimatePoint.objects.filter( - pk=current_instance.get("estimate_point") - ).first() + EstimatePoint.objects.filter(pk=current_instance.get("estimate_point")).first() if current_instance.get("estimate_point") is not None else None ) new_estimate = ( - EstimatePoint.objects.filter( - pk=requested_data.get("estimate_point") - ).first() + EstimatePoint.objects.filter(pk=requested_data.get("estimate_point")).first() if requested_data.get("estimate_point") is not None else None ) @@ -500,9 +458,7 @@ def track_estimate_points( else None ), new_identifier=( - requested_data.get("estimate_point") - if requested_data.get("estimate_point") is not None - else None + requested_data.get("estimate_point") if requested_data.get("estimate_point") is not None else None ), old_value=old_estimate.value if old_estimate else None, new_value=new_estimate.value if new_estimate else None, @@ -575,9 +531,7 @@ def track_closed_to( epoch, ): if requested_data.get("closed_to") is not None: - updated_state = State.objects.get( - pk=requested_data.get("closed_to"), project_id=project_id - ) + updated_state = State.objects.get(pk=requested_data.get("closed_to"), project_id=project_id) issue_activities.append( IssueActivity( issue_id=issue_id, @@ -664,9 +618,7 @@ def update_issue_activity( } requested_data = json.loads(requested_data) if requested_data is not None else None - current_instance = ( - json.loads(current_instance) if current_instance is not None else None - ) + current_instance = json.loads(current_instance) if current_instance is not None else None for key in requested_data: func = ISSUE_ACTIVITY_MAPPER.get(key) @@ -718,9 +670,7 @@ def create_comment_activity( epoch, ): requested_data = json.loads(requested_data) if requested_data is not None else None - current_instance = ( - json.loads(current_instance) if current_instance is not None else None - ) + current_instance = json.loads(current_instance) if current_instance is not None else None issue_activities.append( IssueActivity( @@ -750,9 +700,7 @@ def update_comment_activity( epoch, ): requested_data = json.loads(requested_data) if requested_data is not None else None - current_instance = ( - json.loads(current_instance) if current_instance is not None else None - ) + current_instance = json.loads(current_instance) if current_instance is not None else None if current_instance.get("comment_html") != requested_data.get("comment_html"): issue_activities.append( @@ -811,21 +759,15 @@ def create_cycle_issue_activity( epoch, ): requested_data = json.loads(requested_data) if requested_data is not None else None - current_instance = ( - json.loads(current_instance) if current_instance is not None else None - ) + current_instance = json.loads(current_instance) if current_instance is not None else None # Updated Records: updated_records = current_instance.get("updated_cycle_issues", []) created_records = json.loads(current_instance.get("created_cycle_issues", [])) for updated_record in updated_records: - old_cycle = Cycle.objects.filter( - pk=updated_record.get("old_cycle_id", None) - ).first() - new_cycle = Cycle.objects.filter( - pk=updated_record.get("new_cycle_id", None) - ).first() + old_cycle = Cycle.objects.filter(pk=updated_record.get("old_cycle_id", None)).first() + new_cycle = Cycle.objects.filter(pk=updated_record.get("new_cycle_id", None)).first() issue = Issue.objects.filter(pk=updated_record.get("issue_id")).first() if issue: issue.updated_at = timezone.now() @@ -850,12 +792,8 @@ def create_cycle_issue_activity( ) for created_record in created_records: - cycle = Cycle.objects.filter( - pk=created_record.get("fields").get("cycle") - ).first() - issue = Issue.objects.filter( - pk=created_record.get("fields").get("issue") - ).first() + cycle = Cycle.objects.filter(pk=created_record.get("fields").get("cycle")).first() + issue = Issue.objects.filter(pk=created_record.get("fields").get("issue")).first() if issue: issue.updated_at = timezone.now() issue.save(update_fields=["updated_at"]) @@ -888,9 +826,7 @@ def delete_cycle_issue_activity( epoch, ): requested_data = json.loads(requested_data) if requested_data is not None else None - current_instance = ( - json.loads(current_instance) if current_instance is not None else None - ) + current_instance = json.loads(current_instance) if current_instance is not None else None cycle_id = requested_data.get("cycle_id", "") cycle_name = requested_data.get("cycle_name", "") @@ -962,9 +898,7 @@ def delete_module_issue_activity( epoch, ): requested_data = json.loads(requested_data) if requested_data is not None else None - current_instance = ( - json.loads(current_instance) if current_instance is not None else None - ) + current_instance = json.loads(current_instance) if current_instance is not None else None module_name = current_instance.get("module_name") current_issue = Issue.objects.filter(pk=issue_id).first() if current_issue: @@ -981,11 +915,7 @@ def delete_module_issue_activity( project_id=project_id, workspace_id=workspace_id, comment=f"removed this issue from {module_name}", - old_identifier=( - requested_data.get("module_id") - if requested_data.get("module_id") is not None - else None - ), + old_identifier=(requested_data.get("module_id") if requested_data.get("module_id") is not None else None), epoch=epoch, ) ) @@ -1002,9 +932,7 @@ def create_link_activity( epoch, ): requested_data = json.loads(requested_data) if requested_data is not None else None - current_instance = ( - json.loads(current_instance) if current_instance is not None else None - ) + current_instance = json.loads(current_instance) if current_instance is not None else None issue_activities.append( IssueActivity( @@ -1033,9 +961,7 @@ def update_link_activity( epoch, ): requested_data = json.loads(requested_data) if requested_data is not None else None - current_instance = ( - json.loads(current_instance) if current_instance is not None else None - ) + current_instance = json.loads(current_instance) if current_instance is not None else None if current_instance.get("url") != requested_data.get("url"): issue_activities.append( @@ -1066,9 +992,7 @@ def delete_link_activity( issue_activities, epoch, ): - current_instance = ( - json.loads(current_instance) if current_instance is not None else None - ) + current_instance = json.loads(current_instance) if current_instance is not None else None issue_activities.append( IssueActivity( @@ -1097,9 +1021,7 @@ def create_attachment_activity( epoch, ): requested_data = json.loads(requested_data) if requested_data is not None else None - current_instance = ( - json.loads(current_instance) if current_instance is not None else None - ) + current_instance = json.loads(current_instance) if current_instance is not None else None issue_activities.append( IssueActivity( @@ -1191,9 +1113,7 @@ def delete_issue_reaction_activity( issue_activities, epoch, ): - current_instance = ( - json.loads(current_instance) if current_instance is not None else None - ) + current_instance = json.loads(current_instance) if current_instance is not None else None if current_instance and current_instance.get("reaction") is not None: issue_activities.append( IssueActivity( @@ -1235,11 +1155,7 @@ def create_comment_reaction_activity( .first() ) comment = IssueComment.objects.get(pk=comment_id, project_id=project_id) - if ( - comment is not None - and comment_reaction_id is not None - and comment_id is not None - ): + if comment is not None and comment_reaction_id is not None and comment_id is not None: issue_activities.append( IssueActivity( issue_id=comment.issue_id, @@ -1268,14 +1184,10 @@ def delete_comment_reaction_activity( issue_activities, epoch, ): - current_instance = ( - json.loads(current_instance) if current_instance is not None else None - ) + current_instance = json.loads(current_instance) if current_instance is not None else None if current_instance and current_instance.get("reaction") is not None: issue_id = ( - IssueComment.objects.filter( - pk=current_instance.get("comment_id"), project_id=project_id - ) + IssueComment.objects.filter(pk=current_instance.get("comment_id"), project_id=project_id) .values_list("issue_id", flat=True) .first() ) @@ -1338,9 +1250,7 @@ def delete_issue_vote_activity( issue_activities, epoch, ): - current_instance = ( - json.loads(current_instance) if current_instance is not None else None - ) + current_instance = json.loads(current_instance) if current_instance is not None else None if current_instance and current_instance.get("vote") is not None: issue_activities.append( IssueActivity( @@ -1371,9 +1281,7 @@ def create_issue_relation_activity( epoch, ): requested_data = json.loads(requested_data) if requested_data is not None else None - current_instance = ( - json.loads(current_instance) if current_instance is not None else None - ) + current_instance = json.loads(current_instance) if current_instance is not None else None if current_instance is None and requested_data.get("issues") is not None: for related_issue in requested_data.get("issues"): issue = Issue.objects.get(pk=related_issue) @@ -1422,9 +1330,7 @@ def delete_issue_relation_activity( epoch, ): requested_data = json.loads(requested_data) if requested_data is not None else None - current_instance = ( - json.loads(current_instance) if current_instance is not None else None - ) + current_instance = json.loads(current_instance) if current_instance is not None else None issue = Issue.objects.get(pk=requested_data.get("related_issue")) issue_activities.append( IssueActivity( @@ -1502,13 +1408,8 @@ def update_draft_issue_activity( epoch, ): requested_data = json.loads(requested_data) if requested_data is not None else None - current_instance = ( - json.loads(current_instance) if current_instance is not None else None - ) - if ( - requested_data.get("is_draft") is not None - and requested_data.get("is_draft") is False - ): + current_instance = json.loads(current_instance) if current_instance is not None else None + if requested_data.get("is_draft") is not None and requested_data.get("is_draft") is False: issue_activities.append( IssueActivity( issue_id=issue_id, @@ -1569,9 +1470,7 @@ def create_intake_activity( epoch, ): requested_data = json.loads(requested_data) if requested_data is not None else None - current_instance = ( - json.loads(current_instance) if current_instance is not None else None - ) + current_instance = json.loads(current_instance) if current_instance is not None else None status_dict = { -2: "Pending", -1: "Rejected", diff --git a/apps/api/plane/bgtasks/issue_automation_task.py b/apps/api/plane/bgtasks/issue_automation_task.py index 68f3d32da..1cc303b57 100644 --- a/apps/api/plane/bgtasks/issue_automation_task.py +++ b/apps/api/plane/bgtasks/issue_automation_task.py @@ -39,15 +39,9 @@ def archive_old_issues(): state__group__in=["completed", "cancelled"], ), Q(issue_cycle__isnull=True) - | ( - Q(issue_cycle__cycle__end_date__lt=timezone.now()) - & Q(issue_cycle__isnull=False) - ), + | (Q(issue_cycle__cycle__end_date__lt=timezone.now()) & Q(issue_cycle__isnull=False)), Q(issue_module__isnull=True) - | ( - Q(issue_module__module__target_date__lt=timezone.now()) - & Q(issue_module__isnull=False) - ), + | (Q(issue_module__module__target_date__lt=timezone.now()) & Q(issue_module__isnull=False)), ).filter( Q(issue_intake__status=1) | Q(issue_intake__status=-1) @@ -67,15 +61,11 @@ def archive_old_issues(): # Bulk Update the issues and log the activity if issues_to_update: - Issue.objects.bulk_update( - issues_to_update, ["archived_at"], batch_size=100 - ) + Issue.objects.bulk_update(issues_to_update, ["archived_at"], batch_size=100) _ = [ issue_activity.delay( type="issue.activity.updated", - requested_data=json.dumps( - {"archived_at": str(archive_at), "automation": True} - ), + requested_data=json.dumps({"archived_at": str(archive_at), "automation": True}), actor_id=str(project.created_by_id), issue_id=issue.id, project_id=project_id, @@ -95,9 +85,7 @@ def archive_old_issues(): def close_old_issues(): try: # Get all the projects whose close_in is greater than 0 - projects = Project.objects.filter(close_in__gt=0).select_related( - "default_state" - ) + projects = Project.objects.filter(close_in__gt=0).select_related("default_state") for project in projects: project_id = project.id @@ -112,15 +100,9 @@ def close_old_issues(): state__group__in=["backlog", "unstarted", "started"], ), Q(issue_cycle__isnull=True) - | ( - Q(issue_cycle__cycle__end_date__lt=timezone.now()) - & Q(issue_cycle__isnull=False) - ), + | (Q(issue_cycle__cycle__end_date__lt=timezone.now()) & Q(issue_cycle__isnull=False)), Q(issue_module__isnull=True) - | ( - Q(issue_module__module__target_date__lt=timezone.now()) - & Q(issue_module__isnull=False) - ), + | (Q(issue_module__module__target_date__lt=timezone.now()) & Q(issue_module__isnull=False)), ).filter( Q(issue_intake__status=1) | Q(issue_intake__status=-1) @@ -142,15 +124,11 @@ def close_old_issues(): # Bulk Update the issues and log the activity if issues_to_update: - Issue.objects.bulk_update( - issues_to_update, ["state"], batch_size=100 - ) + Issue.objects.bulk_update(issues_to_update, ["state"], batch_size=100) [ issue_activity.delay( type="issue.activity.updated", - requested_data=json.dumps( - {"closed_to": str(issue.state_id)} - ), + requested_data=json.dumps({"closed_to": str(issue.state_id)}), actor_id=str(project.created_by_id), issue_id=issue.id, project_id=project_id, diff --git a/apps/api/plane/bgtasks/issue_description_version_sync.py b/apps/api/plane/bgtasks/issue_description_version_sync.py index 14956cb50..d10ebfcba 100644 --- a/apps/api/plane/bgtasks/issue_description_version_sync.py +++ b/apps/api/plane/bgtasks/issue_description_version_sync.py @@ -70,9 +70,7 @@ def sync_issue_description_version(batch_size=5000, offset=0, countdown=300): for issue in issues_batch: # Validate required fields if not issue.workspace_id or not issue.project_id: - logging.warning( - f"Skipping {issue.id} - missing workspace_id or project_id" - ) + logging.warning(f"Skipping {issue.id} - missing workspace_id or project_id") continue # Determine owned_by_id @@ -120,6 +118,4 @@ def sync_issue_description_version(batch_size=5000, offset=0, countdown=300): @shared_task def schedule_issue_description_version(batch_size=5000, countdown=300): - sync_issue_description_version.delay( - batch_size=int(batch_size), countdown=countdown - ) + sync_issue_description_version.delay(batch_size=int(batch_size), countdown=countdown) diff --git a/apps/api/plane/bgtasks/issue_description_version_task.py b/apps/api/plane/bgtasks/issue_description_version_task.py index a29fb6c57..06d15705a 100644 --- a/apps/api/plane/bgtasks/issue_description_version_task.py +++ b/apps/api/plane/bgtasks/issue_description_version_task.py @@ -15,10 +15,7 @@ def should_update_existing_version( return time_difference = (timezone.now() - version.last_saved_at).total_seconds() - return ( - str(version.owned_by_id) == str(user_id) - and time_difference <= max_time_difference - ) + return str(version.owned_by_id) == str(user_id) and time_difference <= max_time_difference def update_existing_version(version: IssueDescriptionVersion, issue) -> None: @@ -40,9 +37,7 @@ def update_existing_version(version: IssueDescriptionVersion, issue) -> None: @shared_task -def issue_description_version_task( - updated_issue, issue_id, user_id, is_creating=False -) -> Optional[bool]: +def issue_description_version_task(updated_issue, issue_id, user_id, is_creating=False) -> Optional[bool]: try: # Parse updated issue data current_issue: Dict = json.loads(updated_issue) if updated_issue else {} @@ -51,18 +46,13 @@ def issue_description_version_task( issue = Issue.objects.get(id=issue_id) # Check if description has changed - if ( - current_issue.get("description_html") == issue.description_html - and not is_creating - ): + if current_issue.get("description_html") == issue.description_html and not is_creating: return with transaction.atomic(): # Get latest version latest_version = ( - IssueDescriptionVersion.objects.filter(issue_id=issue_id) - .order_by("-last_saved_at") - .first() + IssueDescriptionVersion.objects.filter(issue_id=issue_id).order_by("-last_saved_at").first() ) # Determine whether to update existing or create new version diff --git a/apps/api/plane/bgtasks/issue_version_sync.py b/apps/api/plane/bgtasks/issue_version_sync.py index 698cedf15..761c26bc2 100644 --- a/apps/api/plane/bgtasks/issue_version_sync.py +++ b/apps/api/plane/bgtasks/issue_version_sync.py @@ -38,24 +38,17 @@ def issue_task(updated_issue, issue_id, user_id): updated_current_issue[key] = value if updated_current_issue: - issue_version = ( - IssueVersion.objects.filter(issue_id=issue_id) - .order_by("-last_saved_at") - .first() - ) + issue_version = IssueVersion.objects.filter(issue_id=issue_id).order_by("-last_saved_at").first() if ( issue_version and str(issue_version.owned_by) == str(user_id) - and (timezone.now() - issue_version.last_saved_at).total_seconds() - <= 600 + and (timezone.now() - issue_version.last_saved_at).total_seconds() <= 600 ): for key, value in updated_current_issue.items(): setattr(issue_version, key, value) issue_version.last_saved_at = timezone.now() - issue_version.save( - update_fields=list(updated_current_issue.keys()) + ["last_saved_at"] - ) + issue_version.save(update_fields=list(updated_current_issue.keys()) + ["last_saved_at"]) else: IssueVersion.log_issue_version(issue, user_id) @@ -88,16 +81,11 @@ def get_owner_id(issue: Issue) -> Optional[int]: def get_related_data(issue_ids: List[UUID]) -> Dict: """Get related data for the given issue IDs""" - cycle_issues = { - ci.issue_id: ci.cycle_id - for ci in CycleIssue.objects.filter(issue_id__in=issue_ids) - } + cycle_issues = {ci.issue_id: ci.cycle_id for ci in CycleIssue.objects.filter(issue_id__in=issue_ids)} # Get assignees with proper grouping assignee_records = list( - IssueAssignee.objects.filter(issue_id__in=issue_ids) - .values_list("issue_id", "assignee_id") - .order_by("issue_id") + IssueAssignee.objects.filter(issue_id__in=issue_ids).values_list("issue_id", "assignee_id").order_by("issue_id") ) assignees = {} for issue_id, group in groupby(assignee_records, key=lambda x: x[0]): @@ -105,9 +93,7 @@ def get_related_data(issue_ids: List[UUID]) -> Dict: # Get labels with proper grouping label_records = list( - IssueLabel.objects.filter(issue_id__in=issue_ids) - .values_list("issue_id", "label_id") - .order_by("issue_id") + IssueLabel.objects.filter(issue_id__in=issue_ids).values_list("issue_id", "label_id").order_by("issue_id") ) labels = {} for issue_id, group in groupby(label_records, key=lambda x: x[0]): @@ -115,9 +101,7 @@ def get_related_data(issue_ids: List[UUID]) -> Dict: # Get modules with proper grouping module_records = list( - ModuleIssue.objects.filter(issue_id__in=issue_ids) - .values_list("issue_id", "module_id") - .order_by("issue_id") + ModuleIssue.objects.filter(issue_id__in=issue_ids).values_list("issue_id", "module_id").order_by("issue_id") ) modules = {} for issue_id, group in groupby(module_records, key=lambda x: x[0]): @@ -125,9 +109,7 @@ def get_related_data(issue_ids: List[UUID]) -> Dict: # Get latest activities latest_activities = {} - activities = IssueActivity.objects.filter(issue_id__in=issue_ids).order_by( - "issue_id", "-created_at" - ) + activities = IssueActivity.objects.filter(issue_id__in=issue_ids).order_by("issue_id", "-created_at") for issue_id, activities_group in groupby(activities, key=lambda x: x.issue_id): first_activity = next(activities_group, None) if first_activity: @@ -147,9 +129,7 @@ def create_issue_version(issue: Issue, related_data: Dict) -> Optional[IssueVers try: if not issue.workspace_id or not issue.project_id: - logging.warning( - f"Skipping issue {issue.id} - missing workspace_id or project_id" - ) + logging.warning(f"Skipping issue {issue.id} - missing workspace_id or project_id") return None owned_by_id = get_owner_id(issue) @@ -209,9 +189,7 @@ def sync_issue_version(batch_size=5000, offset=0, countdown=300): # Get issues batch with optimized queries issues_batch = list( - base_query.order_by("created_at") - .select_related("workspace", "project") - .all()[offset:end_offset] + base_query.order_by("created_at").select_related("workspace", "project").all()[offset:end_offset] ) if not issues_batch: diff --git a/apps/api/plane/bgtasks/notification_task.py b/apps/api/plane/bgtasks/notification_task.py index e58344bbf..6e571c0b1 100644 --- a/apps/api/plane/bgtasks/notification_task.py +++ b/apps/api/plane/bgtasks/notification_task.py @@ -56,9 +56,7 @@ def get_new_mentions(requested_instance, current_instance): mentions_newer = extract_mentions(requested_instance) # Getting Set Difference from mentions_newer - new_mentions = [ - mention for mention in mentions_newer if mention not in mentions_older - ] + new_mentions = [mention for mention in mentions_newer if mention not in mentions_older] return new_mentions @@ -73,9 +71,7 @@ def get_removed_mentions(requested_instance, current_instance): mentions_newer = extract_mentions(requested_instance) # Getting Set Difference from mentions_newer - removed_mentions = [ - mention for mention in mentions_older if mention not in mentions_newer - ] + removed_mentions = [mention for mention in mentions_older if mention not in mentions_newer] return removed_mentions @@ -87,7 +83,7 @@ def extract_mentions_as_subscribers(project_id, issue_id, mentions): bulk_mention_subscribers = [] for mention_id in mentions: - # If the particular mention has not already been subscribed to the issue, he must be sent the mentioned notification + # If the particular mention has not already been subscribed to the issue, he must be sent the mentioned notification # noqa: E501 if ( not IssueSubscriber.objects.filter( issue_id=issue_id, subscriber_id=mention_id, project_id=project_id @@ -95,12 +91,8 @@ def extract_mentions_as_subscribers(project_id, issue_id, mentions): and not IssueAssignee.objects.filter( project_id=project_id, issue_id=issue_id, assignee_id=mention_id ).exists() - and not Issue.objects.filter( - project_id=project_id, pk=issue_id, created_by_id=mention_id - ).exists() - and ProjectMember.objects.filter( - project_id=project_id, member_id=mention_id, is_active=True - ).exists() + and not Issue.objects.filter(project_id=project_id, pk=issue_id, created_by_id=mention_id).exists() + and ProjectMember.objects.filter(project_id=project_id, member_id=mention_id, is_active=True).exists() ): project = Project.objects.get(pk=project_id) @@ -118,15 +110,13 @@ def extract_mentions_as_subscribers(project_id, issue_id, mentions): # Parse Issue Description & extracts mentions def extract_mentions(issue_instance): try: - # issue_instance has to be a dictionary passed, containing the description_html and other set of activity data. + # issue_instance has to be a dictionary passed, containing the description_html and other set of activity data. # noqa: E501 mentions = [] # Convert string to dictionary data = json.loads(issue_instance) html = data.get("description_html") soup = BeautifulSoup(html, "html.parser") - mention_tags = soup.find_all( - "mention-component", attrs={"entity_name": "user_mention"} - ) + mention_tags = soup.find_all("mention-component", attrs={"entity_name": "user_mention"}) mentions = [mention_tag["entity_identifier"] for mention_tag in mention_tags] @@ -140,9 +130,7 @@ def extract_comment_mentions(comment_value): try: mentions = [] soup = BeautifulSoup(comment_value, "html.parser") - mentions_tags = soup.find_all( - "mention-component", attrs={"entity_name": "user_mention"} - ) + mentions_tags = soup.find_all("mention-component", attrs={"entity_name": "user_mention"}) for mention_tag in mentions_tags: mentions.append(mention_tag["entity_identifier"]) return list(set(mentions)) @@ -157,16 +145,12 @@ def get_new_comment_mentions(new_value, old_value): mentions_older = extract_comment_mentions(old_value) # Getting Set Difference from mentions_newer - new_mentions = [ - mention for mention in mentions_newer if mention not in mentions_older - ] + new_mentions = [mention for mention in mentions_newer if mention not in mentions_older] return new_mentions -def create_mention_notification( - project, notification_comment, issue, actor_id, mention_id, issue_id, activity -): +def create_mention_notification(project, notification_comment, issue, actor_id, mention_id, issue_id, activity): return Notification( workspace=project.workspace, sender="in_app:issue_activities:mentioned", @@ -192,16 +176,8 @@ def create_mention_notification( "actor": str(activity.get("actor_id")), "new_value": str(activity.get("new_value")), "old_value": str(activity.get("old_value")), - "old_identifier": ( - str(activity.get("old_identifier")) - if activity.get("old_identifier") - else None - ), - "new_identifier": ( - str(activity.get("new_identifier")) - if activity.get("new_identifier") - else None - ), + "old_identifier": (str(activity.get("old_identifier")) if activity.get("old_identifier") else None), + "new_identifier": (str(activity.get("new_identifier")) if activity.get("new_identifier") else None), }, }, ) @@ -220,9 +196,7 @@ def notifications( ): try: issue_activities_created = ( - json.loads(issue_activities_created) - if issue_activities_created is not None - else None + json.loads(issue_activities_created) if issue_activities_created is not None else None ) if type not in [ "cycle.activity.created", @@ -250,20 +224,14 @@ def notifications( """ # get the list of active project members - project_members = ProjectMember.objects.filter( - project_id=project_id, is_active=True - ).values_list("member_id", flat=True) + project_members = ProjectMember.objects.filter(project_id=project_id, is_active=True).values_list( + "member_id", flat=True + ) # Get new mentions from the newer instance - new_mentions = get_new_mentions( - requested_instance=requested_data, current_instance=current_instance - ) - new_mentions = list( - set(new_mentions) & {str(member) for member in project_members} - ) - removed_mention = get_removed_mentions( - requested_instance=requested_data, current_instance=current_instance - ) + new_mentions = get_new_mentions(requested_instance=requested_data, current_instance=current_instance) + new_mentions = list(set(new_mentions) & {str(member) for member in project_members}) + removed_mention = get_removed_mentions(requested_instance=requested_data, current_instance=current_instance) comment_mentions = [] all_comment_mentions = [] @@ -281,10 +249,7 @@ def notifications( if issue_comment is not None: # TODO: Maybe save the comment mentions, so that in future, we can filter out the issues based on comment mentions as well. - all_comment_mentions = ( - all_comment_mentions - + extract_comment_mentions(issue_comment_new_value) - ) + all_comment_mentions = all_comment_mentions + extract_comment_mentions(issue_comment_new_value) new_comment_mentions = get_new_comment_mentions( old_value=issue_comment_old_value, @@ -292,9 +257,7 @@ def notifications( ) comment_mentions = comment_mentions + new_comment_mentions comment_mentions = [ - mention - for mention in comment_mentions - if UUID(mention) in set(project_members) + mention for mention in comment_mentions if UUID(mention) in set(project_members) ] comment_mention_subscribers = extract_mentions_as_subscribers( @@ -303,20 +266,20 @@ def notifications( """ We will not send subscription activity notification to the below mentioned user sets - Those who have been newly mentioned in the issue description, we will send mention notification to them. - - When the activity is a comment_created and there exist a mention in the comment, then we have to send the "mention_in_comment" notification - - When the activity is a comment_updated and there exist a mention change, then also we have to send the "mention_in_comment" notification + - When the activity is a comment_created and there exist a mention in the comment, + then we have to send the "mention_in_comment" notification + - When the activity is a comment_updated and there exist a mention change, + then also we have to send the "mention_in_comment" notification """ - # ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- # + # --------------------------------------------------------------------------------------------------------- issue_subscribers = list( IssueSubscriber.objects.filter( project_id=project_id, issue_id=issue_id, subscriber__in=Subquery(project_members), ) - .exclude( - subscriber_id__in=list(new_mentions + comment_mentions + [actor_id]) - ) + .exclude(subscriber_id__in=list(new_mentions + comment_mentions + [actor_id])) .values_list("subscriber", flat=True) ) @@ -344,10 +307,7 @@ def notifications( for subscriber in issue_subscribers: if issue.created_by_id and issue.created_by_id == subscriber: sender = "in_app:issue_activities:created" - elif ( - subscriber in issue_assignees - and issue.created_by_id not in issue_assignees - ): + elif subscriber in issue_assignees and issue.created_by_id not in issue_assignees: sender = "in_app:issue_activities:assigned" else: sender = "in_app:issue_activities:subscribed" @@ -365,10 +325,7 @@ def notifications( # Check if the value should be sent or not send_email = False - if ( - issue_activity.get("field") == "state" - and preference.state_change - ): + if issue_activity.get("field") == "state" and preference.state_change: send_email = True elif ( issue_activity.get("field") == "state" @@ -380,9 +337,7 @@ def notifications( ).exists() ): send_email = True - elif ( - issue_activity.get("field") == "comment" and preference.comment - ): + elif issue_activity.get("field") == "comment" and preference.comment: send_email = True elif preference.property_change: send_email = True @@ -429,9 +384,7 @@ def notifications( "new_value": str(issue_activity.get("new_value")), "old_value": str(issue_activity.get("old_value")), "issue_comment": str( - issue_comment.comment_stripped - if issue_comment is not None - else "" + issue_comment.comment_stripped if issue_comment is not None else "" ), "old_identifier": ( str(issue_activity.get("old_identifier")) @@ -461,9 +414,7 @@ def notifications( "name": str(issue.name), "identifier": str(issue.project.identifier), "project_id": str(issue.project.id), - "workspace_slug": str( - issue.project.workspace.slug - ), + "workspace_slug": str(issue.project.workspace.slug), "sequence_id": issue.sequence_id, "state_name": issue.state.name, "state_group": issue.state.group, @@ -473,16 +424,10 @@ def notifications( "verb": str(issue_activity.get("verb")), "field": str(issue_activity.get("field")), "actor": str(issue_activity.get("actor_id")), - "new_value": str( - issue_activity.get("new_value") - ), - "old_value": str( - issue_activity.get("old_value") - ), + "new_value": str(issue_activity.get("new_value")), + "old_value": str(issue_activity.get("old_value")), "issue_comment": str( - issue_comment.comment_stripped - if issue_comment is not None - else "" + issue_comment.comment_stripped if issue_comment is not None else "" ), "old_identifier": ( str(issue_activity.get("old_identifier")) @@ -494,15 +439,13 @@ def notifications( if issue_activity.get("new_identifier") else None ), - "activity_time": issue_activity.get( - "created_at" - ), + "activity_time": issue_activity.get("created_at"), }, }, ) ) - # ----------------------------------------------------------------------------------------------------------------- # + # -------------------------------------------------------------------------------------------------------- # # Add Mentioned as Issue Subscribers IssueSubscriber.objects.bulk_create( @@ -511,24 +454,18 @@ def notifications( ignore_conflicts=True, ) - last_activity = ( - IssueActivity.objects.filter(issue_id=issue_id) - .order_by("-created_at") - .first() - ) + last_activity = IssueActivity.objects.filter(issue_id=issue_id).order_by("-created_at").first() actor = User.objects.get(pk=actor_id) for mention_id in comment_mentions: if mention_id != actor_id: - preference = UserNotificationPreference.objects.get( - user_id=mention_id - ) + preference = UserNotificationPreference.objects.get(user_id=mention_id) for issue_activity in issue_activities_created: notification = create_mention_notification( project=project, issue=issue, - notification_comment=f"{actor.display_name} has mentioned you in a comment in issue {issue.name}", + notification_comment=f"{actor.display_name} has mentioned you in a comment in issue {issue.name}", # noqa: E501 actor_id=actor_id, mention_id=mention_id, issue_id=issue_id, @@ -552,40 +489,26 @@ def notifications( "state_name": issue.state.name, "state_group": issue.state.group, "project_id": str(issue.project.id), - "workspace_slug": str( - issue.project.workspace.slug - ), + "workspace_slug": str(issue.project.workspace.slug), }, "issue_activity": { "id": str(issue_activity.get("id")), "verb": str(issue_activity.get("verb")), "field": str("mention"), - "actor": str( - issue_activity.get("actor_id") - ), - "new_value": str( - issue_activity.get("new_value") - ), - "old_value": str( - issue_activity.get("old_value") - ), + "actor": str(issue_activity.get("actor_id")), + "new_value": str(issue_activity.get("new_value")), + "old_value": str(issue_activity.get("old_value")), "old_identifier": ( - str( - issue_activity.get("old_identifier") - ) + str(issue_activity.get("old_identifier")) if issue_activity.get("old_identifier") else None ), "new_identifier": ( - str( - issue_activity.get("new_identifier") - ) + str(issue_activity.get("new_identifier")) if issue_activity.get("new_identifier") else None ), - "activity_time": issue_activity.get( - "created_at" - ), + "activity_time": issue_activity.get("created_at"), }, }, ) @@ -594,9 +517,7 @@ def notifications( for mention_id in new_mentions: if mention_id != actor_id: - preference = UserNotificationPreference.objects.get( - user_id=mention_id - ) + preference = UserNotificationPreference.objects.get(user_id=mention_id) if ( last_activity is not None and last_activity.field == "description" @@ -621,9 +542,7 @@ def notifications( "state_name": issue.state.name, "state_group": issue.state.group, "project_id": str(issue.project.id), - "workspace_slug": str( - issue.project.workspace.slug - ), + "workspace_slug": str(issue.project.workspace.slug), }, "issue_activity": { "id": str(last_activity.id), @@ -670,22 +589,16 @@ def notifications( "new_value": str(last_activity.new_value), "old_value": str(last_activity.old_value), "old_identifier": ( - str( - issue_activity.get("old_identifier") - ) + str(issue_activity.get("old_identifier")) if issue_activity.get("old_identifier") else None ), "new_identifier": ( - str( - issue_activity.get("new_identifier") - ) + str(issue_activity.get("new_identifier")) if issue_activity.get("new_identifier") else None ), - "activity_time": str( - last_activity.created_at - ), + "activity_time": str(last_activity.created_at), }, }, ) @@ -712,9 +625,7 @@ def notifications( "issue": { "id": str(issue_id), "name": str(issue.name), - "identifier": str( - issue.project.identifier - ), + "identifier": str(issue.project.identifier), "sequence_id": issue.sequence_id, "state_name": issue.state.name, "state_group": issue.state.group, @@ -723,47 +634,27 @@ def notifications( "id": str(issue_activity.get("id")), "verb": str(issue_activity.get("verb")), "field": str("mention"), - "actor": str( - issue_activity.get("actor_id") - ), - "new_value": str( - issue_activity.get("new_value") - ), - "old_value": str( - issue_activity.get("old_value") - ), + "actor": str(issue_activity.get("actor_id")), + "new_value": str(issue_activity.get("new_value")), + "old_value": str(issue_activity.get("old_value")), "old_identifier": ( - str( - issue_activity.get( - "old_identifier" - ) - ) - if issue_activity.get( - "old_identifier" - ) + str(issue_activity.get("old_identifier")) + if issue_activity.get("old_identifier") else None ), "new_identifier": ( - str( - issue_activity.get( - "new_identifier" - ) - ) - if issue_activity.get( - "new_identifier" - ) + str(issue_activity.get("new_identifier")) + if issue_activity.get("new_identifier") else None ), - "activity_time": issue_activity.get( - "created_at" - ), + "activity_time": issue_activity.get("created_at"), }, }, ) ) bulk_notifications.append(notification) - # save new mentions for the particular issue and remove the mentions that has been deleted from the description + # save new mentions for the particular issue and remove the mentions that has been deleted from the description # noqa: E501 update_mentions_for_issue( issue=issue, project=project, @@ -772,9 +663,7 @@ def notifications( ) # Bulk create notifications Notification.objects.bulk_create(bulk_notifications, batch_size=100) - EmailNotificationLog.objects.bulk_create( - bulk_email_logs, batch_size=100, ignore_conflicts=True - ) + EmailNotificationLog.objects.bulk_create(bulk_email_logs, batch_size=100, ignore_conflicts=True) return except Exception as e: print(e) diff --git a/apps/api/plane/bgtasks/page_transaction_task.py b/apps/api/plane/bgtasks/page_transaction_task.py index 893bbf972..09e2cb2ad 100644 --- a/apps/api/plane/bgtasks/page_transaction_task.py +++ b/apps/api/plane/bgtasks/page_transaction_task.py @@ -69,9 +69,7 @@ def page_transaction(new_value, old_value, page_id): ) # Create new PageLog objects for new transactions - PageLog.objects.bulk_create( - new_transactions, batch_size=10, ignore_conflicts=True - ) + PageLog.objects.bulk_create(new_transactions, batch_size=10, ignore_conflicts=True) # Delete the removed transactions PageLog.objects.filter(transaction__in=deleted_transaction_ids).delete() diff --git a/apps/api/plane/bgtasks/page_version_task.py b/apps/api/plane/bgtasks/page_version_task.py index ec1f6c3ca..4de2387be 100644 --- a/apps/api/plane/bgtasks/page_version_task.py +++ b/apps/api/plane/bgtasks/page_version_task.py @@ -16,9 +16,7 @@ def page_version(page_id, existing_instance, user_id): page = Page.objects.get(id=page_id) # Get the current instance - current_instance = ( - json.loads(existing_instance) if existing_instance is not None else {} - ) + current_instance = json.loads(existing_instance) if existing_instance is not None else {} # Create a version if description_html is updated if current_instance.get("description_html") != page.description_html: @@ -37,9 +35,7 @@ def page_version(page_id, existing_instance, user_id): # If page versions are greater than 20 delete the oldest one if PageVersion.objects.filter(page_id=page_id).count() > 20: # Delete the old page version - PageVersion.objects.filter(page_id=page_id).order_by( - "last_saved_at" - ).first().delete() + PageVersion.objects.filter(page_id=page_id).order_by("last_saved_at").first().delete() return except Page.DoesNotExist: diff --git a/apps/api/plane/bgtasks/project_add_user_email_task.py b/apps/api/plane/bgtasks/project_add_user_email_task.py index ab1eb0394..af6014695 100644 --- a/apps/api/plane/bgtasks/project_add_user_email_task.py +++ b/apps/api/plane/bgtasks/project_add_user_email_task.py @@ -54,9 +54,7 @@ def project_add_user_email(current_site, project_member_id, invitor_id): subject = "You have been invited to a Plane project" # Render the email template - html_content = render_to_string( - "emails/notifications/project_addition.html", context - ) + html_content = render_to_string("emails/notifications/project_addition.html", context) text_content = strip_tags(html_content) # Initialize the connection connection = get_connection( diff --git a/apps/api/plane/bgtasks/project_invitation_task.py b/apps/api/plane/bgtasks/project_invitation_task.py index 179dfa00f..b8eed5e45 100644 --- a/apps/api/plane/bgtasks/project_invitation_task.py +++ b/apps/api/plane/bgtasks/project_invitation_task.py @@ -21,11 +21,9 @@ def project_invitation(email, project_id, token, current_site, invitor): try: user = User.objects.get(email=invitor) project = Project.objects.get(pk=project_id) - project_member_invite = ProjectMemberInvite.objects.get( - token=token, email=email - ) + project_member_invite = ProjectMemberInvite.objects.get(token=token, email=email) - relativelink = f"/project-invitations/?invitation_id={project_member_invite.id}&email={email}&slug={project.workspace.slug}&project_id={str(project_id)}" + relativelink = f"/project-invitations/?invitation_id={project_member_invite.id}&email={email}&slug={project.workspace.slug}&project_id={str(project_id)}" # noqa: E501 abs_url = current_site + relativelink subject = f"{user.first_name or user.display_name or user.email} invited you to join {project.name} on Plane" @@ -37,9 +35,7 @@ def project_invitation(email, project_id, token, current_site, invitor): "invitation_url": abs_url, } - html_content = render_to_string( - "emails/invitations/project_invitation.html", context - ) + html_content = render_to_string("emails/invitations/project_invitation.html", context) text_content = strip_tags(html_content) diff --git a/apps/api/plane/bgtasks/recent_visited_task.py b/apps/api/plane/bgtasks/recent_visited_task.py index 4203867da..eda297ce4 100644 --- a/apps/api/plane/bgtasks/recent_visited_task.py +++ b/apps/api/plane/bgtasks/recent_visited_task.py @@ -30,14 +30,10 @@ def recent_visited_task(entity_name, entity_identifier, user_id, project_id, slu except DatabaseError: pass else: - recent_visited_count = UserRecentVisit.objects.filter( - user_id=user_id, workspace_id=workspace.id - ).count() + recent_visited_count = UserRecentVisit.objects.filter(user_id=user_id, workspace_id=workspace.id).count() if recent_visited_count == 20: recent_visited = ( - UserRecentVisit.objects.filter( - user_id=user_id, workspace_id=workspace.id - ) + UserRecentVisit.objects.filter(user_id=user_id, workspace_id=workspace.id) .order_by("created_at") .first() ) diff --git a/apps/api/plane/bgtasks/storage_metadata_task.py b/apps/api/plane/bgtasks/storage_metadata_task.py index f5daf73ba..ea745053f 100644 --- a/apps/api/plane/bgtasks/storage_metadata_task.py +++ b/apps/api/plane/bgtasks/storage_metadata_task.py @@ -15,9 +15,7 @@ def get_asset_object_metadata(asset_id): # Create an instance of the S3 storage storage = S3Storage() # Get the storage - asset.storage_metadata = storage.get_object_metadata( - object_name=asset.asset.name - ) + asset.storage_metadata = storage.get_object_metadata(object_name=asset.asset.name) # Save the asset asset.save(update_fields=["storage_metadata"]) return diff --git a/apps/api/plane/bgtasks/webhook_task.py b/apps/api/plane/bgtasks/webhook_task.py index ae7c30ac9..df36ce815 100644 --- a/apps/api/plane/bgtasks/webhook_task.py +++ b/apps/api/plane/bgtasks/webhook_task.py @@ -80,15 +80,11 @@ logger = logging.getLogger("plane.worker") def get_issue_prefetches(): return [ Prefetch("label_issue", queryset=IssueLabel.objects.select_related("label")), - Prefetch( - "issue_assignee", queryset=IssueAssignee.objects.select_related("assignee") - ), + Prefetch("issue_assignee", queryset=IssueAssignee.objects.select_related("assignee")), ] -def get_model_data( - event: str, event_id: Union[str, List[str]], many: bool = False -) -> Dict[str, Any]: +def get_model_data(event: str, event_id: Union[str, List[str]], many: bool = False) -> Dict[str, Any]: """ Retrieve and serialize model data based on the event type. @@ -125,15 +121,9 @@ def get_model_data( queryset = queryset.prefetch_related(*issue_prefetches) else: issue_id = queryset.id - queryset = ( - model.objects.filter(pk=issue_id) - .prefetch_related(*issue_prefetches) - .first() - ) + queryset = model.objects.filter(pk=issue_id).prefetch_related(*issue_prefetches).first() - return serializer( - queryset, many=many, context={"expand": ["labels", "assignees"]} - ).data + return serializer(queryset, many=many, context={"expand": ["labels", "assignees"]}).data else: return serializer(queryset, many=many).data except ObjectDoesNotExist: @@ -141,9 +131,7 @@ def get_model_data( @shared_task -def send_webhook_deactivation_email( - webhook_id: str, receiver_id: str, current_site: str, reason: str -) -> None: +def send_webhook_deactivation_email(webhook_id: str, receiver_id: str, current_site: str, reason: str) -> None: """ Send an email notification when a webhook is deactivated. @@ -177,9 +165,7 @@ def send_webhook_deactivation_email( "message": message, "webhook_url": f"{current_site}/{str(webhook.workspace.slug)}/settings/webhooks/{str(webhook.id)}", } - html_content = render_to_string( - "emails/notifications/webhook-deactivate.html", context - ) + html_content = render_to_string("emails/notifications/webhook-deactivate.html", context) text_content = strip_tags(html_content) # Set the email connection @@ -248,17 +234,9 @@ def webhook_send_task( } # # Your secret key - event_data = ( - json.loads(json.dumps(event_data, cls=DjangoJSONEncoder)) - if event_data is not None - else None - ) + event_data = json.loads(json.dumps(event_data, cls=DjangoJSONEncoder)) if event_data is not None else None - activity = ( - json.loads(json.dumps(activity, cls=DjangoJSONEncoder)) - if activity is not None - else None - ) + activity = json.loads(json.dumps(activity, cls=DjangoJSONEncoder)) if activity is not None else None action = { "POST": "create", @@ -405,11 +383,7 @@ def webhook_activity( webhook_id=webhook.id, slug=slug, event=event, - event_data=( - {"id": event_id} - if verb == "deleted" - else get_model_data(event=event, event_id=event_id) - ), + event_data=({"id": event_id} if verb == "deleted" else get_model_data(event=event, event_id=event_id)), action=verb, current_site=current_site, activity={ @@ -433,9 +407,7 @@ def webhook_activity( @shared_task -def model_activity( - model_name, model_id, requested_data, current_instance, actor_id, slug, origin=None -): +def model_activity(model_name, model_id, requested_data, current_instance, actor_id, slug, origin=None): """Function takes in two json and computes differences between keys of both the json""" if current_instance is None: webhook_activity.delay( @@ -454,9 +426,7 @@ def model_activity( return # Load the current instance - current_instance = ( - json.loads(current_instance) if current_instance is not None else None - ) + current_instance = json.loads(current_instance) if current_instance is not None else None # Loop through all keys in requested data and check the current value and requested value for key in requested_data: diff --git a/apps/api/plane/bgtasks/workspace_invitation_task.py b/apps/api/plane/bgtasks/workspace_invitation_task.py index c855a8ce6..f7480b36a 100644 --- a/apps/api/plane/bgtasks/workspace_invitation_task.py +++ b/apps/api/plane/bgtasks/workspace_invitation_task.py @@ -21,12 +21,12 @@ def workspace_invitation(email, workspace_id, token, current_site, inviter): user = User.objects.get(email=inviter) workspace = Workspace.objects.get(pk=workspace_id) - workspace_member_invite = WorkspaceMemberInvite.objects.get( - token=token, email=email - ) + workspace_member_invite = WorkspaceMemberInvite.objects.get(token=token, email=email) # Relative link - relative_link = f"/workspace-invitations/?invitation_id={workspace_member_invite.id}&email={email}&slug={workspace.slug}" # noqa: E501 + relative_link = ( + f"/workspace-invitations/?invitation_id={workspace_member_invite.id}&email={email}&slug={workspace.slug}" # noqa: E501 + ) # The complete url including the domain abs_url = str(current_site) + relative_link @@ -51,9 +51,7 @@ def workspace_invitation(email, workspace_id, token, current_site, inviter): "abs_url": abs_url, } - html_content = render_to_string( - "emails/invitations/workspace_invitation.html", context - ) + html_content = render_to_string("emails/invitations/workspace_invitation.html", context) text_content = strip_tags(html_content) diff --git a/apps/api/plane/bgtasks/workspace_seed_task.py b/apps/api/plane/bgtasks/workspace_seed_task.py index 6fae83e41..848646e5a 100644 --- a/apps/api/plane/bgtasks/workspace_seed_task.py +++ b/apps/api/plane/bgtasks/workspace_seed_task.py @@ -68,16 +68,12 @@ def create_project_and_member(workspace: Workspace) -> Dict[int, uuid.UUID]: project_identifier = "".join(ch for ch in workspace.name if ch.isalnum())[:5] # Create members - workspace_members = WorkspaceMember.objects.filter(workspace=workspace).values( - "member_id", "role" - ) + workspace_members = WorkspaceMember.objects.filter(workspace=workspace).values("member_id", "role") projects_map: Dict[int, uuid.UUID] = {} if not project_seeds: - logger.warning( - "Task: workspace_seed_task -> No project seeds found. Skipping project creation." - ) + logger.warning("Task: workspace_seed_task -> No project seeds found. Skipping project creation.") return projects_map for project_seed in project_seeds: @@ -140,9 +136,7 @@ def create_project_and_member(workspace: Workspace) -> Dict[int, uuid.UUID]: return projects_map -def create_project_states( - workspace: Workspace, project_map: Dict[int, uuid.UUID] -) -> Dict[int, uuid.UUID]: +def create_project_states(workspace: Workspace, project_map: Dict[int, uuid.UUID]) -> Dict[int, uuid.UUID]: """Creates states for each project in the workspace. Args: @@ -175,9 +169,7 @@ def create_project_states( return state_map -def create_project_labels( - workspace: Workspace, project_map: Dict[int, uuid.UUID] -) -> Dict[int, uuid.UUID]: +def create_project_labels(workspace: Workspace, project_map: Dict[int, uuid.UUID]) -> Dict[int, uuid.UUID]: """Creates labels for each project in the workspace. Args: @@ -234,9 +226,7 @@ def create_project_issues( # get the values for field in required_fields: if field not in issue_seed: - logger.error( - f"Task: workspace_seed_task -> Required field '{field}' missing in issue seed" - ) + logger.error(f"Task: workspace_seed_task -> Required field '{field}' missing in issue seed") continue # get the values @@ -312,12 +302,8 @@ def workspace_seed(workspace_id: uuid.UUID) -> None: # create project issues create_project_issues(workspace, project_map, state_map, label_map) - logger.info( - f"Task: workspace_seed_task -> Workspace {workspace_id} seeded successfully" - ) + logger.info(f"Task: workspace_seed_task -> Workspace {workspace_id} seeded successfully") return except Exception as e: - logger.error( - f"Task: workspace_seed_task -> Failed to seed workspace {workspace_id}: {str(e)}" - ) + logger.error(f"Task: workspace_seed_task -> Failed to seed workspace {workspace_id}: {str(e)}") raise e diff --git a/apps/api/plane/celery.py b/apps/api/plane/celery.py index 5fb136f23..828f4a6d5 100644 --- a/apps/api/plane/celery.py +++ b/apps/api/plane/celery.py @@ -79,9 +79,7 @@ app.conf.beat_schedule = { # Setup logging @after_setup_logger.connect def setup_loggers(logger, *args, **kwargs): - formatter = JsonFormatter( - '"%(levelname)s %(asctime)s %(module)s %(name)s %(message)s' - ) + formatter = JsonFormatter('"%(levelname)s %(asctime)s %(module)s %(name)s %(message)s') handler = logging.StreamHandler() handler.setFormatter(fmt=formatter) logger.addHandler(handler) @@ -89,9 +87,7 @@ def setup_loggers(logger, *args, **kwargs): @after_setup_task_logger.connect def setup_task_loggers(logger, *args, **kwargs): - formatter = JsonFormatter( - '"%(levelname)s %(asctime)s %(module)s %(name)s %(message)s' - ) + formatter = JsonFormatter('"%(levelname)s %(asctime)s %(module)s %(name)s %(message)s') handler = logging.StreamHandler() handler.setFormatter(fmt=formatter) logger.addHandler(handler) diff --git a/apps/api/plane/db/management/commands/clear_cache.py b/apps/api/plane/db/management/commands/clear_cache.py index c9189ca32..1c66b3eaf 100644 --- a/apps/api/plane/db/management/commands/clear_cache.py +++ b/apps/api/plane/db/management/commands/clear_cache.py @@ -14,9 +14,7 @@ class Command(BaseCommand): try: if options["key"]: cache.delete(options["key"]) - self.stdout.write( - self.style.SUCCESS(f"Cache Cleared for key: {options['key']}") - ) + self.stdout.write(self.style.SUCCESS(f"Cache Cleared for key: {options['key']}")) return cache.clear() diff --git a/apps/api/plane/db/management/commands/create_bucket.py b/apps/api/plane/db/management/commands/create_bucket.py index 838edd6c6..555fe0aa8 100644 --- a/apps/api/plane/db/management/commands/create_bucket.py +++ b/apps/api/plane/db/management/commands/create_bucket.py @@ -16,12 +16,8 @@ class Command(BaseCommand): s3_client = boto3.client( "s3", endpoint_url=os.environ.get("AWS_S3_ENDPOINT_URL"), # MinIO endpoint - aws_access_key_id=os.environ.get( - "AWS_ACCESS_KEY_ID" - ), # MinIO access key - aws_secret_access_key=os.environ.get( - "AWS_SECRET_ACCESS_KEY" - ), # MinIO secret key + aws_access_key_id=os.environ.get("AWS_ACCESS_KEY_ID"), # MinIO access key + aws_secret_access_key=os.environ.get("AWS_SECRET_ACCESS_KEY"), # MinIO secret key region_name=os.environ.get("AWS_REGION"), # MinIO region config=boto3.session.Config(signature_version="s3v4"), ) @@ -38,32 +34,20 @@ class Command(BaseCommand): bucket_name = os.environ.get("AWS_S3_BUCKET_NAME") if error_code == 404: # Bucket does not exist, create it - self.stdout.write( - self.style.WARNING( - f"Bucket '{bucket_name}' does not exist. Creating bucket..." - ) - ) + self.stdout.write(self.style.WARNING(f"Bucket '{bucket_name}' does not exist. Creating bucket...")) try: s3_client.create_bucket(Bucket=bucket_name) - self.stdout.write( - self.style.SUCCESS( - f"Bucket '{bucket_name}' created successfully." - ) - ) + self.stdout.write(self.style.SUCCESS(f"Bucket '{bucket_name}' created successfully.")) # Handle the exception if the bucket creation fails except ClientError as create_error: - self.stdout.write( - self.style.ERROR(f"Failed to create bucket: {create_error}") - ) + self.stdout.write(self.style.ERROR(f"Failed to create bucket: {create_error}")) # Handle the exception if access to the bucket is forbidden elif error_code == 403: # Access to the bucket is forbidden self.stdout.write( - self.style.ERROR( - f"Access to the bucket '{bucket_name}' is forbidden. Check permissions." - ) + self.style.ERROR(f"Access to the bucket '{bucket_name}' is forbidden. Check permissions.") ) else: # Another ClientError occurred diff --git a/apps/api/plane/db/management/commands/create_dummy_data.py b/apps/api/plane/db/management/commands/create_dummy_data.py index 0915cd9d8..220576b8f 100644 --- a/apps/api/plane/db/management/commands/create_dummy_data.py +++ b/apps/api/plane/db/management/commands/create_dummy_data.py @@ -23,27 +23,20 @@ class Command(BaseCommand): creator = input("Your email: ") if creator == "" or not User.objects.filter(email=creator).exists(): - raise CommandError( - "User email is required and should have signed in plane" - ) + raise CommandError("User email is required and should have signed in plane") user = User.objects.get(email=creator) members = input("Enter Member emails (comma separated): ") members = members.split(",") if members != "" else [] # Create workspace - workspace = Workspace.objects.create( - slug=workspace_slug, name=workspace_name, owner=user - ) + workspace = Workspace.objects.create(slug=workspace_slug, name=workspace_name, owner=user) # Create workspace member WorkspaceMember.objects.create(workspace=workspace, role=20, member=user) user_ids = User.objects.filter(email__in=members) _ = WorkspaceMember.objects.bulk_create( - [ - WorkspaceMember(workspace=workspace, member=user_id, role=20) - for user_id in user_ids - ], + [WorkspaceMember(workspace=workspace, member=user_id, role=20) for user_id in user_ids], ignore_conflicts=True, ) @@ -55,9 +48,7 @@ class Command(BaseCommand): cycle_count = int(input("Number of cycles to be created: ")) module_count = int(input("Number of modules to be created: ")) pages_count = int(input("Number of pages to be created: ")) - intake_issue_count = int( - input("Number of intake issues to be created: ") - ) + intake_issue_count = int(input("Number of intake issues to be created: ")) from plane.bgtasks.dummy_data_task import create_dummy_data diff --git a/apps/api/plane/db/management/commands/create_instance_admin.py b/apps/api/plane/db/management/commands/create_instance_admin.py index 8b957f7fc..8d5a912e0 100644 --- a/apps/api/plane/db/management/commands/create_instance_admin.py +++ b/apps/api/plane/db/management/commands/create_instance_admin.py @@ -28,9 +28,7 @@ class Command(BaseCommand): instance = Instance.objects.last() # Get or create an instance admin - _, created = InstanceAdmin.objects.get_or_create( - user=user, instance=instance, role=20 - ) + _, created = InstanceAdmin.objects.get_or_create(user=user, instance=instance, role=20) if not created: raise CommandError("The provided email is already an instance admin.") diff --git a/apps/api/plane/db/management/commands/create_project_member.py b/apps/api/plane/db/management/commands/create_project_member.py index 927f97e9d..d9b46524c 100644 --- a/apps/api/plane/db/management/commands/create_project_member.py +++ b/apps/api/plane/db/management/commands/create_project_member.py @@ -19,9 +19,7 @@ class Command(BaseCommand): # Positional argument parser.add_argument("--project_id", type=str, nargs="?", help="Project ID") parser.add_argument("--user_email", type=str, nargs="?", help="User Email") - parser.add_argument( - "--role", type=int, nargs="?", help="Role of the user in the project" - ) + parser.add_argument("--role", type=int, nargs="?", help="Role of the user in the project") def handle(self, *args: Any, **options: Any): try: @@ -46,16 +44,12 @@ class Command(BaseCommand): raise CommandError("Project not found") # Check if the user exists in the workspace - if not WorkspaceMember.objects.filter( - workspace=project.workspace, member=user, is_active=True - ).exists(): + if not WorkspaceMember.objects.filter(workspace=project.workspace, member=user, is_active=True).exists(): raise CommandError("User not member in workspace") # Get the smallest sort order smallest_sort_order = ( - ProjectMember.objects.filter(workspace_id=project.workspace_id) - .order_by("sort_order") - .first() + ProjectMember.objects.filter(workspace_id=project.workspace_id).order_by("sort_order").first() ) if smallest_sort_order: @@ -70,17 +64,13 @@ class Command(BaseCommand): ) else: # Create the project member - ProjectMember.objects.create( - project=project, member=user, role=role, sort_order=sort_order - ) + ProjectMember.objects.create(project=project, member=user, role=role, sort_order=sort_order) # Issue Property IssueUserProperty.objects.get_or_create(user=user, project=project) # Success message - self.stdout.write( - self.style.SUCCESS(f"User {user_email} added to project {project_id}") - ) + self.stdout.write(self.style.SUCCESS(f"User {user_email} added to project {project_id}")) return except CommandError as e: self.stdout.write(self.style.ERROR(e)) diff --git a/apps/api/plane/db/management/commands/fix_duplicate_sequences.py b/apps/api/plane/db/management/commands/fix_duplicate_sequences.py index 1bf4d4452..2b262606a 100644 --- a/apps/api/plane/db/management/commands/fix_duplicate_sequences.py +++ b/apps/api/plane/db/management/commands/fix_duplicate_sequences.py @@ -43,23 +43,15 @@ class Command(BaseCommand): issue_sequence = self.strict_str_to_int(identifier[1]) # Fetch the project - project = Project.objects.get( - identifier__iexact=project_identifier, workspace__slug=workspace_slug - ) + project = Project.objects.get(identifier__iexact=project_identifier, workspace__slug=workspace_slug) # Get the issues issues = Issue.objects.filter(project=project, sequence_id=issue_sequence) # Check if there are duplicate issues if not issues.count() > 1: - raise CommandError( - "No duplicate issues found with the given identifier" - ) + raise CommandError("No duplicate issues found with the given identifier") - self.stdout.write( - self.style.SUCCESS( - f"{issues.count()} issues found with identifier {issue_identifier}" - ) - ) + self.stdout.write(self.style.SUCCESS(f"{issues.count()} issues found with identifier {issue_identifier}")) with transaction.atomic(): # This ensures only one transaction per project can execute this code at a time lock_key = convert_uuid_to_integer(project.id) @@ -70,17 +62,14 @@ class Command(BaseCommand): cursor.execute("SELECT pg_advisory_xact_lock(%s)", [lock_key]) # Get the maximum sequence ID for the project - last_sequence = IssueSequence.objects.filter(project=project).aggregate( - largest=Max("sequence") - )["largest"] + last_sequence = IssueSequence.objects.filter(project=project).aggregate(largest=Max("sequence"))[ + "largest" + ] bulk_issues = [] bulk_issue_sequences = [] - issue_sequence_map = { - isq.issue_id: isq - for isq in IssueSequence.objects.filter(project=project) - } + issue_sequence_map = {isq.issue_id: isq for isq in IssueSequence.objects.filter(project=project)} # change the ids of duplicate issues for index, issue in enumerate(issues[1:]): diff --git a/apps/api/plane/db/management/commands/sync_issue_description_version.py b/apps/api/plane/db/management/commands/sync_issue_description_version.py index 7ff2fc391..04e608a3c 100644 --- a/apps/api/plane/db/management/commands/sync_issue_description_version.py +++ b/apps/api/plane/db/management/commands/sync_issue_description_version.py @@ -14,10 +14,6 @@ class Command(BaseCommand): batch_size = input("Enter the batch size: ") batch_countdown = input("Enter the batch countdown: ") - schedule_issue_description_version.delay( - batch_size=batch_size, countdown=int(batch_countdown) - ) + schedule_issue_description_version.delay(batch_size=batch_size, countdown=int(batch_countdown)) - self.stdout.write( - self.style.SUCCESS("Successfully created issue description version task") - ) + self.stdout.write(self.style.SUCCESS("Successfully created issue description version task")) diff --git a/apps/api/plane/db/management/commands/sync_issue_version.py b/apps/api/plane/db/management/commands/sync_issue_version.py index 2b6632f26..6c9a2cdac 100644 --- a/apps/api/plane/db/management/commands/sync_issue_version.py +++ b/apps/api/plane/db/management/commands/sync_issue_version.py @@ -12,8 +12,6 @@ class Command(BaseCommand): batch_size = input("Enter the batch size: ") batch_countdown = input("Enter the batch countdown: ") - schedule_issue_version.delay( - batch_size=batch_size, countdown=int(batch_countdown) - ) + schedule_issue_version.delay(batch_size=batch_size, countdown=int(batch_countdown)) self.stdout.write(self.style.SUCCESS("Successfully created issue version task")) diff --git a/apps/api/plane/db/management/commands/test_email.py b/apps/api/plane/db/management/commands/test_email.py index 2ed20eeb3..22841a671 100644 --- a/apps/api/plane/db/management/commands/test_email.py +++ b/apps/api/plane/db/management/commands/test_email.py @@ -60,6 +60,4 @@ class Command(BaseCommand): msg.send() self.stdout.write(self.style.SUCCESS("Email successfully sent")) except Exception as e: - self.stdout.write( - self.style.ERROR(f"Error: Email could not be delivered due to {e}") - ) + self.stdout.write(self.style.ERROR(f"Error: Email could not be delivered due to {e}")) diff --git a/apps/api/plane/db/management/commands/update_bucket.py b/apps/api/plane/db/management/commands/update_bucket.py index 27eb5c83e..ead868995 100644 --- a/apps/api/plane/db/management/commands/update_bucket.py +++ b/apps/api/plane/db/management/commands/update_bucket.py @@ -16,9 +16,7 @@ class Command(BaseCommand): "s3", endpoint_url=os.environ.get("AWS_S3_ENDPOINT_URL"), # MinIO endpoint aws_access_key_id=os.environ.get("AWS_ACCESS_KEY_ID"), # MinIO access key - aws_secret_access_key=os.environ.get( - "AWS_SECRET_ACCESS_KEY" - ), # MinIO secret key + aws_secret_access_key=os.environ.get("AWS_SECRET_ACCESS_KEY"), # MinIO secret key region_name=os.environ.get("AWS_REGION"), # MinIO region config=boto3.session.Config(signature_version="s3v4"), ) @@ -59,9 +57,7 @@ class Command(BaseCommand): # 3. Test s3:PutObject (attempt to upload an object) try: - s3_client.put_object( - Bucket=bucket_name, Key="test_permission_check.txt", Body=b"Test" - ) + s3_client.put_object(Bucket=bucket_name, Key="test_permission_check.txt", Body=b"Test") permissions["s3:PutObject"] = True # Clean up except ClientError as e: @@ -106,9 +102,7 @@ class Command(BaseCommand): if "Contents" in response: for obj in response["Contents"]: object_key = obj["Key"] - public_object_resource.append( - f"arn:aws:s3:::{bucket_name}/{object_key}" - ) + public_object_resource.append(f"arn:aws:s3:::{bucket_name}/{object_key}") bucket_policy = { "Version": "2012-10-17", "Statement": [ @@ -128,9 +122,7 @@ class Command(BaseCommand): # Get the bucket policy bucket_policy = self.generate_bucket_policy(bucket_name) # Apply the policy to the bucket - s3_client.put_bucket_policy( - Bucket=bucket_name, Policy=json.dumps(bucket_policy) - ) + s3_client.put_bucket_policy(Bucket=bucket_name, Policy=json.dumps(bucket_policy)) # Print a success message self.stdout.write("Bucket is private, but existing objects remain public.") return @@ -144,11 +136,7 @@ class Command(BaseCommand): bucket_name = os.environ.get("AWS_S3_BUCKET_NAME") if not bucket_name: - self.stdout.write( - self.style.ERROR( - "Please set the AWS_S3_BUCKET_NAME environment variable." - ) - ) + self.stdout.write(self.style.ERROR("Please set the AWS_S3_BUCKET_NAME environment variable.")) return self.stdout.write(self.style.NOTICE("Checking bucket...")) @@ -158,9 +146,7 @@ class Command(BaseCommand): except ClientError as e: error_code = e.response["Error"]["Code"] if error_code == "404": - self.stdout.write( - self.style.ERROR(f"Bucket '{bucket_name}' does not exist.") - ) + self.stdout.write(self.style.ERROR(f"Bucket '{bucket_name}' does not exist.")) return else: self.stdout.write(f"Error: {e}") @@ -177,9 +163,7 @@ class Command(BaseCommand): # If the access key has the required permissions try: if all(permissions.values()): - self.stdout.write( - self.style.SUCCESS("Access key has the required permissions.") - ) + self.stdout.write(self.style.SUCCESS("Access key has the required permissions.")) # Making the existing objects public self.make_objects_public(bucket_name) return @@ -187,18 +171,12 @@ class Command(BaseCommand): self.stdout.write(f"Error: {e}") # write the bucket policy to a file - self.stdout.write( - self.style.WARNING( - "Generating permissions.json for manual bucket policy update." - ) - ) + self.stdout.write(self.style.WARNING("Generating permissions.json for manual bucket policy update.")) try: # Writing to a file with open("permissions.json", "w") as f: f.write(json.dumps(self.generate_bucket_policy(bucket_name))) - self.stdout.write( - self.style.WARNING("Permissions have been written to permissions.json.") - ) + self.stdout.write(self.style.WARNING("Permissions have been written to permissions.json.")) return except IOError as e: self.stdout.write(f"Error writing permissions.json: {e}") diff --git a/apps/api/plane/db/management/commands/update_deleted_workspace_slug.py b/apps/api/plane/db/management/commands/update_deleted_workspace_slug.py index f4a9285ee..838325354 100644 --- a/apps/api/plane/db/management/commands/update_deleted_workspace_slug.py +++ b/apps/api/plane/db/management/commands/update_deleted_workspace_slug.py @@ -4,9 +4,7 @@ from plane.db.models import Workspace class Command(BaseCommand): - help = ( - "Updates the slug of a soft-deleted workspace by appending the epoch timestamp" - ) + help = "Updates the slug of a soft-deleted workspace by appending the epoch timestamp" def add_arguments(self, parser): parser.add_argument( @@ -28,17 +26,13 @@ class Command(BaseCommand): try: workspace = Workspace.all_objects.get(slug=slug) except Workspace.DoesNotExist: - self.stdout.write( - self.style.ERROR(f"Workspace with slug '{slug}' not found.") - ) + self.stdout.write(self.style.ERROR(f"Workspace with slug '{slug}' not found.")) return # Check if the workspace is soft-deleted if workspace.deleted_at is None: self.stdout.write( - self.style.WARNING( - f"Workspace '{workspace.name}' (slug: {workspace.slug}) is not deleted." - ) + self.style.WARNING(f"Workspace '{workspace.name}' (slug: {workspace.slug}) is not deleted.") ) return @@ -58,9 +52,7 @@ class Command(BaseCommand): new_slug = f"{workspace.slug}__{deletion_timestamp}" if dry_run: - self.stdout.write( - f"Would update workspace '{workspace.name}' slug from '{workspace.slug}' to '{new_slug}'" - ) + self.stdout.write(f"Would update workspace '{workspace.name}' slug from '{workspace.slug}' to '{new_slug}'") else: try: with transaction.atomic(): @@ -72,8 +64,4 @@ class Command(BaseCommand): ) ) except Exception as e: - self.stdout.write( - self.style.ERROR( - f"Error updating workspace '{workspace.name}': {str(e)}" - ) - ) + self.stdout.write(self.style.ERROR(f"Error updating workspace '{workspace.name}': {str(e)}")) diff --git a/apps/api/plane/db/management/commands/wait_for_migrations.py b/apps/api/plane/db/management/commands/wait_for_migrations.py index 91c8a4ce8..13b251de5 100644 --- a/apps/api/plane/db/management/commands/wait_for_migrations.py +++ b/apps/api/plane/db/management/commands/wait_for_migrations.py @@ -13,9 +13,7 @@ class Command(BaseCommand): self.stdout.write("Waiting for database migrations to complete...") time.sleep(10) # wait for 10 seconds before checking again - self.stdout.write( - self.style.SUCCESS("No migrations Pending. Starting processes ...") - ) + self.stdout.write(self.style.SUCCESS("No migrations Pending. Starting processes ...")) def _pending_migrations(self): connection = connections[DEFAULT_DB_ALIAS] diff --git a/apps/api/plane/db/mixins.py b/apps/api/plane/db/mixins.py index b198de121..ca3c9a2d3 100644 --- a/apps/api/plane/db/mixins.py +++ b/apps/api/plane/db/mixins.py @@ -48,9 +48,7 @@ class SoftDeletionQuerySet(models.QuerySet): class SoftDeletionManager(models.Manager): def get_queryset(self): - return SoftDeletionQuerySet(self.model, using=self._db).filter( - deleted_at__isnull=True - ) + return SoftDeletionQuerySet(self.model, using=self._db).filter(deleted_at__isnull=True) class SoftDeleteModel(models.Model): @@ -70,9 +68,7 @@ class SoftDeleteModel(models.Model): self.deleted_at = timezone.now() self.save(using=using) - soft_delete_related_objects.delay( - self._meta.app_label, self._meta.model_name, self.pk, using=using - ) + soft_delete_related_objects.delay(self._meta.app_label, self._meta.model_name, self.pk, using=using) else: # Perform hard delete if soft deletion is not enabled diff --git a/apps/api/plane/db/models/__init__.py b/apps/api/plane/db/models/__init__.py index de8af54e4..fcf77b936 100644 --- a/apps/api/plane/db/models/__init__.py +++ b/apps/api/plane/db/models/__init__.py @@ -84,4 +84,4 @@ from .device import Device, DeviceSession from .sticky import Sticky -from .description import Description, DescriptionVersion \ No newline at end of file +from .description import Description, DescriptionVersion diff --git a/apps/api/plane/db/models/analytic.py b/apps/api/plane/db/models/analytic.py index 68747e8c4..0efcb957f 100644 --- a/apps/api/plane/db/models/analytic.py +++ b/apps/api/plane/db/models/analytic.py @@ -5,9 +5,7 @@ from .base import BaseModel class AnalyticView(BaseModel): - workspace = models.ForeignKey( - "db.Workspace", related_name="analytics", on_delete=models.CASCADE - ) + workspace = models.ForeignKey("db.Workspace", related_name="analytics", on_delete=models.CASCADE) name = models.CharField(max_length=255) description = models.TextField(blank=True) query = models.JSONField() diff --git a/apps/api/plane/db/models/api.py b/apps/api/plane/db/models/api.py index 01be8e643..7d040ebc2 100644 --- a/apps/api/plane/db/models/api.py +++ b/apps/api/plane/db/models/api.py @@ -24,20 +24,12 @@ class APIToken(BaseModel): last_used = models.DateTimeField(null=True) # Token - token = models.CharField( - max_length=255, unique=True, default=generate_token, db_index=True - ) + token = models.CharField(max_length=255, unique=True, default=generate_token, db_index=True) # User Information - user = models.ForeignKey( - settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name="bot_tokens" - ) - user_type = models.PositiveSmallIntegerField( - choices=((0, "Human"), (1, "Bot")), default=0 - ) - workspace = models.ForeignKey( - "db.Workspace", related_name="api_tokens", on_delete=models.CASCADE, null=True - ) + user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name="bot_tokens") + user_type = models.PositiveSmallIntegerField(choices=((0, "Human"), (1, "Bot")), default=0) + workspace = models.ForeignKey("db.Workspace", related_name="api_tokens", on_delete=models.CASCADE, null=True) expired_at = models.DateTimeField(blank=True, null=True) is_service = models.BooleanField(default=False) diff --git a/apps/api/plane/db/models/asset.py b/apps/api/plane/db/models/asset.py index 965262482..1de0f18b4 100644 --- a/apps/api/plane/db/models/asset.py +++ b/apps/api/plane/db/models/asset.py @@ -40,27 +40,13 @@ class FileAsset(BaseModel): attributes = models.JSONField(default=dict) asset = models.FileField(upload_to=get_upload_path, max_length=800) - user = models.ForeignKey( - "db.User", on_delete=models.CASCADE, null=True, related_name="assets" - ) - workspace = models.ForeignKey( - "db.Workspace", on_delete=models.CASCADE, null=True, related_name="assets" - ) - draft_issue = models.ForeignKey( - "db.DraftIssue", on_delete=models.CASCADE, null=True, related_name="assets" - ) - project = models.ForeignKey( - "db.Project", on_delete=models.CASCADE, null=True, related_name="assets" - ) - issue = models.ForeignKey( - "db.Issue", on_delete=models.CASCADE, null=True, related_name="assets" - ) - comment = models.ForeignKey( - "db.IssueComment", on_delete=models.CASCADE, null=True, related_name="assets" - ) - page = models.ForeignKey( - "db.Page", on_delete=models.CASCADE, null=True, related_name="assets" - ) + user = models.ForeignKey("db.User", on_delete=models.CASCADE, null=True, related_name="assets") + workspace = models.ForeignKey("db.Workspace", on_delete=models.CASCADE, null=True, related_name="assets") + draft_issue = models.ForeignKey("db.DraftIssue", on_delete=models.CASCADE, null=True, related_name="assets") + project = models.ForeignKey("db.Project", on_delete=models.CASCADE, null=True, related_name="assets") + issue = models.ForeignKey("db.Issue", on_delete=models.CASCADE, null=True, related_name="assets") + comment = models.ForeignKey("db.IssueComment", on_delete=models.CASCADE, null=True, related_name="assets") + page = models.ForeignKey("db.Page", on_delete=models.CASCADE, null=True, related_name="assets") entity_type = models.CharField(max_length=255, null=True, blank=True) entity_identifier = models.CharField(max_length=255, null=True, blank=True) is_deleted = models.BooleanField(default=False) @@ -78,12 +64,8 @@ class FileAsset(BaseModel): ordering = ("-created_at",) indexes = [ models.Index(fields=["entity_type"], name="asset_entity_type_idx"), - models.Index( - fields=["entity_identifier"], name="asset_entity_identifier_idx" - ), - models.Index( - fields=["entity_type", "entity_identifier"], name="asset_entity_idx" - ), + models.Index(fields=["entity_identifier"], name="asset_entity_identifier_idx"), + models.Index(fields=["entity_type", "entity_identifier"], name="asset_entity_idx"), ] def __str__(self): @@ -100,7 +82,7 @@ class FileAsset(BaseModel): return f"/api/assets/v2/static/{self.id}/" if self.entity_type == self.EntityTypeContext.ISSUE_ATTACHMENT: - return f"/api/assets/v2/workspaces/{self.workspace.slug}/projects/{self.project_id}/issues/{self.issue_id}/attachments/{self.id}/" + return f"/api/assets/v2/workspaces/{self.workspace.slug}/projects/{self.project_id}/issues/{self.issue_id}/attachments/{self.id}/" # noqa: E501 if self.entity_type in [ self.EntityTypeContext.ISSUE_DESCRIPTION, diff --git a/apps/api/plane/db/models/base.py b/apps/api/plane/db/models/base.py index 558c25a40..468af8261 100644 --- a/apps/api/plane/db/models/base.py +++ b/apps/api/plane/db/models/base.py @@ -11,9 +11,7 @@ from ..mixins import AuditModel class BaseModel(AuditModel): - id = models.UUIDField( - default=uuid.uuid4, unique=True, editable=False, db_index=True, primary_key=True - ) + id = models.UUIDField(default=uuid.uuid4, unique=True, editable=False, db_index=True, primary_key=True) class Meta: abstract = True diff --git a/apps/api/plane/db/models/cycle.py b/apps/api/plane/db/models/cycle.py index 9e45028c5..bdffd283d 100644 --- a/apps/api/plane/db/models/cycle.py +++ b/apps/api/plane/db/models/cycle.py @@ -102,12 +102,8 @@ class CycleIssue(ProjectBaseModel): Cycle Issues """ - issue = models.ForeignKey( - "db.Issue", on_delete=models.CASCADE, related_name="issue_cycle" - ) - cycle = models.ForeignKey( - Cycle, on_delete=models.CASCADE, related_name="issue_cycle" - ) + issue = models.ForeignKey("db.Issue", on_delete=models.CASCADE, related_name="issue_cycle") + cycle = models.ForeignKey(Cycle, on_delete=models.CASCADE, related_name="issue_cycle") class Meta: unique_together = ["issue", "cycle", "deleted_at"] @@ -128,9 +124,7 @@ class CycleIssue(ProjectBaseModel): class CycleUserProperties(ProjectBaseModel): - cycle = models.ForeignKey( - "db.Cycle", on_delete=models.CASCADE, related_name="cycle_user_properties" - ) + cycle = models.ForeignKey("db.Cycle", on_delete=models.CASCADE, related_name="cycle_user_properties") user = models.ForeignKey( settings.AUTH_USER_MODEL, on_delete=models.CASCADE, diff --git a/apps/api/plane/db/models/deploy_board.py b/apps/api/plane/db/models/deploy_board.py index f053f4a82..da9c0d698 100644 --- a/apps/api/plane/db/models/deploy_board.py +++ b/apps/api/plane/db/models/deploy_board.py @@ -25,14 +25,10 @@ class DeployBoard(WorkspaceBaseModel): entity_identifier = models.UUIDField(null=True) entity_name = models.CharField(max_length=30, null=True, blank=True) - anchor = models.CharField( - max_length=255, default=get_anchor, unique=True, db_index=True - ) + anchor = models.CharField(max_length=255, default=get_anchor, unique=True, db_index=True) is_comments_enabled = models.BooleanField(default=False) is_reactions_enabled = models.BooleanField(default=False) - intake = models.ForeignKey( - "db.Intake", related_name="publish_intake", on_delete=models.SET_NULL, null=True - ) + intake = models.ForeignKey("db.Intake", related_name="publish_intake", on_delete=models.SET_NULL, null=True) is_votes_enabled = models.BooleanField(default=False) view_props = models.JSONField(default=dict) is_activity_enabled = models.BooleanField(default=True) diff --git a/apps/api/plane/db/models/description.py b/apps/api/plane/db/models/description.py index 24c15d395..6c298546a 100644 --- a/apps/api/plane/db/models/description.py +++ b/apps/api/plane/db/models/description.py @@ -4,8 +4,6 @@ from .workspace import WorkspaceBaseModel class Description(WorkspaceBaseModel): - - description_json = models.JSONField(default=dict, blank=True) description_html = models.TextField(blank=True, default="

") description_binary = models.BinaryField(null=True) @@ -32,9 +30,7 @@ class DescriptionVersion(WorkspaceBaseModel): DescriptionVersion is a model used to store historical versions of a Description. """ - description = models.ForeignKey( - "db.Description", on_delete=models.CASCADE, related_name="versions" - ) + description = models.ForeignKey("db.Description", on_delete=models.CASCADE, related_name="versions") description_json = models.JSONField(default=dict, blank=True) description_html = models.TextField(blank=True, default="

") description_binary = models.BinaryField(null=True) diff --git a/apps/api/plane/db/models/device.py b/apps/api/plane/db/models/device.py index 055d8ccc4..adcf7974a 100644 --- a/apps/api/plane/db/models/device.py +++ b/apps/api/plane/db/models/device.py @@ -11,9 +11,7 @@ class Device(BaseModel): WEB = "WEB", "Web" DESKTOP = "DESKTOP", "Desktop" - user = models.ForeignKey( - settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name="devices" - ) + user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name="devices") device_id = models.CharField(max_length=255, blank=True, null=True) device_type = models.CharField(max_length=255, choices=DeviceType.choices) push_token = models.CharField(max_length=255, blank=True, null=True) @@ -26,12 +24,8 @@ class Device(BaseModel): class DeviceSession(BaseModel): - device = models.ForeignKey( - Device, on_delete=models.CASCADE, related_name="sessions" - ) - session = models.ForeignKey( - "db.Session", on_delete=models.CASCADE, related_name="device_sessions" - ) + device = models.ForeignKey(Device, on_delete=models.CASCADE, related_name="sessions") + session = models.ForeignKey("db.Session", on_delete=models.CASCADE, related_name="device_sessions") is_active = models.BooleanField(default=True) user_agent = models.CharField(max_length=255, null=True, blank=True) ip_address = models.GenericIPAddressField(null=True, blank=True) diff --git a/apps/api/plane/db/models/draft.py b/apps/api/plane/db/models/draft.py index 42148d5bb..55dbb61df 100644 --- a/apps/api/plane/db/models/draft.py +++ b/apps/api/plane/db/models/draft.py @@ -38,9 +38,7 @@ class DraftIssue(WorkspaceBaseModel): null=True, blank=True, ) - name = models.CharField( - max_length=255, verbose_name="Issue Name", blank=True, null=True - ) + name = models.CharField(max_length=255, verbose_name="Issue Name", blank=True, null=True) description = models.JSONField(blank=True, default=dict) description_html = models.TextField(blank=True, default="

") description_stripped = models.TextField(blank=True, null=True) @@ -60,9 +58,7 @@ class DraftIssue(WorkspaceBaseModel): through="DraftIssueAssignee", through_fields=("draft_issue", "assignee"), ) - labels = models.ManyToManyField( - "db.Label", blank=True, related_name="draft_labels", through="DraftIssueLabel" - ) + labels = models.ManyToManyField("db.Label", blank=True, related_name="draft_labels", through="DraftIssueLabel") sort_order = models.FloatField(default=65535) completed_at = models.DateTimeField(null=True) external_source = models.CharField(max_length=255, null=True, blank=True) @@ -90,9 +86,7 @@ class DraftIssue(WorkspaceBaseModel): ~models.Q(is_triage=True), project=self.project, default=True ).first() if default_state is None: - random_state = State.objects.filter( - ~models.Q(is_triage=True), project=self.project - ).first() + random_state = State.objects.filter(~models.Q(is_triage=True), project=self.project).first() self.state = random_state else: self.state = default_state @@ -116,9 +110,9 @@ class DraftIssue(WorkspaceBaseModel): if (self.description_html == "" or self.description_html is None) else strip_tags(self.description_html) ) - largest_sort_order = DraftIssue.objects.filter( - project=self.project, state=self.state - ).aggregate(largest=models.Max("sort_order"))["largest"] + largest_sort_order = DraftIssue.objects.filter(project=self.project, state=self.state).aggregate( + largest=models.Max("sort_order") + )["largest"] if largest_sort_order is not None: self.sort_order = largest_sort_order + 10000 @@ -139,9 +133,7 @@ class DraftIssue(WorkspaceBaseModel): class DraftIssueAssignee(WorkspaceBaseModel): - draft_issue = models.ForeignKey( - DraftIssue, on_delete=models.CASCADE, related_name="draft_issue_assignee" - ) + draft_issue = models.ForeignKey(DraftIssue, on_delete=models.CASCADE, related_name="draft_issue_assignee") assignee = models.ForeignKey( settings.AUTH_USER_MODEL, on_delete=models.CASCADE, @@ -167,12 +159,8 @@ class DraftIssueAssignee(WorkspaceBaseModel): class DraftIssueLabel(WorkspaceBaseModel): - draft_issue = models.ForeignKey( - "db.DraftIssue", on_delete=models.CASCADE, related_name="draft_label_issue" - ) - label = models.ForeignKey( - "db.Label", on_delete=models.CASCADE, related_name="draft_label_issue" - ) + draft_issue = models.ForeignKey("db.DraftIssue", on_delete=models.CASCADE, related_name="draft_label_issue") + label = models.ForeignKey("db.Label", on_delete=models.CASCADE, related_name="draft_label_issue") class Meta: verbose_name = "Draft Issue Label" @@ -185,12 +173,8 @@ class DraftIssueLabel(WorkspaceBaseModel): class DraftIssueModule(WorkspaceBaseModel): - module = models.ForeignKey( - "db.Module", on_delete=models.CASCADE, related_name="draft_issue_module" - ) - draft_issue = models.ForeignKey( - "db.DraftIssue", on_delete=models.CASCADE, related_name="draft_issue_module" - ) + module = models.ForeignKey("db.Module", on_delete=models.CASCADE, related_name="draft_issue_module") + draft_issue = models.ForeignKey("db.DraftIssue", on_delete=models.CASCADE, related_name="draft_issue_module") class Meta: unique_together = ["draft_issue", "module", "deleted_at"] @@ -215,12 +199,8 @@ class DraftIssueCycle(WorkspaceBaseModel): Draft Issue Cycles """ - draft_issue = models.ForeignKey( - "db.DraftIssue", on_delete=models.CASCADE, related_name="draft_issue_cycle" - ) - cycle = models.ForeignKey( - "db.Cycle", on_delete=models.CASCADE, related_name="draft_issue_cycle" - ) + draft_issue = models.ForeignKey("db.DraftIssue", on_delete=models.CASCADE, related_name="draft_issue_cycle") + cycle = models.ForeignKey("db.Cycle", on_delete=models.CASCADE, related_name="draft_issue_cycle") class Meta: unique_together = ["draft_issue", "cycle", "deleted_at"] diff --git a/apps/api/plane/db/models/estimate.py b/apps/api/plane/db/models/estimate.py index b0097562d..9373fb320 100644 --- a/apps/api/plane/db/models/estimate.py +++ b/apps/api/plane/db/models/estimate.py @@ -33,12 +33,8 @@ class Estimate(ProjectBaseModel): class EstimatePoint(ProjectBaseModel): - estimate = models.ForeignKey( - "db.Estimate", on_delete=models.CASCADE, related_name="points" - ) - key = models.IntegerField( - default=0, validators=[MinValueValidator(0), MaxValueValidator(12)] - ) + estimate = models.ForeignKey("db.Estimate", on_delete=models.CASCADE, related_name="points") + key = models.IntegerField(default=0, validators=[MinValueValidator(0), MaxValueValidator(12)]) description = models.TextField(blank=True) value = models.CharField(max_length=255) diff --git a/apps/api/plane/db/models/exporter.py b/apps/api/plane/db/models/exporter.py index 40c13576d..8ad9daad7 100644 --- a/apps/api/plane/db/models/exporter.py +++ b/apps/api/plane/db/models/exporter.py @@ -18,9 +18,7 @@ def generate_token(): class ExporterHistory(BaseModel): - name = models.CharField( - max_length=255, verbose_name="Exporter Name", null=True, blank=True - ) + name = models.CharField(max_length=255, verbose_name="Exporter Name", null=True, blank=True) type = models.CharField( max_length=50, default="issue_exports", @@ -29,13 +27,9 @@ class ExporterHistory(BaseModel): ("issue_worklogs", "Issue Worklogs"), ), ) - workspace = models.ForeignKey( - "db.WorkSpace", on_delete=models.CASCADE, related_name="workspace_exporters" - ) + workspace = models.ForeignKey("db.WorkSpace", on_delete=models.CASCADE, related_name="workspace_exporters") project = ArrayField(models.UUIDField(default=uuid.uuid4), blank=True, null=True) - provider = models.CharField( - max_length=50, choices=(("json", "json"), ("csv", "csv"), ("xlsx", "xlsx")) - ) + provider = models.CharField(max_length=50, choices=(("json", "json"), ("csv", "csv"), ("xlsx", "xlsx"))) status = models.CharField( max_length=50, choices=( diff --git a/apps/api/plane/db/models/favorite.py b/apps/api/plane/db/models/favorite.py index 165072088..de2b101a0 100644 --- a/apps/api/plane/db/models/favorite.py +++ b/apps/api/plane/db/models/favorite.py @@ -12,9 +12,7 @@ class UserFavorite(WorkspaceBaseModel): UserFavorite (model): To store all the favorites of the user """ - user = models.ForeignKey( - settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name="favorites" - ) + user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name="favorites") entity_type = models.CharField(max_length=100) entity_identifier = models.UUIDField(null=True, blank=True) name = models.CharField(max_length=255, blank=True, null=True) @@ -43,24 +41,20 @@ class UserFavorite(WorkspaceBaseModel): ordering = ("-created_at",) indexes = [ models.Index(fields=["entity_type"], name="fav_entity_type_idx"), - models.Index( - fields=["entity_identifier"], name="fav_entity_identifier_idx" - ), - models.Index( - fields=["entity_type", "entity_identifier"], name="fav_entity_idx" - ), + models.Index(fields=["entity_identifier"], name="fav_entity_identifier_idx"), + models.Index(fields=["entity_type", "entity_identifier"], name="fav_entity_idx"), ] def save(self, *args, **kwargs): if self._state.adding: if self.project: - largest_sequence = UserFavorite.objects.filter( - workspace=self.project.workspace - ).aggregate(largest=models.Max("sequence"))["largest"] + largest_sequence = UserFavorite.objects.filter(workspace=self.project.workspace).aggregate( + largest=models.Max("sequence") + )["largest"] else: - largest_sequence = UserFavorite.objects.filter( - workspace=self.workspace - ).aggregate(largest=models.Max("sequence"))["largest"] + largest_sequence = UserFavorite.objects.filter(workspace=self.workspace).aggregate( + largest=models.Max("sequence") + )["largest"] if largest_sequence is not None: self.sequence = largest_sequence + 10000 diff --git a/apps/api/plane/db/models/importer.py b/apps/api/plane/db/models/importer.py index df93b95d1..9bcea8cf0 100644 --- a/apps/api/plane/db/models/importer.py +++ b/apps/api/plane/db/models/importer.py @@ -7,9 +7,7 @@ from .project import ProjectBaseModel class Importer(ProjectBaseModel): - service = models.CharField( - max_length=50, choices=(("github", "GitHub"), ("jira", "Jira")) - ) + service = models.CharField(max_length=50, choices=(("github", "GitHub"), ("jira", "Jira"))) status = models.CharField( max_length=50, choices=( @@ -20,15 +18,11 @@ class Importer(ProjectBaseModel): ), default="queued", ) - initiated_by = models.ForeignKey( - settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name="imports" - ) + initiated_by = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name="imports") metadata = models.JSONField(default=dict) config = models.JSONField(default=dict) data = models.JSONField(default=dict) - token = models.ForeignKey( - "db.APIToken", on_delete=models.CASCADE, related_name="importer" - ) + token = models.ForeignKey("db.APIToken", on_delete=models.CASCADE, related_name="importer") imported_data = models.JSONField(null=True) class Meta: diff --git a/apps/api/plane/db/models/intake.py b/apps/api/plane/db/models/intake.py index c6c366c9e..c3369ae1d 100644 --- a/apps/api/plane/db/models/intake.py +++ b/apps/api/plane/db/models/intake.py @@ -44,12 +44,8 @@ class IntakeIssueStatus(models.IntegerChoices): class IntakeIssue(ProjectBaseModel): - intake = models.ForeignKey( - "db.Intake", related_name="issue_intake", on_delete=models.CASCADE - ) - issue = models.ForeignKey( - "db.Issue", related_name="issue_intake", on_delete=models.CASCADE - ) + intake = models.ForeignKey("db.Intake", related_name="issue_intake", on_delete=models.CASCADE) + issue = models.ForeignKey("db.Issue", related_name="issue_intake", on_delete=models.CASCADE) status = models.IntegerField( choices=( (-2, "Pending"), diff --git a/apps/api/plane/db/models/integration/base.py b/apps/api/plane/db/models/integration/base.py index 61dad67b0..296c3cf6d 100644 --- a/apps/api/plane/db/models/integration/base.py +++ b/apps/api/plane/db/models/integration/base.py @@ -10,14 +10,10 @@ from plane.db.mixins import AuditModel class Integration(AuditModel): - id = models.UUIDField( - default=uuid.uuid4, unique=True, editable=False, db_index=True, primary_key=True - ) + id = models.UUIDField(default=uuid.uuid4, unique=True, editable=False, db_index=True, primary_key=True) title = models.CharField(max_length=400) provider = models.CharField(max_length=400, unique=True) - network = models.PositiveIntegerField( - default=1, choices=((1, "Private"), (2, "Public")) - ) + network = models.PositiveIntegerField(default=1, choices=((1, "Private"), (2, "Public"))) description = models.JSONField(default=dict) author = models.CharField(max_length=400, blank=True) webhook_url = models.TextField(blank=True) @@ -39,19 +35,11 @@ class Integration(AuditModel): class WorkspaceIntegration(BaseModel): - workspace = models.ForeignKey( - "db.Workspace", related_name="workspace_integrations", on_delete=models.CASCADE - ) + workspace = models.ForeignKey("db.Workspace", related_name="workspace_integrations", on_delete=models.CASCADE) # Bot user - actor = models.ForeignKey( - "db.User", related_name="integrations", on_delete=models.CASCADE - ) - integration = models.ForeignKey( - "db.Integration", related_name="integrated_workspaces", on_delete=models.CASCADE - ) - api_token = models.ForeignKey( - "db.APIToken", related_name="integrations", on_delete=models.CASCADE - ) + actor = models.ForeignKey("db.User", related_name="integrations", on_delete=models.CASCADE) + integration = models.ForeignKey("db.Integration", related_name="integrated_workspaces", on_delete=models.CASCADE) + api_token = models.ForeignKey("db.APIToken", related_name="integrations", on_delete=models.CASCADE) metadata = models.JSONField(default=dict) config = models.JSONField(default=dict) diff --git a/apps/api/plane/db/models/integration/github.py b/apps/api/plane/db/models/integration/github.py index 410972404..ba278497e 100644 --- a/apps/api/plane/db/models/integration/github.py +++ b/apps/api/plane/db/models/integration/github.py @@ -26,20 +26,14 @@ class GithubRepository(ProjectBaseModel): class GithubRepositorySync(ProjectBaseModel): - repository = models.OneToOneField( - "db.GithubRepository", on_delete=models.CASCADE, related_name="syncs" - ) + repository = models.OneToOneField("db.GithubRepository", on_delete=models.CASCADE, related_name="syncs") credentials = models.JSONField(default=dict) # Bot user - actor = models.ForeignKey( - "db.User", related_name="user_syncs", on_delete=models.CASCADE - ) + actor = models.ForeignKey("db.User", related_name="user_syncs", on_delete=models.CASCADE) workspace_integration = models.ForeignKey( "db.WorkspaceIntegration", related_name="github_syncs", on_delete=models.CASCADE ) - label = models.ForeignKey( - "db.Label", on_delete=models.SET_NULL, null=True, related_name="repo_syncs" - ) + label = models.ForeignKey("db.Label", on_delete=models.SET_NULL, null=True, related_name="repo_syncs") def __str__(self): """Return the repo sync""" @@ -57,12 +51,8 @@ class GithubIssueSync(ProjectBaseModel): repo_issue_id = models.BigIntegerField() github_issue_id = models.BigIntegerField() issue_url = models.URLField(blank=False) - issue = models.ForeignKey( - "db.Issue", related_name="github_syncs", on_delete=models.CASCADE - ) - repository_sync = models.ForeignKey( - "db.GithubRepositorySync", related_name="issue_syncs", on_delete=models.CASCADE - ) + issue = models.ForeignKey("db.Issue", related_name="github_syncs", on_delete=models.CASCADE) + repository_sync = models.ForeignKey("db.GithubRepositorySync", related_name="issue_syncs", on_delete=models.CASCADE) def __str__(self): """Return the github issue sync""" @@ -78,12 +68,8 @@ class GithubIssueSync(ProjectBaseModel): class GithubCommentSync(ProjectBaseModel): repo_comment_id = models.BigIntegerField() - comment = models.ForeignKey( - "db.IssueComment", related_name="comment_syncs", on_delete=models.CASCADE - ) - issue_sync = models.ForeignKey( - "db.GithubIssueSync", related_name="comment_syncs", on_delete=models.CASCADE - ) + comment = models.ForeignKey("db.IssueComment", related_name="comment_syncs", on_delete=models.CASCADE) + issue_sync = models.ForeignKey("db.GithubIssueSync", related_name="comment_syncs", on_delete=models.CASCADE) def __str__(self): """Return the github issue sync""" diff --git a/apps/api/plane/db/models/issue.py b/apps/api/plane/db/models/issue.py index 2baf8ace1..9412a57c6 100644 --- a/apps/api/plane/db/models/issue.py +++ b/apps/api/plane/db/models/issue.py @@ -123,9 +123,7 @@ class Issue(ProjectBaseModel): blank=True, related_name="state_issue", ) - point = models.IntegerField( - validators=[MinValueValidator(0), MaxValueValidator(12)], null=True, blank=True - ) + point = models.IntegerField(validators=[MinValueValidator(0), MaxValueValidator(12)], null=True, blank=True) estimate_point = models.ForeignKey( "db.EstimatePoint", on_delete=models.SET_NULL, @@ -154,9 +152,7 @@ class Issue(ProjectBaseModel): through_fields=("issue", "assignee"), ) sequence_id = models.IntegerField(default=1, verbose_name="Issue Sequence ID") - labels = models.ManyToManyField( - "db.Label", blank=True, related_name="labels", through="IssueLabel" - ) + labels = models.ManyToManyField("db.Label", blank=True, related_name="labels", through="IssueLabel") sort_order = models.FloatField(default=65535) completed_at = models.DateTimeField(null=True) archived_at = models.DateField(null=True) @@ -188,9 +184,7 @@ class Issue(ProjectBaseModel): ~models.Q(is_triage=True), project=self.project, default=True ).first() if default_state is None: - random_state = State.objects.filter( - ~models.Q(is_triage=True), project=self.project - ).first() + random_state = State.objects.filter(~models.Q(is_triage=True), project=self.project).first() self.state = random_state else: self.state = default_state @@ -219,29 +213,25 @@ class Issue(ProjectBaseModel): try: # Get the last sequence for the project - last_sequence = IssueSequence.objects.filter( - project=self.project - ).aggregate(largest=models.Max("sequence"))["largest"] + last_sequence = IssueSequence.objects.filter(project=self.project).aggregate( + largest=models.Max("sequence") + )["largest"] self.sequence_id = last_sequence + 1 if last_sequence else 1 # Strip the html tags using html parser self.description_stripped = ( None - if ( - self.description_html == "" or self.description_html is None - ) + if (self.description_html == "" or self.description_html is None) else strip_tags(self.description_html) ) - largest_sort_order = Issue.objects.filter( - project=self.project, state=self.state - ).aggregate(largest=models.Max("sort_order"))["largest"] + largest_sort_order = Issue.objects.filter(project=self.project, state=self.state).aggregate( + largest=models.Max("sort_order") + )["largest"] if largest_sort_order is not None: self.sort_order = largest_sort_order + 10000 super(Issue, self).save(*args, **kwargs) - IssueSequence.objects.create( - issue=self, sequence=self.sequence_id, project=self.project - ) + IssueSequence.objects.create(issue=self, sequence=self.sequence_id, project=self.project) finally: # Release the lock with connection.cursor() as cursor: @@ -261,12 +251,8 @@ class Issue(ProjectBaseModel): class IssueBlocker(ProjectBaseModel): - block = models.ForeignKey( - Issue, related_name="blocker_issues", on_delete=models.CASCADE - ) - blocked_by = models.ForeignKey( - Issue, related_name="blocked_issues", on_delete=models.CASCADE - ) + block = models.ForeignKey(Issue, related_name="blocker_issues", on_delete=models.CASCADE) + blocked_by = models.ForeignKey(Issue, related_name="blocked_issues", on_delete=models.CASCADE) class Meta: verbose_name = "Issue Blocker" @@ -288,12 +274,8 @@ class IssueRelationChoices(models.TextChoices): class IssueRelation(ProjectBaseModel): - issue = models.ForeignKey( - Issue, related_name="issue_relation", on_delete=models.CASCADE - ) - related_issue = models.ForeignKey( - Issue, related_name="issue_related", on_delete=models.CASCADE - ) + issue = models.ForeignKey(Issue, related_name="issue_relation", on_delete=models.CASCADE) + related_issue = models.ForeignKey(Issue, related_name="issue_related", on_delete=models.CASCADE) relation_type = models.CharField( max_length=20, verbose_name="Issue Relation Type", @@ -319,12 +301,8 @@ class IssueRelation(ProjectBaseModel): class IssueMention(ProjectBaseModel): - issue = models.ForeignKey( - Issue, on_delete=models.CASCADE, related_name="issue_mention" - ) - mention = models.ForeignKey( - settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name="issue_mention" - ) + issue = models.ForeignKey(Issue, on_delete=models.CASCADE, related_name="issue_mention") + mention = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name="issue_mention") class Meta: unique_together = ["issue", "mention", "deleted_at"] @@ -345,9 +323,7 @@ class IssueMention(ProjectBaseModel): class IssueAssignee(ProjectBaseModel): - issue = models.ForeignKey( - Issue, on_delete=models.CASCADE, related_name="issue_assignee" - ) + issue = models.ForeignKey(Issue, on_delete=models.CASCADE, related_name="issue_assignee") assignee = models.ForeignKey( settings.AUTH_USER_MODEL, on_delete=models.CASCADE, @@ -375,9 +351,7 @@ class IssueAssignee(ProjectBaseModel): class IssueLink(ProjectBaseModel): title = models.CharField(max_length=255, null=True, blank=True) url = models.TextField() - issue = models.ForeignKey( - "db.Issue", on_delete=models.CASCADE, related_name="issue_link" - ) + issue = models.ForeignKey("db.Issue", on_delete=models.CASCADE, related_name="issue_link") metadata = models.JSONField(default=dict) class Meta: @@ -403,9 +377,7 @@ def file_size(value): class IssueAttachment(ProjectBaseModel): attributes = models.JSONField(default=dict) asset = models.FileField(upload_to=get_upload_path, validators=[file_size]) - issue = models.ForeignKey( - "db.Issue", on_delete=models.CASCADE, related_name="issue_attachment" - ) + issue = models.ForeignKey("db.Issue", on_delete=models.CASCADE, related_name="issue_attachment") external_source = models.CharField(max_length=255, null=True, blank=True) external_id = models.CharField(max_length=255, blank=True, null=True) @@ -420,13 +392,9 @@ class IssueAttachment(ProjectBaseModel): class IssueActivity(ProjectBaseModel): - issue = models.ForeignKey( - Issue, on_delete=models.SET_NULL, null=True, related_name="issue_activity" - ) + issue = models.ForeignKey(Issue, on_delete=models.SET_NULL, null=True, related_name="issue_activity") verb = models.CharField(max_length=255, verbose_name="Action", default="created") - field = models.CharField( - max_length=255, verbose_name="Field Name", blank=True, null=True - ) + field = models.CharField(max_length=255, verbose_name="Field Name", blank=True, null=True) old_value = models.TextField(verbose_name="Old Value", blank=True, null=True) new_value = models.TextField(verbose_name="New Value", blank=True, null=True) @@ -464,9 +432,7 @@ class IssueComment(ProjectBaseModel): comment_json = models.JSONField(blank=True, default=dict) comment_html = models.TextField(blank=True, default="

") attachments = ArrayField(models.URLField(), size=10, blank=True, default=list) - issue = models.ForeignKey( - Issue, on_delete=models.CASCADE, related_name="issue_comments" - ) + issue = models.ForeignKey(Issue, on_delete=models.CASCADE, related_name="issue_comments") # System can also create comment actor = models.ForeignKey( settings.AUTH_USER_MODEL, @@ -484,9 +450,7 @@ class IssueComment(ProjectBaseModel): edited_at = models.DateTimeField(null=True, blank=True) def save(self, *args, **kwargs): - self.comment_stripped = ( - strip_tags(self.comment_html) if self.comment_html != "" else "" - ) + self.comment_stripped = strip_tags(self.comment_html) if self.comment_html != "" else "" return super(IssueComment, self).save(*args, **kwargs) class Meta: @@ -531,12 +495,8 @@ class IssueUserProperty(ProjectBaseModel): class IssueLabel(ProjectBaseModel): - issue = models.ForeignKey( - "db.Issue", on_delete=models.CASCADE, related_name="label_issue" - ) - label = models.ForeignKey( - "db.Label", on_delete=models.CASCADE, related_name="label_issue" - ) + issue = models.ForeignKey("db.Issue", on_delete=models.CASCADE, related_name="label_issue") + label = models.ForeignKey("db.Label", on_delete=models.CASCADE, related_name="label_issue") class Meta: verbose_name = "Issue Label" @@ -566,9 +526,7 @@ class IssueSequence(ProjectBaseModel): class IssueSubscriber(ProjectBaseModel): - issue = models.ForeignKey( - Issue, on_delete=models.CASCADE, related_name="issue_subscribers" - ) + issue = models.ForeignKey(Issue, on_delete=models.CASCADE, related_name="issue_subscribers") subscriber = models.ForeignKey( settings.AUTH_USER_MODEL, on_delete=models.CASCADE, @@ -599,9 +557,7 @@ class IssueReaction(ProjectBaseModel): on_delete=models.CASCADE, related_name="issue_reactions", ) - issue = models.ForeignKey( - Issue, on_delete=models.CASCADE, related_name="issue_reactions" - ) + issue = models.ForeignKey(Issue, on_delete=models.CASCADE, related_name="issue_reactions") reaction = models.TextField() class Meta: @@ -628,9 +584,7 @@ class CommentReaction(ProjectBaseModel): on_delete=models.CASCADE, related_name="comment_reactions", ) - comment = models.ForeignKey( - IssueComment, on_delete=models.CASCADE, related_name="comment_reactions" - ) + comment = models.ForeignKey(IssueComment, on_delete=models.CASCADE, related_name="comment_reactions") reaction = models.TextField() class Meta: @@ -653,9 +607,7 @@ class CommentReaction(ProjectBaseModel): class IssueVote(ProjectBaseModel): issue = models.ForeignKey(Issue, on_delete=models.CASCADE, related_name="votes") - actor = models.ForeignKey( - settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name="votes" - ) + actor = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name="votes") vote = models.IntegerField(choices=((-1, "DOWNVOTE"), (1, "UPVOTE")), default=1) class Meta: @@ -713,9 +665,7 @@ class IssueVersion(ProjectBaseModel): meta = models.JSONField(default=dict) # issue meta last_saved_at = models.DateTimeField(default=timezone.now) - issue = models.ForeignKey( - "db.Issue", on_delete=models.CASCADE, related_name="versions" - ) + issue = models.ForeignKey("db.Issue", on_delete=models.CASCADE, related_name="versions") activity = models.ForeignKey( "db.IssueActivity", on_delete=models.SET_NULL, @@ -760,17 +710,9 @@ class IssueVersion(ProjectBaseModel): priority=issue.priority, start_date=issue.start_date, target_date=issue.target_date, - assignees=list( - IssueAssignee.objects.filter(issue=issue).values_list( - "assignee_id", flat=True - ) - ), + assignees=list(IssueAssignee.objects.filter(issue=issue).values_list("assignee_id", flat=True)), sequence_id=issue.sequence_id, - labels=list( - IssueLabel.objects.filter(issue=issue).values_list( - "label_id", flat=True - ) - ), + labels=list(IssueLabel.objects.filter(issue=issue).values_list("label_id", flat=True)), sort_order=issue.sort_order, completed_at=issue.completed_at, archived_at=issue.archived_at, @@ -779,9 +721,7 @@ class IssueVersion(ProjectBaseModel): external_id=issue.external_id, type=issue.type_id, cycle=cycle_issue.cycle_id if cycle_issue else None, - modules=list( - Module.objects.filter(issue=issue).values_list("id", flat=True) - ), + modules=list(Module.objects.filter(issue=issue).values_list("id", flat=True)), properties={}, meta={}, last_saved_at=timezone.now(), @@ -794,9 +734,7 @@ class IssueVersion(ProjectBaseModel): class IssueDescriptionVersion(ProjectBaseModel): - issue = models.ForeignKey( - "db.Issue", on_delete=models.CASCADE, related_name="description_versions" - ) + issue = models.ForeignKey("db.Issue", on_delete=models.CASCADE, related_name="description_versions") description_binary = models.BinaryField(null=True) description_html = models.TextField(blank=True, default="

") description_stripped = models.TextField(blank=True, null=True) diff --git a/apps/api/plane/db/models/issue_type.py b/apps/api/plane/db/models/issue_type.py index 953afcc8b..4f3dc08de 100644 --- a/apps/api/plane/db/models/issue_type.py +++ b/apps/api/plane/db/models/issue_type.py @@ -8,9 +8,7 @@ from .base import BaseModel class IssueType(BaseModel): - workspace = models.ForeignKey( - "db.Workspace", related_name="issue_types", on_delete=models.CASCADE - ) + workspace = models.ForeignKey("db.Workspace", related_name="issue_types", on_delete=models.CASCADE) name = models.CharField(max_length=255) description = models.TextField(blank=True) logo_props = models.JSONField(default=dict) @@ -31,9 +29,7 @@ class IssueType(BaseModel): class ProjectIssueType(ProjectBaseModel): - issue_type = models.ForeignKey( - "db.IssueType", related_name="project_issue_types", on_delete=models.CASCADE - ) + issue_type = models.ForeignKey("db.IssueType", related_name="project_issue_types", on_delete=models.CASCADE) level = models.PositiveIntegerField(default=0) is_default = models.BooleanField(default=False) diff --git a/apps/api/plane/db/models/label.py b/apps/api/plane/db/models/label.py index 11e2da8c3..76ecf10e6 100644 --- a/apps/api/plane/db/models/label.py +++ b/apps/api/plane/db/models/label.py @@ -42,9 +42,7 @@ class Label(WorkspaceBaseModel): def save(self, *args, **kwargs): if self._state.adding: # Get the maximum sequence value from the database - last_id = Label.objects.filter(project=self.project).aggregate( - largest=models.Max("sort_order") - )["largest"] + last_id = Label.objects.filter(project=self.project).aggregate(largest=models.Max("sort_order"))["largest"] # if last_id is not None if last_id is not None: self.sort_order = last_id + 10000 diff --git a/apps/api/plane/db/models/module.py b/apps/api/plane/db/models/module.py index 897cf26b1..ab62f2df5 100644 --- a/apps/api/plane/db/models/module.py +++ b/apps/api/plane/db/models/module.py @@ -63,12 +63,8 @@ class ModuleStatus(models.TextChoices): class Module(ProjectBaseModel): name = models.CharField(max_length=255, verbose_name="Module Name") description = models.TextField(verbose_name="Module Description", blank=True) - description_text = models.JSONField( - verbose_name="Module Description RT", blank=True, null=True - ) - description_html = models.JSONField( - verbose_name="Module Description HTML", blank=True, null=True - ) + description_text = models.JSONField(verbose_name="Module Description RT", blank=True, null=True) + description_html = models.JSONField(verbose_name="Module Description HTML", blank=True, null=True) start_date = models.DateField(null=True) target_date = models.DateField(null=True) status = models.CharField( @@ -83,9 +79,7 @@ class Module(ProjectBaseModel): default="planned", max_length=20, ) - lead = models.ForeignKey( - "db.User", on_delete=models.SET_NULL, related_name="module_leads", null=True - ) + lead = models.ForeignKey("db.User", on_delete=models.SET_NULL, related_name="module_leads", null=True) members = models.ManyToManyField( settings.AUTH_USER_MODEL, blank=True, @@ -152,12 +146,8 @@ class ModuleMember(ProjectBaseModel): class ModuleIssue(ProjectBaseModel): - module = models.ForeignKey( - "db.Module", on_delete=models.CASCADE, related_name="issue_module" - ) - issue = models.ForeignKey( - "db.Issue", on_delete=models.CASCADE, related_name="issue_module" - ) + module = models.ForeignKey("db.Module", on_delete=models.CASCADE, related_name="issue_module") + issue = models.ForeignKey("db.Issue", on_delete=models.CASCADE, related_name="issue_module") class Meta: unique_together = ["issue", "module", "deleted_at"] @@ -180,9 +170,7 @@ class ModuleIssue(ProjectBaseModel): class ModuleLink(ProjectBaseModel): title = models.CharField(max_length=255, blank=True, null=True) url = models.URLField() - module = models.ForeignKey( - Module, on_delete=models.CASCADE, related_name="link_module" - ) + module = models.ForeignKey(Module, on_delete=models.CASCADE, related_name="link_module") metadata = models.JSONField(default=dict) class Meta: @@ -196,9 +184,7 @@ class ModuleLink(ProjectBaseModel): class ModuleUserProperties(ProjectBaseModel): - module = models.ForeignKey( - "db.Module", on_delete=models.CASCADE, related_name="module_user_properties" - ) + module = models.ForeignKey("db.Module", on_delete=models.CASCADE, related_name="module_user_properties") user = models.ForeignKey( settings.AUTH_USER_MODEL, on_delete=models.CASCADE, diff --git a/apps/api/plane/db/models/notification.py b/apps/api/plane/db/models/notification.py index a57e288ab..aa58bc30c 100644 --- a/apps/api/plane/db/models/notification.py +++ b/apps/api/plane/db/models/notification.py @@ -7,12 +7,8 @@ from .base import BaseModel class Notification(BaseModel): - workspace = models.ForeignKey( - "db.Workspace", related_name="notifications", on_delete=models.CASCADE - ) - project = models.ForeignKey( - "db.Project", related_name="notifications", on_delete=models.CASCADE, null=True - ) + workspace = models.ForeignKey("db.Workspace", related_name="notifications", on_delete=models.CASCADE) + project = models.ForeignKey("db.Project", related_name="notifications", on_delete=models.CASCADE, null=True) data = models.JSONField(null=True) entity_identifier = models.UUIDField(null=True) entity_name = models.CharField(max_length=255) @@ -27,9 +23,7 @@ class Notification(BaseModel): on_delete=models.SET_NULL, null=True, ) - receiver = models.ForeignKey( - "db.User", related_name="received_notifications", on_delete=models.CASCADE - ) + receiver = models.ForeignKey("db.User", related_name="received_notifications", on_delete=models.CASCADE) read_at = models.DateTimeField(null=True) snoozed_till = models.DateTimeField(null=True) archived_at = models.DateTimeField(null=True) @@ -40,9 +34,7 @@ class Notification(BaseModel): db_table = "notifications" ordering = ("-created_at",) indexes = [ - models.Index( - fields=["entity_identifier"], name="notif_entity_identifier_idx" - ), + models.Index(fields=["entity_identifier"], name="notif_entity_identifier_idx"), models.Index(fields=["entity_name"], name="notif_entity_name_idx"), models.Index(fields=["read_at"], name="notif_read_at_idx"), models.Index(fields=["receiver", "read_at"], name="notif_entity_idx"), diff --git a/apps/api/plane/db/models/page.py b/apps/api/plane/db/models/page.py index 4d465cd58..213954d14 100644 --- a/apps/api/plane/db/models/page.py +++ b/apps/api/plane/db/models/page.py @@ -23,24 +23,16 @@ class Page(BaseModel): ACCESS_CHOICES = ((PRIVATE_ACCESS, "Private"), (PUBLIC_ACCESS, "Public")) - workspace = models.ForeignKey( - "db.Workspace", on_delete=models.CASCADE, related_name="pages" - ) + workspace = models.ForeignKey("db.Workspace", on_delete=models.CASCADE, related_name="pages") name = models.TextField(blank=True) description = models.JSONField(default=dict, blank=True) description_binary = models.BinaryField(null=True) description_html = models.TextField(blank=True, default="

") description_stripped = models.TextField(blank=True, null=True) - owned_by = models.ForeignKey( - settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name="pages" - ) - access = models.PositiveSmallIntegerField( - choices=((0, "Public"), (1, "Private")), default=0 - ) + owned_by = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name="pages") + access = models.PositiveSmallIntegerField(choices=((0, "Public"), (1, "Private")), default=0) color = models.CharField(max_length=255, blank=True) - labels = models.ManyToManyField( - "db.Label", blank=True, related_name="pages", through="db.PageLabel" - ) + labels = models.ManyToManyField("db.Label", blank=True, related_name="pages", through="db.PageLabel") parent = models.ForeignKey( "self", on_delete=models.CASCADE, @@ -53,9 +45,7 @@ class Page(BaseModel): view_props = models.JSONField(default=get_view_props) logo_props = models.JSONField(default=dict) is_global = models.BooleanField(default=False) - projects = models.ManyToManyField( - "db.Project", related_name="pages", through="db.ProjectPage" - ) + projects = models.ManyToManyField("db.Project", related_name="pages", through="db.ProjectPage") moved_to_page = models.UUIDField(null=True, blank=True) moved_to_project = models.UUIDField(null=True, blank=True) sort_order = models.FloatField(default=DEFAULT_SORT_ORDER) @@ -102,12 +92,8 @@ class PageLog(BaseModel): page = models.ForeignKey(Page, related_name="page_log", on_delete=models.CASCADE) entity_identifier = models.UUIDField(null=True, blank=True) entity_name = models.CharField(max_length=30, verbose_name="Transaction Type") - entity_type = models.CharField( - max_length=30, verbose_name="Entity Type", null=True, blank=True - ) - workspace = models.ForeignKey( - "db.Workspace", on_delete=models.CASCADE, related_name="workspace_page_log" - ) + entity_type = models.CharField(max_length=30, verbose_name="Entity Type", null=True, blank=True) + workspace = models.ForeignKey("db.Workspace", on_delete=models.CASCADE, related_name="workspace_page_log") class Meta: unique_together = ["page", "transaction"] @@ -119,12 +105,8 @@ class PageLog(BaseModel): models.Index(fields=["entity_type"], name="pagelog_entity_type_idx"), models.Index(fields=["entity_identifier"], name="pagelog_entity_id_idx"), models.Index(fields=["entity_name"], name="pagelog_entity_name_idx"), - models.Index( - fields=["entity_type", "entity_identifier"], name="pagelog_type_id_idx" - ), - models.Index( - fields=["entity_name", "entity_identifier"], name="pagelog_name_id_idx" - ), + models.Index(fields=["entity_type", "entity_identifier"], name="pagelog_type_id_idx"), + models.Index(fields=["entity_name", "entity_identifier"], name="pagelog_name_id_idx"), ] def __str__(self): @@ -132,15 +114,9 @@ class PageLog(BaseModel): class PageLabel(BaseModel): - label = models.ForeignKey( - "db.Label", on_delete=models.CASCADE, related_name="page_labels" - ) - page = models.ForeignKey( - "db.Page", on_delete=models.CASCADE, related_name="page_labels" - ) - workspace = models.ForeignKey( - "db.Workspace", on_delete=models.CASCADE, related_name="workspace_page_label" - ) + label = models.ForeignKey("db.Label", on_delete=models.CASCADE, related_name="page_labels") + page = models.ForeignKey("db.Page", on_delete=models.CASCADE, related_name="page_labels") + workspace = models.ForeignKey("db.Workspace", on_delete=models.CASCADE, related_name="workspace_page_label") class Meta: verbose_name = "Page Label" @@ -153,15 +129,9 @@ class PageLabel(BaseModel): class ProjectPage(BaseModel): - project = models.ForeignKey( - "db.Project", on_delete=models.CASCADE, related_name="project_pages" - ) - page = models.ForeignKey( - "db.Page", on_delete=models.CASCADE, related_name="project_pages" - ) - workspace = models.ForeignKey( - "db.Workspace", on_delete=models.CASCADE, related_name="project_pages" - ) + project = models.ForeignKey("db.Project", on_delete=models.CASCADE, related_name="project_pages") + page = models.ForeignKey("db.Page", on_delete=models.CASCADE, related_name="project_pages") + workspace = models.ForeignKey("db.Workspace", on_delete=models.CASCADE, related_name="project_pages") class Meta: unique_together = ["project", "page", "deleted_at"] @@ -182,16 +152,10 @@ class ProjectPage(BaseModel): class PageVersion(BaseModel): - workspace = models.ForeignKey( - "db.Workspace", on_delete=models.CASCADE, related_name="page_versions" - ) - page = models.ForeignKey( - "db.Page", on_delete=models.CASCADE, related_name="page_versions" - ) + workspace = models.ForeignKey("db.Workspace", on_delete=models.CASCADE, related_name="page_versions") + page = models.ForeignKey("db.Page", on_delete=models.CASCADE, related_name="page_versions") last_saved_at = models.DateTimeField(default=timezone.now) - owned_by = models.ForeignKey( - settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name="page_versions" - ) + owned_by = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name="page_versions") description_binary = models.BinaryField(null=True) description_html = models.TextField(blank=True, default="

") description_stripped = models.TextField(blank=True, null=True) diff --git a/apps/api/plane/db/models/project.py b/apps/api/plane/db/models/project.py index 81a84f1ac..ed5a08772 100644 --- a/apps/api/plane/db/models/project.py +++ b/apps/api/plane/db/models/project.py @@ -66,19 +66,11 @@ class Project(BaseModel): NETWORK_CHOICES = ((0, "Secret"), (2, "Public")) name = models.CharField(max_length=255, verbose_name="Project Name") description = models.TextField(verbose_name="Project Description", blank=True) - description_text = models.JSONField( - verbose_name="Project Description RT", blank=True, null=True - ) - description_html = models.JSONField( - verbose_name="Project Description HTML", blank=True, null=True - ) + description_text = models.JSONField(verbose_name="Project Description RT", blank=True, null=True) + description_html = models.JSONField(verbose_name="Project Description HTML", blank=True, null=True) network = models.PositiveSmallIntegerField(default=2, choices=NETWORK_CHOICES) - workspace = models.ForeignKey( - "db.WorkSpace", on_delete=models.CASCADE, related_name="workspace_project" - ) - identifier = models.CharField( - max_length=12, verbose_name="Project Identifier", db_index=True - ) + workspace = models.ForeignKey("db.WorkSpace", on_delete=models.CASCADE, related_name="workspace_project") + identifier = models.CharField(max_length=12, verbose_name="Project Identifier", db_index=True) default_assignee = models.ForeignKey( settings.AUTH_USER_MODEL, on_delete=models.CASCADE, @@ -111,19 +103,11 @@ class Project(BaseModel): blank=True, related_name="project_cover_image", ) - estimate = models.ForeignKey( - "db.Estimate", on_delete=models.SET_NULL, related_name="projects", null=True - ) - archive_in = models.IntegerField( - default=0, validators=[MinValueValidator(0), MaxValueValidator(12)] - ) - close_in = models.IntegerField( - default=0, validators=[MinValueValidator(0), MaxValueValidator(12)] - ) + estimate = models.ForeignKey("db.Estimate", on_delete=models.SET_NULL, related_name="projects", null=True) + archive_in = models.IntegerField(default=0, validators=[MinValueValidator(0), MaxValueValidator(12)]) + close_in = models.IntegerField(default=0, validators=[MinValueValidator(0), MaxValueValidator(12)]) logo_props = models.JSONField(default=dict) - default_state = models.ForeignKey( - "db.State", on_delete=models.SET_NULL, null=True, related_name="default_state" - ) + default_state = models.ForeignKey("db.State", on_delete=models.SET_NULL, null=True, related_name="default_state") archived_at = models.DateTimeField(null=True) # timezone TIMEZONE_CHOICES = tuple(zip(pytz.common_timezones, pytz.common_timezones)) @@ -176,12 +160,8 @@ class Project(BaseModel): class ProjectBaseModel(BaseModel): - project = models.ForeignKey( - Project, on_delete=models.CASCADE, related_name="project_%(class)s" - ) - workspace = models.ForeignKey( - "db.Workspace", on_delete=models.CASCADE, related_name="workspace_%(class)s" - ) + project = models.ForeignKey(Project, on_delete=models.CASCADE, related_name="project_%(class)s") + workspace = models.ForeignKey("db.Workspace", on_delete=models.CASCADE, related_name="workspace_%(class)s") class Meta: abstract = True @@ -258,12 +238,8 @@ class ProjectMember(ProjectBaseModel): # TODO: Remove workspace relation later class ProjectIdentifier(AuditModel): - workspace = models.ForeignKey( - "db.Workspace", models.CASCADE, related_name="project_identifiers", null=True - ) - project = models.OneToOneField( - Project, on_delete=models.CASCADE, related_name="project_identifier" - ) + workspace = models.ForeignKey("db.Workspace", models.CASCADE, related_name="project_identifiers", null=True) + project = models.OneToOneField(Project, on_delete=models.CASCADE, related_name="project_identifier") name = models.CharField(max_length=12, db_index=True) class Meta: @@ -298,14 +274,10 @@ def get_default_views(): # DEPRECATED TODO: # used to get the old anchors for the project deploy boards class ProjectDeployBoard(ProjectBaseModel): - anchor = models.CharField( - max_length=255, default=get_anchor, unique=True, db_index=True - ) + anchor = models.CharField(max_length=255, default=get_anchor, unique=True, db_index=True) comments = models.BooleanField(default=False) reactions = models.BooleanField(default=False) - intake = models.ForeignKey( - "db.Intake", related_name="board_intake", on_delete=models.SET_NULL, null=True - ) + intake = models.ForeignKey("db.Intake", related_name="board_intake", on_delete=models.SET_NULL, null=True) votes = models.BooleanField(default=False) views = models.JSONField(default=get_default_views) diff --git a/apps/api/plane/db/models/state.py b/apps/api/plane/db/models/state.py index 3478d70d2..e9d56acf9 100644 --- a/apps/api/plane/db/models/state.py +++ b/apps/api/plane/db/models/state.py @@ -52,9 +52,7 @@ class State(ProjectBaseModel): self.slug = slugify(self.name) if self._state.adding: # Get the maximum sequence value from the database - last_id = State.objects.filter(project=self.project).aggregate( - largest=models.Max("sequence") - )["largest"] + last_id = State.objects.filter(project=self.project).aggregate(largest=models.Max("sequence"))["largest"] # if last_id is not None if last_id is not None: self.sequence = last_id + 15000 diff --git a/apps/api/plane/db/models/sticky.py b/apps/api/plane/db/models/sticky.py index 34f37b81e..157077eb8 100644 --- a/apps/api/plane/db/models/sticky.py +++ b/apps/api/plane/db/models/sticky.py @@ -21,12 +21,8 @@ class Sticky(BaseModel): color = models.CharField(max_length=255, blank=True, null=True) background_color = models.CharField(max_length=255, blank=True, null=True) - workspace = models.ForeignKey( - "db.Workspace", on_delete=models.CASCADE, related_name="stickies" - ) - owner = models.ForeignKey( - settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name="stickies" - ) + workspace = models.ForeignKey("db.Workspace", on_delete=models.CASCADE, related_name="stickies") + owner = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name="stickies") sort_order = models.FloatField(default=65535) class Meta: @@ -44,9 +40,9 @@ class Sticky(BaseModel): ) if self._state.adding: # Get the maximum sequence value from the database - last_id = Sticky.objects.filter(workspace=self.workspace).aggregate( - largest=models.Max("sort_order") - )["largest"] + last_id = Sticky.objects.filter(workspace=self.workspace).aggregate(largest=models.Max("sort_order"))[ + "largest" + ] # if last_id is not None if last_id is not None: self.sort_order = last_id + 10000 diff --git a/apps/api/plane/db/models/user.py b/apps/api/plane/db/models/user.py index 2e7d2a7b8..c9f0df9b0 100644 --- a/apps/api/plane/db/models/user.py +++ b/apps/api/plane/db/models/user.py @@ -36,9 +36,7 @@ def get_mobile_default_onboarding(): class User(AbstractBaseUser, PermissionsMixin): - id = models.UUIDField( - default=uuid.uuid4, unique=True, editable=False, db_index=True, primary_key=True - ) + id = models.UUIDField(default=uuid.uuid4, unique=True, editable=False, db_index=True, primary_key=True) username = models.CharField(max_length=128, unique=True) # user fields mobile_number = models.CharField(max_length=255, blank=True, null=True) @@ -97,15 +95,11 @@ class User(AbstractBaseUser, PermissionsMixin): # my_issues_prop = models.JSONField(null=True) is_bot = models.BooleanField(default=False) - bot_type = models.CharField( - max_length=30, verbose_name="Bot Type", blank=True, null=True - ) + bot_type = models.CharField(max_length=30, verbose_name="Bot Type", blank=True, null=True) # timezone USER_TIMEZONE_CHOICES = tuple(zip(pytz.common_timezones, pytz.common_timezones)) - user_timezone = models.CharField( - max_length=255, default="UTC", choices=USER_TIMEZONE_CHOICES - ) + user_timezone = models.CharField(max_length=255, default="UTC", choices=USER_TIMEZONE_CHOICES) # email validation is_email_valid = models.BooleanField(default=False) @@ -189,13 +183,9 @@ class Profile(TimeAuditModel): (SATURDAY, "Saturday"), ) - id = models.UUIDField( - default=uuid.uuid4, unique=True, editable=False, db_index=True, primary_key=True - ) + id = models.UUIDField(default=uuid.uuid4, unique=True, editable=False, db_index=True, primary_key=True) # User - user = models.OneToOneField( - "db.User", on_delete=models.CASCADE, related_name="profile" - ) + user = models.OneToOneField("db.User", on_delete=models.CASCADE, related_name="profile") # General theme = models.JSONField(default=dict) is_app_rail_docked = models.BooleanField(default=True) @@ -220,9 +210,7 @@ class Profile(TimeAuditModel): mobile_timezone_auto_set = models.BooleanField(default=False) # language language = models.CharField(max_length=255, default="en") - start_of_the_week = models.PositiveSmallIntegerField( - choices=START_OF_THE_WEEK_CHOICES, default=SUNDAY - ) + start_of_the_week = models.PositiveSmallIntegerField(choices=START_OF_THE_WEEK_CHOICES, default=SUNDAY) goals = models.JSONField(default=dict) background_color = models.CharField(max_length=255, default=get_random_color) @@ -243,12 +231,8 @@ class Account(TimeAuditModel): ("gitlab", "GitLab"), ) - id = models.UUIDField( - default=uuid.uuid4, unique=True, editable=False, db_index=True, primary_key=True - ) - user = models.ForeignKey( - "db.User", on_delete=models.CASCADE, related_name="accounts" - ) + id = models.UUIDField(default=uuid.uuid4, unique=True, editable=False, db_index=True, primary_key=True) + user = models.ForeignKey("db.User", on_delete=models.CASCADE, related_name="accounts") provider_account_id = models.CharField(max_length=255) provider = models.CharField(choices=PROVIDER_CHOICES) access_token = models.TextField() diff --git a/apps/api/plane/db/models/view.py b/apps/api/plane/db/models/view.py index 87d22e44f..d430cd5f9 100644 --- a/apps/api/plane/db/models/view.py +++ b/apps/api/plane/db/models/view.py @@ -59,14 +59,10 @@ class IssueView(WorkspaceBaseModel): display_filters = models.JSONField(default=get_default_display_filters) display_properties = models.JSONField(default=get_default_display_properties) rich_filters = models.JSONField(default=dict) - access = models.PositiveSmallIntegerField( - default=1, choices=((0, "Private"), (1, "Public")) - ) + access = models.PositiveSmallIntegerField(default=1, choices=((0, "Private"), (1, "Public"))) sort_order = models.FloatField(default=65535) logo_props = models.JSONField(default=dict) - owned_by = models.ForeignKey( - settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name="views" - ) + owned_by = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name="views") is_locked = models.BooleanField(default=False) class Meta: @@ -81,13 +77,13 @@ class IssueView(WorkspaceBaseModel): if self._state.adding: if self.project: - largest_sort_order = IssueView.objects.filter( - project=self.project - ).aggregate(largest=models.Max("sort_order"))["largest"] + largest_sort_order = IssueView.objects.filter(project=self.project).aggregate( + largest=models.Max("sort_order") + )["largest"] else: - largest_sort_order = IssueView.objects.filter( - workspace=self.workspace, project__isnull=True - ).aggregate(largest=models.Max("sort_order"))["largest"] + largest_sort_order = IssueView.objects.filter(workspace=self.workspace, project__isnull=True).aggregate( + largest=models.Max("sort_order") + )["largest"] if largest_sort_order is not None: self.sort_order = largest_sort_order + 10000 diff --git a/apps/api/plane/db/models/webhook.py b/apps/api/plane/db/models/webhook.py index 189ccb279..8872d0bb2 100644 --- a/apps/api/plane/db/models/webhook.py +++ b/apps/api/plane/db/models/webhook.py @@ -28,12 +28,8 @@ def validate_domain(value): class Webhook(BaseModel): - workspace = models.ForeignKey( - "db.Workspace", on_delete=models.CASCADE, related_name="workspace_webhooks" - ) - url = models.URLField( - validators=[validate_schema, validate_domain], max_length=1024 - ) + workspace = models.ForeignKey("db.Workspace", on_delete=models.CASCADE, related_name="workspace_webhooks") + url = models.URLField(validators=[validate_schema, validate_domain], max_length=1024) is_active = models.BooleanField(default=True) secret_key = models.CharField(max_length=255, default=generate_token) project = models.BooleanField(default=False) @@ -62,9 +58,7 @@ class Webhook(BaseModel): class WebhookLog(BaseModel): - workspace = models.ForeignKey( - "db.Workspace", on_delete=models.CASCADE, related_name="webhook_logs" - ) + workspace = models.ForeignKey("db.Workspace", on_delete=models.CASCADE, related_name="webhook_logs") # Associated webhook webhook = models.UUIDField() @@ -92,11 +86,8 @@ class WebhookLog(BaseModel): return f"{self.event_type} {str(self.webhook)}" - class ProjectWebhook(ProjectBaseModel): - webhook = models.ForeignKey( - "db.Webhook", on_delete=models.CASCADE, related_name="project_webhooks" - ) + webhook = models.ForeignKey("db.Webhook", on_delete=models.CASCADE, related_name="project_webhooks") class Meta: unique_together = ["project", "webhook", "deleted_at"] @@ -110,4 +101,4 @@ class ProjectWebhook(ProjectBaseModel): verbose_name = "Project Webhook" verbose_name_plural = "Project Webhooks" db_table = "project_webhooks" - ordering = ("-created_at",) \ No newline at end of file + ordering = ("-created_at",) diff --git a/apps/api/plane/db/models/workspace.py b/apps/api/plane/db/models/workspace.py index 75a45f72c..0f5c09760 100644 --- a/apps/api/plane/db/models/workspace.py +++ b/apps/api/plane/db/models/workspace.py @@ -129,9 +129,7 @@ class Workspace(BaseModel): on_delete=models.CASCADE, related_name="owner_workspace", ) - slug = models.SlugField( - max_length=48, db_index=True, unique=True, validators=[slug_validator] - ) + slug = models.SlugField(max_length=48, db_index=True, unique=True, validators=[slug_validator]) organization_size = models.CharField(max_length=20, blank=True, null=True) timezone = models.CharField(max_length=255, default="UTC", choices=TIMEZONE_CHOICES) background_color = models.CharField(max_length=255, default=get_random_color) @@ -151,9 +149,7 @@ class Workspace(BaseModel): return self.logo return None - def delete( - self, using: Optional[str] = None, soft: bool = True, *args: Any, **kwargs: Any - ): + def delete(self, using: Optional[str] = None, soft: bool = True, *args: Any, **kwargs: Any): """ Override the delete method to append epoch timestamp to the slug when soft deleting. @@ -183,12 +179,8 @@ class Workspace(BaseModel): class WorkspaceBaseModel(BaseModel): - workspace = models.ForeignKey( - "db.Workspace", models.CASCADE, related_name="workspace_%(class)s" - ) - project = models.ForeignKey( - "db.Project", models.CASCADE, related_name="project_%(class)s", null=True - ) + workspace = models.ForeignKey("db.Workspace", models.CASCADE, related_name="workspace_%(class)s") + project = models.ForeignKey("db.Project", models.CASCADE, related_name="project_%(class)s", null=True) class Meta: abstract = True @@ -200,9 +192,7 @@ class WorkspaceBaseModel(BaseModel): class WorkspaceMember(BaseModel): - workspace = models.ForeignKey( - "db.Workspace", on_delete=models.CASCADE, related_name="workspace_member" - ) + workspace = models.ForeignKey("db.Workspace", on_delete=models.CASCADE, related_name="workspace_member") member = models.ForeignKey( settings.AUTH_USER_MODEL, on_delete=models.CASCADE, @@ -235,9 +225,7 @@ class WorkspaceMember(BaseModel): class WorkspaceMemberInvite(BaseModel): - workspace = models.ForeignKey( - "db.Workspace", on_delete=models.CASCADE, related_name="workspace_member_invite" - ) + workspace = models.ForeignKey("db.Workspace", on_delete=models.CASCADE, related_name="workspace_member_invite") email = models.CharField(max_length=255) accepted = models.BooleanField(default=False) token = models.CharField(max_length=255) @@ -266,9 +254,7 @@ class WorkspaceMemberInvite(BaseModel): class Team(BaseModel): name = models.CharField(max_length=255, verbose_name="Team Name") description = models.TextField(verbose_name="Team Description", blank=True) - workspace = models.ForeignKey( - Workspace, on_delete=models.CASCADE, related_name="workspace_team" - ) + workspace = models.ForeignKey(Workspace, on_delete=models.CASCADE, related_name="workspace_team") logo_props = models.JSONField(default=dict) def __str__(self): @@ -291,13 +277,9 @@ class Team(BaseModel): class WorkspaceTheme(BaseModel): - workspace = models.ForeignKey( - "db.Workspace", on_delete=models.CASCADE, related_name="themes" - ) + workspace = models.ForeignKey("db.Workspace", on_delete=models.CASCADE, related_name="themes") name = models.CharField(max_length=300) - actor = models.ForeignKey( - settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name="themes" - ) + actor = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name="themes") colors = models.JSONField(default=dict) def __str__(self): diff --git a/apps/api/plane/license/api/permissions/instance.py b/apps/api/plane/license/api/permissions/instance.py index 848de4d7b..a430b688b 100644 --- a/apps/api/plane/license/api/permissions/instance.py +++ b/apps/api/plane/license/api/permissions/instance.py @@ -11,6 +11,4 @@ class InstanceAdminPermission(BasePermission): return False instance = Instance.objects.first() - return InstanceAdmin.objects.filter( - role__gte=15, instance=instance, user=request.user - ).exists() + return InstanceAdmin.objects.filter(role__gte=15, instance=instance, user=request.user).exists() diff --git a/apps/api/plane/license/api/serializers/instance.py b/apps/api/plane/license/api/serializers/instance.py index 49c5194c8..c75c62e50 100644 --- a/apps/api/plane/license/api/serializers/instance.py +++ b/apps/api/plane/license/api/serializers/instance.py @@ -5,9 +5,7 @@ from plane.app.serializers import UserAdminLiteSerializer class InstanceSerializer(BaseSerializer): - primary_owner_details = UserAdminLiteSerializer( - source="primary_owner", read_only=True - ) + primary_owner_details = UserAdminLiteSerializer(source="primary_owner", read_only=True) class Meta: model = Instance diff --git a/apps/api/plane/license/api/views/admin.py b/apps/api/plane/license/api/views/admin.py index 3a9563e3b..72c976116 100644 --- a/apps/api/plane/license/api/views/admin.py +++ b/apps/api/plane/license/api/views/admin.py @@ -47,9 +47,7 @@ class InstanceAdminEndpoint(BaseAPIView): role = request.data.get("role", 20) if not email: - return Response( - {"error": "Email is required"}, status=status.HTTP_400_BAD_REQUEST - ) + return Response({"error": "Email is required"}, status=status.HTTP_400_BAD_REQUEST) instance = Instance.objects.first() if instance is None: @@ -61,9 +59,7 @@ class InstanceAdminEndpoint(BaseAPIView): # Fetch the user user = User.objects.get(email=email) - instance_admin = InstanceAdmin.objects.create( - instance=instance, user=user, role=role - ) + instance_admin = InstanceAdmin.objects.create(instance=instance, user=user, role=role) serializer = InstanceAdminSerializer(instance_admin) return Response(serializer.data, status=status.HTTP_201_CREATED) @@ -127,9 +123,7 @@ class InstanceAdminSignUpEndpoint(View): # return error if the email and password is not present if not email or not password or not first_name: exc = AuthenticationException( - error_code=AUTHENTICATION_ERROR_CODES[ - "REQUIRED_ADMIN_EMAIL_PASSWORD_FIRST_NAME" - ], + error_code=AUTHENTICATION_ERROR_CODES["REQUIRED_ADMIN_EMAIL_PASSWORD_FIRST_NAME"], error_message="REQUIRED_ADMIN_EMAIL_PASSWORD_FIRST_NAME", payload={ "email": email, @@ -369,10 +363,7 @@ class InstanceAdminUserSessionEndpoint(BaseAPIView): permission_classes = [AllowAny] def get(self, request): - if ( - request.user.is_authenticated - and InstanceAdmin.objects.filter(user=request.user).exists() - ): + if request.user.is_authenticated and InstanceAdmin.objects.filter(user=request.user).exists(): serializer = InstanceAdminMeSerializer(request.user) data = {"is_authenticated": True} data["user"] = serializer.data @@ -393,14 +384,8 @@ class InstanceAdminSignOutEndpoint(View): user.save() # Log the user out logout(request) - url = get_safe_redirect_url( - base_url=base_host(request=request, is_admin=True), - next_path="" - ) + url = get_safe_redirect_url(base_url=base_host(request=request, is_admin=True), next_path="") return HttpResponseRedirect(url) except Exception: - url = get_safe_redirect_url( - base_url=base_host(request=request, is_admin=True), - next_path="" - ) + url = get_safe_redirect_url(base_url=base_host(request=request, is_admin=True), next_path="") return HttpResponseRedirect(url) diff --git a/apps/api/plane/license/api/views/base.py b/apps/api/plane/license/api/views/base.py index 05b42b801..d209bd6bf 100644 --- a/apps/api/plane/license/api/views/base.py +++ b/apps/api/plane/license/api/views/base.py @@ -97,9 +97,7 @@ class BaseAPIView(TimezoneMixin, APIView, BasePaginator): if settings.DEBUG: from django.db import connection - print( - f"{request.method} - {request.get_full_path()} of Queries: {len(connection.queries)}" - ) + print(f"{request.method} - {request.get_full_path()} of Queries: {len(connection.queries)}") return response except Exception as exc: @@ -108,14 +106,10 @@ class BaseAPIView(TimezoneMixin, APIView, BasePaginator): @property def fields(self): - fields = [ - field for field in self.request.GET.get("fields", "").split(",") if field - ] + fields = [field for field in self.request.GET.get("fields", "").split(",") if field] return fields if fields else None @property def expand(self): - expand = [ - expand for expand in self.request.GET.get("expand", "").split(",") if expand - ] + expand = [expand for expand in self.request.GET.get("expand", "").split(",") if expand] return expand if expand else None diff --git a/apps/api/plane/license/api/views/configuration.py b/apps/api/plane/license/api/views/configuration.py index 3bf996db9..8bb953565 100644 --- a/apps/api/plane/license/api/views/configuration.py +++ b/apps/api/plane/license/api/views/configuration.py @@ -37,9 +37,7 @@ class InstanceConfigurationEndpoint(BaseAPIView): @invalidate_cache(path="/api/instances/configurations/", user=False) @invalidate_cache(path="/api/instances/", user=False) def patch(self, request): - configurations = InstanceConfiguration.objects.filter( - key__in=request.data.keys() - ) + configurations = InstanceConfiguration.objects.filter(key__in=request.data.keys()) bulk_configurations = [] for configuration in configurations: @@ -50,9 +48,7 @@ class InstanceConfigurationEndpoint(BaseAPIView): configuration.value = value bulk_configurations.append(configuration) - InstanceConfiguration.objects.bulk_update( - bulk_configurations, ["value"], batch_size=100 - ) + InstanceConfiguration.objects.bulk_update(bulk_configurations, ["value"], batch_size=100) serializer = InstanceConfigurationSerializer(configurations, many=True) return Response(serializer.data, status=status.HTTP_200_OK) @@ -75,9 +71,7 @@ class DisableEmailFeatureEndpoint(BaseAPIView): "EMAIL_FROM", ] ) - ).update( - value=Case(When(key="ENABLE_SMTP", then=Value("0")), default=Value("")) - ) + ).update(value=Case(When(key="ENABLE_SMTP", then=Value("0")), default=Value(""))) return Response(status=status.HTTP_200_OK) except Exception: return Response( @@ -127,13 +121,9 @@ class EmailCredentialCheckEndpoint(BaseAPIView): connection=connection, ) msg.send(fail_silently=False) - return Response( - {"message": "Email successfully sent."}, status=status.HTTP_200_OK - ) + return Response({"message": "Email successfully sent."}, status=status.HTTP_200_OK) except BadHeaderError: - return Response( - {"error": "Invalid email header."}, status=status.HTTP_400_BAD_REQUEST - ) + return Response({"error": "Invalid email header."}, status=status.HTTP_400_BAD_REQUEST) except SMTPAuthenticationError: return Response( {"error": "Invalid credentials provided"}, @@ -166,9 +156,7 @@ class EmailCredentialCheckEndpoint(BaseAPIView): ) except ConnectionError: return Response( - { - "error": "Network connection error. Please check your internet connection." - }, + {"error": "Network connection error. Please check your internet connection."}, status=status.HTTP_400_BAD_REQUEST, ) except Exception: diff --git a/apps/api/plane/license/api/views/workspace.py b/apps/api/plane/license/api/views/workspace.py index 8b5eaac6b..5d1a2f24b 100644 --- a/apps/api/plane/license/api/views/workspace.py +++ b/apps/api/plane/license/api/views/workspace.py @@ -24,10 +24,7 @@ class InstanceWorkSpaceAvailabilityCheckEndpoint(BaseAPIView): status=status.HTTP_400_BAD_REQUEST, ) - workspace = ( - Workspace.objects.filter(slug__iexact=slug).exists() - or slug in RESTRICTED_WORKSPACE_SLUGS - ) + workspace = Workspace.objects.filter(slug__iexact=slug).exists() or slug in RESTRICTED_WORKSPACE_SLUGS return Response({"status": not workspace}, status=status.HTTP_200_OK) @@ -45,18 +42,14 @@ class InstanceWorkSpaceEndpoint(BaseAPIView): ) member_count = ( - WorkspaceMember.objects.filter( - workspace=OuterRef("id"), member__is_bot=False, is_active=True - ) + WorkspaceMember.objects.filter(workspace=OuterRef("id"), member__is_bot=False, is_active=True) .select_related("owner") .order_by() .annotate(count=Func(F("id"), function="Count")) .values("count") ) - workspaces = Workspace.objects.annotate( - total_projects=project_count, total_members=member_count - ) + workspaces = Workspace.objects.annotate(total_projects=project_count, total_members=member_count) # Add search functionality search = request.query_params.get("search", None) diff --git a/apps/api/plane/license/bgtasks/tracer.py b/apps/api/plane/license/bgtasks/tracer.py index 47e74c83a..055c45d6c 100644 --- a/apps/api/plane/license/bgtasks/tracer.py +++ b/apps/api/plane/license/bgtasks/tracer.py @@ -51,14 +51,10 @@ def instance_traces(): span.set_attribute("instance_name", instance.instance_name) span.set_attribute("current_version", instance.current_version) span.set_attribute("latest_version", instance.latest_version) - span.set_attribute( - "is_telemetry_enabled", instance.is_telemetry_enabled - ) + span.set_attribute("is_telemetry_enabled", instance.is_telemetry_enabled) span.set_attribute("is_support_required", instance.is_support_required) span.set_attribute("is_setup_done", instance.is_setup_done) - span.set_attribute( - "is_signup_screen_visited", instance.is_signup_screen_visited - ) + span.set_attribute("is_signup_screen_visited", instance.is_signup_screen_visited) span.set_attribute("is_verified", instance.is_verified) span.set_attribute("edition", instance.edition) span.set_attribute("domain", instance.domain) @@ -80,16 +76,10 @@ def instance_traces(): issue_count = Issue.objects.filter(workspace=workspace).count() module_count = Module.objects.filter(workspace=workspace).count() cycle_count = Cycle.objects.filter(workspace=workspace).count() - cycle_issue_count = CycleIssue.objects.filter( - workspace=workspace - ).count() - module_issue_count = ModuleIssue.objects.filter( - workspace=workspace - ).count() + cycle_issue_count = CycleIssue.objects.filter(workspace=workspace).count() + module_issue_count = ModuleIssue.objects.filter(workspace=workspace).count() page_count = Page.objects.filter(workspace=workspace).count() - member_count = WorkspaceMember.objects.filter( - workspace=workspace - ).count() + member_count = WorkspaceMember.objects.filter(workspace=workspace).count() # Set span attributes with tracer.start_as_current_span("workspace_details") as span: diff --git a/apps/api/plane/license/management/commands/configure_instance.py b/apps/api/plane/license/management/commands/configure_instance.py index 1414c970c..5611eec52 100644 --- a/apps/api/plane/license/management/commands/configure_instance.py +++ b/apps/api/plane/license/management/commands/configure_instance.py @@ -190,9 +190,7 @@ class Command(BaseCommand): ] for item in config_keys: - obj, created = InstanceConfiguration.objects.get_or_create( - key=item.get("key") - ) + obj, created = InstanceConfiguration.objects.get_or_create(key=item.get("key")) if created: obj.category = item.get("category") obj.is_encrypted = item.get("is_encrypted", False) @@ -201,15 +199,9 @@ class Command(BaseCommand): else: obj.value = item.get("value") obj.save() - self.stdout.write( - self.style.SUCCESS( - f"{obj.key} loaded with value from environment variable." - ) - ) + self.stdout.write(self.style.SUCCESS(f"{obj.key} loaded with value from environment variable.")) else: - self.stdout.write( - self.style.WARNING(f"{obj.key} configuration already exists") - ) + self.stdout.write(self.style.WARNING(f"{obj.key} configuration already exists")) keys = ["IS_GOOGLE_ENABLED", "IS_GITHUB_ENABLED", "IS_GITLAB_ENABLED"] if not InstanceConfiguration.objects.filter(key__in=keys).exists(): @@ -237,11 +229,7 @@ class Command(BaseCommand): category="AUTHENTICATION", is_encrypted=False, ) - self.stdout.write( - self.style.SUCCESS( - f"{key} loaded with value from environment variable." - ) - ) + self.stdout.write(self.style.SUCCESS(f"{key} loaded with value from environment variable.")) if key == "IS_GITHUB_ENABLED": GITHUB_CLIENT_ID, GITHUB_CLIENT_SECRET = get_configuration_value( [ @@ -265,39 +253,25 @@ class Command(BaseCommand): category="AUTHENTICATION", is_encrypted=False, ) - self.stdout.write( - self.style.SUCCESS( - f"{key} loaded with value from environment variable." - ) - ) + self.stdout.write(self.style.SUCCESS(f"{key} loaded with value from environment variable.")) if key == "IS_GITLAB_ENABLED": - GITLAB_HOST, GITLAB_CLIENT_ID, GITLAB_CLIENT_SECRET = ( - get_configuration_value( - [ - { - "key": "GITLAB_HOST", - "default": os.environ.get( - "GITLAB_HOST", "https://gitlab.com" - ), - }, - { - "key": "GITLAB_CLIENT_ID", - "default": os.environ.get("GITLAB_CLIENT_ID", ""), - }, - { - "key": "GITLAB_CLIENT_SECRET", - "default": os.environ.get( - "GITLAB_CLIENT_SECRET", "" - ), - }, - ] - ) + GITLAB_HOST, GITLAB_CLIENT_ID, GITLAB_CLIENT_SECRET = get_configuration_value( + [ + { + "key": "GITLAB_HOST", + "default": os.environ.get("GITLAB_HOST", "https://gitlab.com"), + }, + { + "key": "GITLAB_CLIENT_ID", + "default": os.environ.get("GITLAB_CLIENT_ID", ""), + }, + { + "key": "GITLAB_CLIENT_SECRET", + "default": os.environ.get("GITLAB_CLIENT_SECRET", ""), + }, + ] ) - if ( - bool(GITLAB_HOST) - and bool(GITLAB_CLIENT_ID) - and bool(GITLAB_CLIENT_SECRET) - ): + if bool(GITLAB_HOST) and bool(GITLAB_CLIENT_ID) and bool(GITLAB_CLIENT_SECRET): value = "1" else: value = "0" @@ -307,13 +281,7 @@ class Command(BaseCommand): category="AUTHENTICATION", is_encrypted=False, ) - self.stdout.write( - self.style.SUCCESS( - f"{key} loaded with value from environment variable." - ) - ) + self.stdout.write(self.style.SUCCESS(f"{key} loaded with value from environment variable.")) else: for key in keys: - self.stdout.write( - self.style.WARNING(f"{key} configuration already exists") - ) + self.stdout.write(self.style.WARNING(f"{key} configuration already exists")) diff --git a/apps/api/plane/license/models/instance.py b/apps/api/plane/license/models/instance.py index 0e596d8de..1767d8c22 100644 --- a/apps/api/plane/license/models/instance.py +++ b/apps/api/plane/license/models/instance.py @@ -22,9 +22,7 @@ class Instance(BaseModel): instance_id = models.CharField(max_length=255, unique=True) current_version = models.CharField(max_length=255) latest_version = models.CharField(max_length=255, null=True, blank=True) - edition = models.CharField( - max_length=255, default=InstanceEdition.PLANE_COMMUNITY.value - ) + edition = models.CharField(max_length=255, default=InstanceEdition.PLANE_COMMUNITY.value) domain = models.TextField(blank=True) # Instance specifics last_checked_at = models.DateTimeField() @@ -55,9 +53,7 @@ class InstanceAdmin(BaseModel): null=True, related_name="instance_owner", ) - instance = models.ForeignKey( - Instance, on_delete=models.CASCADE, related_name="admins" - ) + instance = models.ForeignKey(Instance, on_delete=models.CASCADE, related_name="admins") role = models.PositiveIntegerField(choices=ROLE_CHOICES, default=20) is_verified = models.BooleanField(default=False) diff --git a/apps/api/plane/license/utils/encryption.py b/apps/api/plane/license/utils/encryption.py index 6781605dd..d56766d1e 100644 --- a/apps/api/plane/license/utils/encryption.py +++ b/apps/api/plane/license/utils/encryption.py @@ -31,9 +31,7 @@ def decrypt_data(encrypted_data): try: if encrypted_data: cipher_suite = Fernet(derive_key(settings.SECRET_KEY)) - decrypted_data = cipher_suite.decrypt( - encrypted_data.encode() - ) # Convert string back to bytes + decrypted_data = cipher_suite.decrypt(encrypted_data.encode()) # Convert string back to bytes return decrypted_data.decode() else: return "" diff --git a/apps/api/plane/license/utils/instance_value.py b/apps/api/plane/license/utils/instance_value.py index 72241fe54..8901bc814 100644 --- a/apps/api/plane/license/utils/instance_value.py +++ b/apps/api/plane/license/utils/instance_value.py @@ -14,9 +14,7 @@ def get_configuration_value(keys): environment_list = [] if settings.SKIP_ENV_VAR: # Get the configurations - instance_configuration = InstanceConfiguration.objects.values( - "key", "value", "is_encrypted" - ) + instance_configuration = InstanceConfiguration.objects.values("key", "value", "is_encrypted") for key in keys: for item in instance_configuration: @@ -51,9 +49,7 @@ def get_email_configuration(): {"key": "EMAIL_USE_SSL", "default": os.environ.get("EMAIL_USE_SSL", "0")}, { "key": "EMAIL_FROM", - "default": os.environ.get( - "EMAIL_FROM", "Team Plane " - ), + "default": os.environ.get("EMAIL_FROM", "Team Plane "), }, ] ) diff --git a/apps/api/plane/middleware/db_routing.py b/apps/api/plane/middleware/db_routing.py index dc7ff3fa3..68b5c4491 100644 --- a/apps/api/plane/middleware/db_routing.py +++ b/apps/api/plane/middleware/db_routing.py @@ -154,9 +154,7 @@ class ReadReplicaRoutingMiddleware: # The try/finally in __call__ should handle most cases, but this # provides extra protection specifically for view exceptions clear_read_replica_context() - logger.debug( - f"Cleaned up read replica context due to exception: {type(exception).__name__}" - ) + logger.debug(f"Cleaned up read replica context due to exception: {type(exception).__name__}") # Return None to let the exception continue propagating return None diff --git a/apps/api/plane/middleware/logger.py b/apps/api/plane/middleware/logger.py index 7481c3992..62f868476 100644 --- a/apps/api/plane/middleware/logger.py +++ b/apps/api/plane/middleware/logger.py @@ -47,10 +47,7 @@ class RequestLoggerMiddleware: return response user_id = ( - request.user.id - if getattr(request, "user") - and getattr(request.user, "is_authenticated", False) - else None + request.user.id if getattr(request, "user") and getattr(request.user, "is_authenticated", False) else None ) user_agent = request.META.get("HTTP_USER_AGENT", "") @@ -97,11 +94,7 @@ class APITokenLogMiddleware: return None # Check if content is binary by looking for common binary file signatures - if ( - content.startswith(b"\x89PNG") - or content.startswith(b"\xff\xd8\xff") - or content.startswith(b"%PDF") - ): + if content.startswith(b"\x89PNG") or content.startswith(b"\xff\xd8\xff") or content.startswith(b"%PDF"): return "[Binary Content]" try: @@ -121,14 +114,8 @@ class APITokenLogMiddleware: method=request.method, query_params=request.META.get("QUERY_STRING", ""), headers=str(request.headers), - body=( - self._safe_decode_body(request_body) if request_body else None - ), - response_body=( - self._safe_decode_body(response.content) - if response.content - else None - ), + body=(self._safe_decode_body(request_body) if request_body else None), + response_body=(self._safe_decode_body(response.content) if response.content else None), response_code=response.status_code, ip_address=get_client_ip(request=request), user_agent=request.META.get("HTTP_USER_AGENT", None), diff --git a/apps/api/plane/settings/common.py b/apps/api/plane/settings/common.py index 3c3410107..44a4d0d39 100644 --- a/apps/api/plane/settings/common.py +++ b/apps/api/plane/settings/common.py @@ -68,9 +68,7 @@ MIDDLEWARE = [ # Rest Framework settings REST_FRAMEWORK = { - "DEFAULT_AUTHENTICATION_CLASSES": ( - "rest_framework.authentication.SessionAuthentication", - ), + "DEFAULT_AUTHENTICATION_CLASSES": ("rest_framework.authentication.SessionAuthentication",), "DEFAULT_PERMISSION_CLASSES": ("rest_framework.permissions.IsAuthenticated",), "DEFAULT_RENDERER_CLASSES": ("rest_framework.renderers.JSONRenderer",), "DEFAULT_FILTER_BACKENDS": ("django_filters.rest_framework.DjangoFilterBackend",), @@ -107,16 +105,10 @@ TEMPLATES = [ CORS_ALLOW_CREDENTIALS = True cors_origins_raw = os.environ.get("CORS_ALLOWED_ORIGINS", "") # filter out empty strings -cors_allowed_origins = [ - origin.strip() for origin in cors_origins_raw.split(",") if origin.strip() -] +cors_allowed_origins = [origin.strip() for origin in cors_origins_raw.split(",") if origin.strip()] if cors_allowed_origins: CORS_ALLOWED_ORIGINS = cors_allowed_origins - secure_origins = ( - False - if [origin for origin in cors_allowed_origins if "http:" in origin] - else True - ) + secure_origins = False if [origin for origin in cors_allowed_origins if "http:" in origin] else True else: CORS_ALLOW_ALL_ORIGINS = True secure_origins = False @@ -153,9 +145,7 @@ else: if os.environ.get("ENABLE_READ_REPLICA", "0") == "1": if bool(os.environ.get("DATABASE_READ_REPLICA_URL")): # Parse database configuration from $DATABASE_URL - DATABASES["replica"] = dj_database_url.parse( - os.environ.get("DATABASE_READ_REPLICA_URL") - ) + DATABASES["replica"] = dj_database_url.parse(os.environ.get("DATABASE_READ_REPLICA_URL")) else: DATABASES["replica"] = { "ENGINE": "django.db.backends.postgresql", @@ -198,9 +188,7 @@ else: # Password validations AUTH_PASSWORD_VALIDATORS = [ - { - "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator" - }, + {"NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator"}, {"NAME": "django.contrib.auth.password_validation.MinimumLengthValidator"}, {"NAME": "django.contrib.auth.password_validation.CommonPasswordValidator"}, {"NAME": "django.contrib.auth.password_validation.NumericPasswordValidator"}, @@ -237,11 +225,7 @@ EMAIL_BACKEND = "django.core.mail.backends.smtp.EmailBackend" # Use Minio settings USE_MINIO = int(os.environ.get("USE_MINIO", 0)) == 1 -STORAGES = { - "staticfiles": { - "BACKEND": "whitenoise.storage.CompressedManifestStaticFilesStorage" - } -} +STORAGES = {"staticfiles": {"BACKEND": "whitenoise.storage.CompressedManifestStaticFilesStorage"}} STORAGES["default"] = {"BACKEND": "plane.settings.storage.S3Storage"} AWS_ACCESS_KEY_ID = os.environ.get("AWS_ACCESS_KEY_ID", "access-key") AWS_SECRET_ACCESS_KEY = os.environ.get("AWS_SECRET_ACCESS_KEY", "secret-key") @@ -250,9 +234,7 @@ AWS_REGION = os.environ.get("AWS_REGION", "") AWS_DEFAULT_ACL = "public-read" AWS_QUERYSTRING_AUTH = False AWS_S3_FILE_OVERWRITE = False -AWS_S3_ENDPOINT_URL = os.environ.get("AWS_S3_ENDPOINT_URL", None) or os.environ.get( - "MINIO_ENDPOINT_URL", None -) +AWS_S3_ENDPOINT_URL = os.environ.get("AWS_S3_ENDPOINT_URL", None) or os.environ.get("MINIO_ENDPOINT_URL", None) if AWS_S3_ENDPOINT_URL and USE_MINIO: parsed_url = urlparse(os.environ.get("WEB_URL", "http://localhost")) AWS_S3_CUSTOM_DOMAIN = f"{parsed_url.netloc}/{AWS_STORAGE_BUCKET_NAME}" diff --git a/apps/api/plane/settings/local.py b/apps/api/plane/settings/local.py index 15af36a2d..84737712b 100644 --- a/apps/api/plane/settings/local.py +++ b/apps/api/plane/settings/local.py @@ -13,9 +13,7 @@ MIDDLEWARE += ("debug_toolbar.middleware.DebugToolbarMiddleware",) # noqa DEBUG_TOOLBAR_PATCH_SETTINGS = False # Only show emails in console don't send it to smtp -EMAIL_BACKEND = os.environ.get( - "EMAIL_BACKEND", "django.core.mail.backends.console.EmailBackend" -) +EMAIL_BACKEND = os.environ.get("EMAIL_BACKEND", "django.core.mail.backends.console.EmailBackend") CACHES = { "default": { diff --git a/apps/api/plane/settings/mongo.py b/apps/api/plane/settings/mongo.py index 57d25b477..879d0c436 100644 --- a/apps/api/plane/settings/mongo.py +++ b/apps/api/plane/settings/mongo.py @@ -101,9 +101,7 @@ class MongoConnection: try: db = cls.get_db() if db is None: - logger.warning( - f"Cannot access collection '{collection_name}': MongoDB not configured" - ) + logger.warning(f"Cannot access collection '{collection_name}': MongoDB not configured") return None return db[collection_name] except Exception as e: diff --git a/apps/api/plane/settings/production.py b/apps/api/plane/settings/production.py index 4f4e99bdb..4725db38a 100644 --- a/apps/api/plane/settings/production.py +++ b/apps/api/plane/settings/production.py @@ -28,9 +28,7 @@ LOGGING = { "version": 1, "disable_existing_loggers": True, "formatters": { - "verbose": { - "format": "%(asctime)s [%(process)d] %(levelname)s %(name)s: %(message)s" - }, + "verbose": {"format": "%(asctime)s [%(process)d] %(levelname)s %(name)s: %(message)s"}, "json": { "()": "pythonjsonlogger.jsonlogger.JsonFormatter", "fmt": "%(levelname)s %(asctime)s %(module)s %(name)s %(message)s", diff --git a/apps/api/plane/settings/storage.py b/apps/api/plane/settings/storage.py index 71709ebe0..0a0720086 100644 --- a/apps/api/plane/settings/storage.py +++ b/apps/api/plane/settings/storage.py @@ -28,9 +28,7 @@ class S3Storage(S3Boto3Storage): # Use the AWS_REGION environment variable for the region self.aws_region = os.environ.get("AWS_REGION") # Use the AWS_S3_ENDPOINT_URL environment variable for the endpoint URL - self.aws_s3_endpoint_url = os.environ.get( - "AWS_S3_ENDPOINT_URL" - ) or os.environ.get("MINIO_ENDPOINT_URL") + self.aws_s3_endpoint_url = os.environ.get("AWS_S3_ENDPOINT_URL") or os.environ.get("MINIO_ENDPOINT_URL") if os.environ.get("USE_MINIO") == "1": # Determine protocol based on environment variable @@ -44,11 +42,7 @@ class S3Storage(S3Boto3Storage): aws_access_key_id=self.aws_access_key_id, aws_secret_access_key=self.aws_secret_access_key, region_name=self.aws_region, - endpoint_url=( - f"{endpoint_protocol}://{request.get_host()}" - if request - else self.aws_s3_endpoint_url - ), + endpoint_url=(f"{endpoint_protocol}://{request.get_host()}" if request else self.aws_s3_endpoint_url), config=boto3.session.Config(signature_version="s3v4"), ) else: @@ -62,9 +56,7 @@ class S3Storage(S3Boto3Storage): config=boto3.session.Config(signature_version="s3v4"), ) - def generate_presigned_post( - self, object_name, file_type, file_size, expiration=3600 - ): + def generate_presigned_post(self, object_name, file_type, file_size, expiration=3600): """Generate a presigned URL to upload an S3 object""" fields = {"Content-Type": file_type} @@ -76,9 +68,7 @@ class S3Storage(S3Boto3Storage): # Add condition for the object name (key) if object_name.startswith("${filename}"): - conditions.append( - ["starts-with", "$key", object_name[: -len("${filename}")]] - ) + conditions.append(["starts-with", "$key", object_name[: -len("${filename}")]]) else: fields["key"] = object_name conditions.append({"key": object_name}) @@ -142,9 +132,7 @@ class S3Storage(S3Boto3Storage): def get_object_metadata(self, object_name): """Get the metadata for an S3 object""" try: - response = self.s3_client.head_object( - Bucket=self.aws_storage_bucket_name, Key=object_name - ) + response = self.s3_client.head_object(Bucket=self.aws_storage_bucket_name, Key=object_name) except ClientError as e: log_exception(e) return None @@ -152,11 +140,7 @@ class S3Storage(S3Boto3Storage): return { "ContentType": response.get("ContentType"), "ContentLength": response.get("ContentLength"), - "LastModified": ( - response.get("LastModified").isoformat() - if response.get("LastModified") - else None - ), + "LastModified": (response.get("LastModified").isoformat() if response.get("LastModified") else None), "ETag": response.get("ETag"), "Metadata": response.get("Metadata", {}), } diff --git a/apps/api/plane/space/serializer/issue.py b/apps/api/plane/space/serializer/issue.py index 64f151a2d..a89846cfc 100644 --- a/apps/api/plane/space/serializer/issue.py +++ b/apps/api/plane/space/serializer/issue.py @@ -130,12 +130,8 @@ class IssueLinkSerializer(BaseSerializer): # Validation if url already exists def create(self, validated_data): - if IssueLink.objects.filter( - url=validated_data.get("url"), issue_id=validated_data.get("issue_id") - ).exists(): - raise serializers.ValidationError( - {"error": "URL already exists for this Issue"} - ) + if IssueLink.objects.filter(url=validated_data.get("url"), issue_id=validated_data.get("issue_id")).exists(): + raise serializers.ValidationError({"error": "URL already exists for this Issue"}) return IssueLink.objects.create(**validated_data) @@ -167,12 +163,8 @@ class IssueSerializer(BaseSerializer): parent_detail = IssueStateFlatSerializer(read_only=True, source="parent") label_details = LabelSerializer(read_only=True, source="labels", many=True) assignee_details = UserLiteSerializer(read_only=True, source="assignees", many=True) - related_issues = IssueRelationSerializer( - read_only=True, source="issue_relation", many=True - ) - issue_relations = RelatedIssueSerializer( - read_only=True, source="issue_related", many=True - ) + related_issues = IssueRelationSerializer(read_only=True, source="issue_relation", many=True) + issue_relations = RelatedIssueSerializer(read_only=True, source="issue_related", many=True) issue_cycle = IssueCycleDetailSerializer(read_only=True) issue_module = IssueModuleDetailSerializer(read_only=True) issue_link = IssueLinkSerializer(read_only=True, many=True) @@ -290,13 +282,9 @@ class IssueCreateSerializer(BaseSerializer): # Validate description content for security if "description_html" in data and data["description_html"]: - is_valid, error_msg, sanitized_html = validate_html_content( - data["description_html"] - ) + is_valid, error_msg, sanitized_html = validate_html_content(data["description_html"]) if not is_valid: - raise serializers.ValidationError( - {"error": "html content is not valid"} - ) + raise serializers.ValidationError({"error": "html content is not valid"}) # Update the data with sanitized HTML if available if sanitized_html is not None: data["description_html"] = sanitized_html @@ -431,9 +419,7 @@ class IssueVoteSerializer(BaseSerializer): class IssuePublicSerializer(BaseSerializer): - reactions = IssueReactionSerializer( - read_only=True, many=True, source="issue_reactions" - ) + reactions = IssueReactionSerializer(read_only=True, many=True, source="issue_reactions") votes = IssueVoteSerializer(read_only=True, many=True) module_ids = serializers.ListField(child=serializers.UUIDField(), required=False) label_ids = serializers.ListField(child=serializers.UUIDField(), required=False) diff --git a/apps/api/plane/space/urls/intake.py b/apps/api/plane/space/urls/intake.py index 09aca16df..59fda12e2 100644 --- a/apps/api/plane/space/urls/intake.py +++ b/apps/api/plane/space/urls/intake.py @@ -20,9 +20,7 @@ urlpatterns = [ ), path( "anchor//intakes//intake-issues//", - IntakeIssuePublicViewSet.as_view( - {"get": "retrieve", "patch": "partial_update", "delete": "destroy"} - ), + IntakeIssuePublicViewSet.as_view({"get": "retrieve", "patch": "partial_update", "delete": "destroy"}), name="intake-issue", ), path( diff --git a/apps/api/plane/space/urls/issue.py b/apps/api/plane/space/urls/issue.py index 2391fd38a..bb63e6695 100644 --- a/apps/api/plane/space/urls/issue.py +++ b/apps/api/plane/space/urls/issue.py @@ -22,9 +22,7 @@ urlpatterns = [ ), path( "anchor//issues//comments//", - IssueCommentPublicViewSet.as_view( - {"get": "retrieve", "patch": "partial_update", "delete": "destroy"} - ), + IssueCommentPublicViewSet.as_view({"get": "retrieve", "patch": "partial_update", "delete": "destroy"}), name="issue-comments-project-board", ), path( @@ -49,9 +47,7 @@ urlpatterns = [ ), path( "anchor//issues//votes/", - IssueVotePublicViewSet.as_view( - {"get": "list", "post": "create", "delete": "destroy"} - ), + IssueVotePublicViewSet.as_view({"get": "list", "post": "create", "delete": "destroy"}), name="issue-vote-project-board", ), ] diff --git a/apps/api/plane/space/utils/grouper.py b/apps/api/plane/space/utils/grouper.py index 9e18f172e..f8e2c50a4 100644 --- a/apps/api/plane/space/utils/grouper.py +++ b/apps/api/plane/space/utils/grouper.py @@ -169,9 +169,7 @@ def issue_on_results( default=None, output_field=JSONField(), ), - filter=Q( - issue_reactions__isnull=False, issue_reactions__deleted_at__isnull=True - ), + filter=Q(issue_reactions__isnull=False, issue_reactions__deleted_at__isnull=True), distinct=True, ), ).values(*required_fields, "vote_items", "reaction_items") @@ -187,17 +185,13 @@ def issue_group_values( queryset: Optional[QuerySet] = None, ) -> List[Union[str, Any]]: if field == "state_id": - queryset = State.objects.filter( - is_triage=False, workspace__slug=slug - ).values_list("id", flat=True) + queryset = State.objects.filter(is_triage=False, workspace__slug=slug).values_list("id", flat=True) if project_id: return list(queryset.filter(project_id=project_id)) else: return list(queryset) if field == "labels__id": - queryset = Label.objects.filter(workspace__slug=slug).values_list( - "id", flat=True - ) + queryset = Label.objects.filter(workspace__slug=slug).values_list("id", flat=True) if project_id: return list(queryset.filter(project_id=project_id)) + ["None"] else: @@ -209,30 +203,22 @@ def issue_group_values( ).values_list("member_id", flat=True) else: return list( - WorkspaceMember.objects.filter( - workspace__slug=slug, is_active=True - ).values_list("member_id", flat=True) + WorkspaceMember.objects.filter(workspace__slug=slug, is_active=True).values_list("member_id", flat=True) ) if field == "issue_module__module_id": - queryset = Module.objects.filter(workspace__slug=slug).values_list( - "id", flat=True - ) + queryset = Module.objects.filter(workspace__slug=slug).values_list("id", flat=True) if project_id: return list(queryset.filter(project_id=project_id)) + ["None"] else: return list(queryset) + ["None"] if field == "cycle_id": - queryset = Cycle.objects.filter(workspace__slug=slug).values_list( - "id", flat=True - ) + queryset = Cycle.objects.filter(workspace__slug=slug).values_list("id", flat=True) if project_id: return list(queryset.filter(project_id=project_id)) + ["None"] else: return list(queryset) + ["None"] if field == "project_id": - queryset = Project.objects.filter(workspace__slug=slug).values_list( - "id", flat=True - ) + queryset = Project.objects.filter(workspace__slug=slug).values_list("id", flat=True) return list(queryset) if field == "priority": return ["low", "medium", "high", "urgent", "none"] diff --git a/apps/api/plane/space/views/asset.py b/apps/api/plane/space/views/asset.py index d2537671f..6ed5ab9b6 100644 --- a/apps/api/plane/space/views/asset.py +++ b/apps/api/plane/space/views/asset.py @@ -62,14 +62,10 @@ class EntityAssetEndpoint(BaseAPIView): def post(self, request, anchor): # Get the deploy board - deploy_board = DeployBoard.objects.filter( - anchor=anchor, entity_name="project" - ).first() + deploy_board = DeployBoard.objects.filter(anchor=anchor).first() # Check if the project is published if not deploy_board: - return Response( - {"error": "Project is not published"}, status=status.HTTP_404_NOT_FOUND - ) + return Response({"error": "Project is not published"}, status=status.HTTP_404_NOT_FOUND) # Get the asset name = request.data.get("name") @@ -120,9 +116,7 @@ class EntityAssetEndpoint(BaseAPIView): # 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 - ) + presigned_url = storage.generate_presigned_post(object_name=asset_key, file_type=type, file_size=size) # Return the presigned URL return Response( { @@ -135,14 +129,10 @@ class EntityAssetEndpoint(BaseAPIView): def patch(self, request, anchor, pk): # Get the deploy board - deploy_board = DeployBoard.objects.filter( - anchor=anchor, entity_name="project" - ).first() + deploy_board = DeployBoard.objects.filter(anchor=anchor).first() # Check if the project is published if not deploy_board: - return Response( - {"error": "Project is not published"}, status=status.HTTP_404_NOT_FOUND - ) + return Response({"error": "Project is not published"}, status=status.HTTP_404_NOT_FOUND) # get the asset id asset = FileAsset.objects.get(id=pk, workspace=deploy_board.workspace) @@ -160,18 +150,12 @@ class EntityAssetEndpoint(BaseAPIView): def delete(self, request, anchor, pk): # Get the deploy board - deploy_board = DeployBoard.objects.filter( - anchor=anchor, entity_name="project" - ).first() + deploy_board = DeployBoard.objects.filter(anchor=anchor, entity_name="project").first() # Check if the project is published if not deploy_board: - return Response( - {"error": "Project is not published"}, status=status.HTTP_404_NOT_FOUND - ) + return Response({"error": "Project is not published"}, status=status.HTTP_404_NOT_FOUND) # Get the asset - asset = FileAsset.objects.get( - id=pk, workspace=deploy_board.workspace, project_id=deploy_board.project_id - ) + asset = FileAsset.objects.get(id=pk, workspace=deploy_board.workspace, project_id=deploy_board.project_id) # Check deleted assets asset.is_deleted = True asset.deleted_at = timezone.now() @@ -185,14 +169,10 @@ class AssetRestoreEndpoint(BaseAPIView): def post(self, request, anchor, asset_id): # Get the deploy board - deploy_board = DeployBoard.objects.filter( - anchor=anchor, entity_name="project" - ).first() + deploy_board = DeployBoard.objects.filter(anchor=anchor, entity_name="project").first() # Check if the project is published if not deploy_board: - return Response( - {"error": "Project is not published"}, status=status.HTTP_404_NOT_FOUND - ) + return Response({"error": "Project is not published"}, status=status.HTTP_404_NOT_FOUND) # Get the asset asset = FileAsset.all_objects.get(id=asset_id, workspace=deploy_board.workspace) @@ -207,22 +187,16 @@ class EntityBulkAssetEndpoint(BaseAPIView): def post(self, request, anchor, entity_id): # Get the deploy board - deploy_board = DeployBoard.objects.filter( - anchor=anchor, entity_name="project" - ).first() + deploy_board = DeployBoard.objects.filter(anchor=anchor, entity_name="project").first() # Check if the project is published if not deploy_board: - return Response( - {"error": "Project is not published"}, status=status.HTTP_404_NOT_FOUND - ) + return Response({"error": "Project is not published"}, status=status.HTTP_404_NOT_FOUND) asset_ids = request.data.get("asset_ids", []) # 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( diff --git a/apps/api/plane/space/views/base.py b/apps/api/plane/space/views/base.py index 82809f08d..9be6a2e10 100644 --- a/apps/api/plane/space/views/base.py +++ b/apps/api/plane/space/views/base.py @@ -105,9 +105,7 @@ class BaseViewSet(TimezoneMixin, ModelViewSet, BasePaginator): if settings.DEBUG: from django.db import connection - print( - f"{request.method} - {request.get_full_path()} of Queries: {len(connection.queries)}" - ) + print(f"{request.method} - {request.get_full_path()} of Queries: {len(connection.queries)}") return response except Exception as exc: @@ -190,9 +188,7 @@ class BaseAPIView(TimezoneMixin, APIView, BasePaginator): if settings.DEBUG: from django.db import connection - print( - f"{request.method} - {request.get_full_path()} of Queries: {len(connection.queries)}" - ) + print(f"{request.method} - {request.get_full_path()} of Queries: {len(connection.queries)}") return response except Exception as exc: diff --git a/apps/api/plane/space/views/cycle.py b/apps/api/plane/space/views/cycle.py index 399d626a1..505c17ba4 100644 --- a/apps/api/plane/space/views/cycle.py +++ b/apps/api/plane/space/views/cycle.py @@ -14,9 +14,7 @@ class ProjectCyclesEndpoint(BaseAPIView): def get(self, request, anchor): deploy_board = DeployBoard.objects.filter(anchor=anchor).first() if not deploy_board: - return Response( - {"error": "Invalid anchor"}, status=status.HTTP_404_NOT_FOUND - ) + return Response({"error": "Invalid anchor"}, status=status.HTTP_404_NOT_FOUND) cycles = Cycle.objects.filter( workspace__slug=deploy_board.workspace.slug, diff --git a/apps/api/plane/space/views/intake.py b/apps/api/plane/space/views/intake.py index 83ec354c6..60d4443b5 100644 --- a/apps/api/plane/space/views/intake.py +++ b/apps/api/plane/space/views/intake.py @@ -50,9 +50,7 @@ class IntakeIssuePublicViewSet(BaseViewSet): return IntakeIssue.objects.none() def list(self, request, anchor, intake_id): - project_deploy_board = DeployBoard.objects.get( - anchor=anchor, entity_name="project" - ) + project_deploy_board = DeployBoard.objects.get(anchor=anchor, entity_name="project") if project_deploy_board.intake is None: return Response( {"error": "Intake is not enabled for this Project Board"}, @@ -95,9 +93,7 @@ class IntakeIssuePublicViewSet(BaseViewSet): .prefetch_related( Prefetch( "issue_intake", - queryset=IntakeIssue.objects.only( - "status", "duplicate_to", "snoozed_till", "source" - ), + queryset=IntakeIssue.objects.only("status", "duplicate_to", "snoozed_till", "source"), ) ) ) @@ -105,9 +101,7 @@ class IntakeIssuePublicViewSet(BaseViewSet): return Response(issues_data, status=status.HTTP_200_OK) def create(self, request, anchor, intake_id): - project_deploy_board = DeployBoard.objects.get( - anchor=anchor, entity_name="project" - ) + project_deploy_board = DeployBoard.objects.get(anchor=anchor, entity_name="project") if project_deploy_board.intake is None: return Response( {"error": "Intake is not enabled for this Project Board"}, @@ -115,9 +109,7 @@ class IntakeIssuePublicViewSet(BaseViewSet): ) if not request.data.get("issue", {}).get("name", False): - return Response( - {"error": "Name is required"}, status=status.HTTP_400_BAD_REQUEST - ) + return Response({"error": "Name is required"}, status=status.HTTP_400_BAD_REQUEST) # Check for valid priority if request.data.get("issue", {}).get("priority", "none") not in [ @@ -127,17 +119,13 @@ class IntakeIssuePublicViewSet(BaseViewSet): "urgent", "none", ]: - return Response( - {"error": "Invalid priority"}, status=status.HTTP_400_BAD_REQUEST - ) + return Response({"error": "Invalid priority"}, status=status.HTTP_400_BAD_REQUEST) # create an issue issue = Issue.objects.create( name=request.data.get("issue", {}).get("name"), description=request.data.get("issue", {}).get("description", {}), - description_html=request.data.get("issue", {}).get( - "description_html", "

" - ), + description_html=request.data.get("issue", {}).get("description_html", "

"), priority=request.data.get("issue", {}).get("priority", "low"), project_id=project_deploy_board.project_id, ) @@ -164,9 +152,7 @@ class IntakeIssuePublicViewSet(BaseViewSet): return Response(serializer.data, status=status.HTTP_200_OK) def partial_update(self, request, anchor, intake_id, pk): - project_deploy_board = DeployBoard.objects.get( - anchor=anchor, entity_name="project" - ) + project_deploy_board = DeployBoard.objects.get(anchor=anchor, entity_name="project") if project_deploy_board.intake is None: return Response( {"error": "Intake is not enabled for this Project Board"}, @@ -197,9 +183,7 @@ class IntakeIssuePublicViewSet(BaseViewSet): # viewers and guests since only viewers and guests issue_data = { "name": issue_data.get("name", issue.name), - "description_html": issue_data.get( - "description_html", issue.description_html - ), + "description_html": issue_data.get("description_html", issue.description_html), "description": issue_data.get("description", issue.description), } @@ -221,9 +205,7 @@ class IntakeIssuePublicViewSet(BaseViewSet): actor_id=str(request.user.id), issue_id=str(issue.id), project_id=str(project_deploy_board.project_id), - current_instance=json.dumps( - IssueSerializer(current_instance).data, cls=DjangoJSONEncoder - ), + current_instance=json.dumps(IssueSerializer(current_instance).data, cls=DjangoJSONEncoder), epoch=int(timezone.now().timestamp()), ) issue_serializer.save() @@ -231,9 +213,7 @@ class IntakeIssuePublicViewSet(BaseViewSet): return Response(issue_serializer.errors, status=status.HTTP_400_BAD_REQUEST) def retrieve(self, request, anchor, intake_id, pk): - project_deploy_board = DeployBoard.objects.get( - anchor=anchor, entity_name="project" - ) + project_deploy_board = DeployBoard.objects.get(anchor=anchor, entity_name="project") if project_deploy_board.intake is None: return Response( {"error": "Intake is not enabled for this Project Board"}, @@ -255,9 +235,7 @@ class IntakeIssuePublicViewSet(BaseViewSet): return Response(serializer.data, status=status.HTTP_200_OK) def destroy(self, request, anchor, intake_id, pk): - project_deploy_board = DeployBoard.objects.get( - anchor=anchor, entity_name="project" - ) + project_deploy_board = DeployBoard.objects.get(anchor=anchor, entity_name="project") if project_deploy_board.intake is None: return Response( {"error": "Intake is not enabled for this Project Board"}, diff --git a/apps/api/plane/space/views/issue.py b/apps/api/plane/space/views/issue.py index 93aaaa7b9..220fc1307 100644 --- a/apps/api/plane/space/views/issue.py +++ b/apps/api/plane/space/views/issue.py @@ -73,13 +73,9 @@ class ProjectIssuesPublicEndpoint(BaseAPIView): filters = issue_filters(request.query_params, "GET") order_by_param = request.GET.get("order_by", "-created_at") - deploy_board = DeployBoard.objects.filter( - anchor=anchor, entity_name="project" - ).first() + deploy_board = DeployBoard.objects.filter(anchor=anchor, entity_name="project").first() if not deploy_board: - return Response( - {"error": "Project is not published"}, status=status.HTTP_404_NOT_FOUND - ) + return Response({"error": "Project is not published"}, status=status.HTTP_404_NOT_FOUND) project_id = deploy_board.entity_identifier slug = deploy_board.workspace.slug @@ -94,14 +90,10 @@ class ProjectIssuesPublicEndpoint(BaseAPIView): queryset=IssueReaction.objects.select_related("actor"), ) ) - .prefetch_related( - Prefetch("votes", queryset=IssueVote.objects.select_related("actor")) - ) + .prefetch_related(Prefetch("votes", queryset=IssueVote.objects.select_related("actor"))) .annotate( cycle_id=Subquery( - CycleIssue.objects.filter( - issue=OuterRef("id"), deleted_at__isnull=True - ).values("cycle_id")[:1] + CycleIssue.objects.filter(issue=OuterRef("id"), deleted_at__isnull=True).values("cycle_id")[:1] ) ) .annotate( @@ -139,17 +131,13 @@ class ProjectIssuesPublicEndpoint(BaseAPIView): sub_group_by = request.GET.get("sub_group_by", False) # issue queryset - issue_queryset = issue_queryset_grouper( - queryset=issue_queryset, group_by=group_by, sub_group_by=sub_group_by - ) + issue_queryset = issue_queryset_grouper(queryset=issue_queryset, group_by=group_by, sub_group_by=sub_group_by) if group_by: if sub_group_by: if group_by == sub_group_by: return Response( - { - "error": "Group by and sub group by cannot have same parameters" - }, + {"error": "Group by and sub group by cannot have same parameters"}, status=status.HTTP_400_BAD_REQUEST, ) else: @@ -215,9 +203,7 @@ class ProjectIssuesPublicEndpoint(BaseAPIView): order_by=order_by_param, request=request, queryset=issue_queryset, - on_results=lambda issues: issue_on_results( - group_by=group_by, issues=issues, sub_group_by=sub_group_by - ), + on_results=lambda issues: issue_on_results(group_by=group_by, issues=issues, sub_group_by=sub_group_by), ) @@ -237,9 +223,7 @@ class IssueCommentPublicViewSet(BaseViewSet): def get_queryset(self): try: - project_deploy_board = DeployBoard.objects.get( - anchor=self.kwargs.get("anchor"), entity_name="project" - ) + project_deploy_board = DeployBoard.objects.get(anchor=self.kwargs.get("anchor"), entity_name="project") if project_deploy_board.is_comments_enabled: return self.filter_queryset( super() @@ -267,9 +251,7 @@ class IssueCommentPublicViewSet(BaseViewSet): return IssueComment.objects.none() def create(self, request, anchor, issue_id): - project_deploy_board = DeployBoard.objects.get( - anchor=anchor, entity_name="project" - ) + project_deploy_board = DeployBoard.objects.get(anchor=anchor, entity_name="project") if not project_deploy_board.is_comments_enabled: return Response( @@ -308,9 +290,7 @@ class IssueCommentPublicViewSet(BaseViewSet): return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) def partial_update(self, request, anchor, issue_id, pk): - project_deploy_board = DeployBoard.objects.get( - anchor=anchor, entity_name="project" - ) + project_deploy_board = DeployBoard.objects.get(anchor=anchor, entity_name="project") if not project_deploy_board.is_comments_enabled: return Response( @@ -327,18 +307,14 @@ class IssueCommentPublicViewSet(BaseViewSet): actor_id=str(request.user.id), issue_id=str(issue_id), project_id=str(project_deploy_board.project_id), - current_instance=json.dumps( - IssueCommentSerializer(comment).data, cls=DjangoJSONEncoder - ), + current_instance=json.dumps(IssueCommentSerializer(comment).data, cls=DjangoJSONEncoder), epoch=int(timezone.now().timestamp()), ) return Response(serializer.data, status=status.HTTP_200_OK) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) def destroy(self, request, anchor, issue_id, pk): - project_deploy_board = DeployBoard.objects.get( - anchor=anchor, entity_name="project" - ) + project_deploy_board = DeployBoard.objects.get(anchor=anchor, entity_name="project") if not project_deploy_board.is_comments_enabled: return Response( @@ -352,9 +328,7 @@ class IssueCommentPublicViewSet(BaseViewSet): actor_id=str(request.user.id), issue_id=str(issue_id), project_id=str(project_deploy_board.project_id), - current_instance=json.dumps( - IssueCommentSerializer(comment).data, cls=DjangoJSONEncoder - ), + current_instance=json.dumps(IssueCommentSerializer(comment).data, cls=DjangoJSONEncoder), epoch=int(timezone.now().timestamp()), ) comment.delete() @@ -386,9 +360,7 @@ class IssueReactionPublicViewSet(BaseViewSet): return IssueReaction.objects.none() def create(self, request, anchor, issue_id): - project_deploy_board = DeployBoard.objects.get( - anchor=anchor, entity_name="project" - ) + project_deploy_board = DeployBoard.objects.get(anchor=anchor, entity_name="project") if not project_deploy_board.is_reactions_enabled: return Response( @@ -425,9 +397,7 @@ class IssueReactionPublicViewSet(BaseViewSet): return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) def destroy(self, request, anchor, issue_id, reaction_code): - project_deploy_board = DeployBoard.objects.get( - anchor=anchor, entity_name="project" - ) + project_deploy_board = DeployBoard.objects.get(anchor=anchor, entity_name="project") if not project_deploy_board.is_reactions_enabled: return Response( @@ -446,9 +416,7 @@ class IssueReactionPublicViewSet(BaseViewSet): actor_id=str(self.request.user.id), issue_id=str(self.kwargs.get("issue_id", None)), project_id=str(project_deploy_board.project_id), - current_instance=json.dumps( - {"reaction": str(reaction_code), "identifier": str(issue_reaction.id)} - ), + current_instance=json.dumps({"reaction": str(reaction_code), "identifier": str(issue_reaction.id)}), epoch=int(timezone.now().timestamp()), ) issue_reaction.delete() @@ -461,9 +429,7 @@ class CommentReactionPublicViewSet(BaseViewSet): def get_queryset(self): try: - project_deploy_board = DeployBoard.objects.get( - anchor=self.kwargs.get("anchor"), entity_name="project" - ) + project_deploy_board = DeployBoard.objects.get(anchor=self.kwargs.get("anchor"), entity_name="project") if project_deploy_board.is_reactions_enabled: return ( super() @@ -479,9 +445,7 @@ class CommentReactionPublicViewSet(BaseViewSet): return CommentReaction.objects.none() def create(self, request, anchor, comment_id): - project_deploy_board = DeployBoard.objects.get( - anchor=anchor, entity_name="project" - ) + project_deploy_board = DeployBoard.objects.get(anchor=anchor, entity_name="project") if not project_deploy_board.is_reactions_enabled: return Response( @@ -518,9 +482,7 @@ class CommentReactionPublicViewSet(BaseViewSet): return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) def destroy(self, request, anchor, comment_id, reaction_code): - project_deploy_board = DeployBoard.objects.get( - anchor=anchor, entity_name="project" - ) + project_deploy_board = DeployBoard.objects.get(anchor=anchor, entity_name="project") if not project_deploy_board.is_reactions_enabled: return Response( {"error": "Reactions are not enabled for this board"}, @@ -575,9 +537,7 @@ class IssueVotePublicViewSet(BaseViewSet): return IssueVote.objects.none() def create(self, request, anchor, issue_id): - project_deploy_board = DeployBoard.objects.get( - anchor=anchor, entity_name="project" - ) + project_deploy_board = DeployBoard.objects.get(anchor=anchor, entity_name="project") issue_vote, _ = IssueVote.objects.get_or_create( actor_id=request.user.id, project_id=project_deploy_board.project_id, @@ -607,9 +567,7 @@ class IssueVotePublicViewSet(BaseViewSet): return Response(serializer.data, status=status.HTTP_201_CREATED) def destroy(self, request, anchor, issue_id): - project_deploy_board = DeployBoard.objects.get( - anchor=anchor, entity_name="project" - ) + project_deploy_board = DeployBoard.objects.get(anchor=anchor, entity_name="project") issue_vote = IssueVote.objects.get( issue_id=issue_id, actor_id=request.user.id, @@ -622,9 +580,7 @@ class IssueVotePublicViewSet(BaseViewSet): actor_id=str(self.request.user.id), issue_id=str(self.kwargs.get("issue_id", None)), project_id=str(project_deploy_board.project_id), - current_instance=json.dumps( - {"vote": str(issue_vote.vote), "identifier": str(issue_vote.id)} - ), + current_instance=json.dumps({"vote": str(issue_vote.vote), "identifier": str(issue_vote.id)}), epoch=int(timezone.now().timestamp()), ) issue_vote.delete() @@ -647,9 +603,7 @@ class IssueRetrievePublicEndpoint(BaseAPIView): .prefetch_related("assignees", "labels", "issue_module__module") .annotate( cycle_id=Subquery( - CycleIssue.objects.filter( - issue=OuterRef("id"), deleted_at__isnull=True - ).values("cycle_id")[:1] + CycleIssue.objects.filter(issue=OuterRef("id"), deleted_at__isnull=True).values("cycle_id")[:1] ) ) .annotate( @@ -657,10 +611,7 @@ class IssueRetrievePublicEndpoint(BaseAPIView): ArrayAgg( "labels__id", distinct=True, - filter=Q( - ~Q(labels__id__isnull=True) - & Q(label_issue__deleted_at__isnull=True) - ), + filter=Q(~Q(labels__id__isnull=True) & Q(label_issue__deleted_at__isnull=True)), ), Value([], output_field=ArrayField(UUIDField())), ), @@ -693,9 +644,7 @@ class IssueRetrievePublicEndpoint(BaseAPIView): queryset=IssueReaction.objects.select_related("issue", "actor"), ) ) - .prefetch_related( - Prefetch("votes", queryset=IssueVote.objects.select_related("actor")) - ) + .prefetch_related(Prefetch("votes", queryset=IssueVote.objects.select_related("actor"))) .annotate( vote_items=ArrayAgg( Case( @@ -771,9 +720,7 @@ class IssueRetrievePublicEndpoint(BaseAPIView): default=Value(None), output_field=CharField(), ), - display_name=F( - "issue_reactions__actor__display_name" - ), + display_name=F("issue_reactions__actor__display_name"), ), ), ), diff --git a/apps/api/plane/space/views/label.py b/apps/api/plane/space/views/label.py index ad0c8f0ca..51ddb832e 100644 --- a/apps/api/plane/space/views/label.py +++ b/apps/api/plane/space/views/label.py @@ -14,9 +14,7 @@ class ProjectLabelsEndpoint(BaseAPIView): def get(self, request, anchor): deploy_board = DeployBoard.objects.filter(anchor=anchor).first() if not deploy_board: - return Response( - {"error": "Invalid anchor"}, status=status.HTTP_404_NOT_FOUND - ) + return Response({"error": "Invalid anchor"}, status=status.HTTP_404_NOT_FOUND) labels = Label.objects.filter( workspace__slug=deploy_board.workspace.slug, diff --git a/apps/api/plane/space/views/meta.py b/apps/api/plane/space/views/meta.py index dc7ecb648..be612db70 100644 --- a/apps/api/plane/space/views/meta.py +++ b/apps/api/plane/space/views/meta.py @@ -16,17 +16,13 @@ class ProjectMetaDataEndpoint(BaseAPIView): try: deploy_board = DeployBoard.objects.get(anchor=anchor, entity_name="project") except DeployBoard.DoesNotExist: - return Response( - {"error": "Project is not published"}, status=status.HTTP_404_NOT_FOUND - ) + return Response({"error": "Project is not published"}, status=status.HTTP_404_NOT_FOUND) try: project_id = deploy_board.entity_identifier project = Project.objects.get(id=project_id) except Project.DoesNotExist: - return Response( - {"error": "Project is not published"}, status=status.HTTP_404_NOT_FOUND - ) + return Response({"error": "Project is not published"}, status=status.HTTP_404_NOT_FOUND) serializer = ProjectLiteSerializer(project) return Response(serializer.data, status=status.HTTP_200_OK) diff --git a/apps/api/plane/space/views/module.py b/apps/api/plane/space/views/module.py index 7db676537..7c4628f64 100644 --- a/apps/api/plane/space/views/module.py +++ b/apps/api/plane/space/views/module.py @@ -14,9 +14,7 @@ class ProjectModulesEndpoint(BaseAPIView): def get(self, request, anchor): deploy_board = DeployBoard.objects.filter(anchor=anchor).first() if not deploy_board: - return Response( - {"error": "Invalid anchor"}, status=status.HTTP_404_NOT_FOUND - ) + return Response({"error": "Invalid anchor"}, status=status.HTTP_404_NOT_FOUND) modules = Module.objects.filter( workspace__slug=deploy_board.workspace.slug, diff --git a/apps/api/plane/space/views/project.py b/apps/api/plane/space/views/project.py index 1574871ef..6f332781f 100644 --- a/apps/api/plane/space/views/project.py +++ b/apps/api/plane/space/views/project.py @@ -16,9 +16,7 @@ class ProjectDeployBoardPublicSettingsEndpoint(BaseAPIView): permission_classes = [AllowAny] def get(self, request, anchor): - project_deploy_board = DeployBoard.objects.get( - anchor=anchor, entity_name="project" - ) + project_deploy_board = DeployBoard.objects.get(anchor=anchor, entity_name="project") serializer = DeployBoardSerializer(project_deploy_board) return Response(serializer.data, status=status.HTTP_200_OK) @@ -27,16 +25,12 @@ class WorkspaceProjectDeployBoardEndpoint(BaseAPIView): permission_classes = [AllowAny] def get(self, request, anchor): - deploy_board = DeployBoard.objects.filter( - anchor=anchor, entity_name="project" - ).values_list + deploy_board = DeployBoard.objects.filter(anchor=anchor, entity_name="project").values_list projects = ( Project.objects.filter(workspace=deploy_board.workspace) .annotate( is_public=Exists( - DeployBoard.objects.filter( - anchor=anchor, project_id=OuterRef("pk"), entity_name="project" - ) + DeployBoard.objects.filter(anchor=anchor, project_id=OuterRef("pk"), entity_name="project") ) ) .filter(is_public=True) diff --git a/apps/api/plane/space/views/state.py b/apps/api/plane/space/views/state.py index 39f2b1bfd..c13186600 100644 --- a/apps/api/plane/space/views/state.py +++ b/apps/api/plane/space/views/state.py @@ -17,9 +17,7 @@ class ProjectStatesEndpoint(BaseAPIView): def get(self, request, anchor): deploy_board = DeployBoard.objects.filter(anchor=anchor).first() if not deploy_board: - return Response( - {"error": "Invalid anchor"}, status=status.HTTP_404_NOT_FOUND - ) + return Response({"error": "Invalid anchor"}, status=status.HTTP_404_NOT_FOUND) states = State.objects.filter( ~Q(name="Triage"), diff --git a/apps/api/plane/tests/conftest.py b/apps/api/plane/tests/conftest.py index 15f3a8a28..abfede197 100644 --- a/apps/api/plane/tests/conftest.py +++ b/apps/api/plane/tests/conftest.py @@ -131,8 +131,6 @@ def workspace(create_user): slug="test-workspace", ) - WorkspaceMember.objects.create( - workspace=created_workspace, member=create_user, role=20 - ) + WorkspaceMember.objects.create(workspace=created_workspace, member=create_user, role=20) return created_workspace diff --git a/apps/api/plane/tests/conftest_external.py b/apps/api/plane/tests/conftest_external.py index b4853e531..cebb768ca 100644 --- a/apps/api/plane/tests/conftest_external.py +++ b/apps/api/plane/tests/conftest_external.py @@ -66,27 +66,15 @@ def mock_mongodb(): # Configure common MongoDB collection operations mock_mongo_collection.find_one.return_value = None - mock_mongo_collection.find.return_value = MagicMock( - __iter__=lambda x: iter([]), count=lambda: 0 - ) - mock_mongo_collection.insert_one.return_value = MagicMock( - inserted_id="mock_id_123", acknowledged=True - ) + mock_mongo_collection.find.return_value = MagicMock(__iter__=lambda x: iter([]), count=lambda: 0) + mock_mongo_collection.insert_one.return_value = MagicMock(inserted_id="mock_id_123", acknowledged=True) mock_mongo_collection.insert_many.return_value = MagicMock( inserted_ids=["mock_id_123", "mock_id_456"], acknowledged=True ) - mock_mongo_collection.update_one.return_value = MagicMock( - modified_count=1, matched_count=1, acknowledged=True - ) - mock_mongo_collection.update_many.return_value = MagicMock( - modified_count=2, matched_count=2, acknowledged=True - ) - mock_mongo_collection.delete_one.return_value = MagicMock( - deleted_count=1, acknowledged=True - ) - mock_mongo_collection.delete_many.return_value = MagicMock( - deleted_count=2, acknowledged=True - ) + mock_mongo_collection.update_one.return_value = MagicMock(modified_count=1, matched_count=1, acknowledged=True) + mock_mongo_collection.update_many.return_value = MagicMock(modified_count=2, matched_count=2, acknowledged=True) + mock_mongo_collection.delete_one.return_value = MagicMock(deleted_count=1, acknowledged=True) + mock_mongo_collection.delete_many.return_value = MagicMock(deleted_count=2, acknowledged=True) mock_mongo_collection.count_documents.return_value = 0 # Start the patch diff --git a/apps/api/plane/tests/contract/api/test_labels.py b/apps/api/plane/tests/contract/api/test_labels.py index 7837823f9..a3a43d90a 100644 --- a/apps/api/plane/tests/contract/api/test_labels.py +++ b/apps/api/plane/tests/contract/api/test_labels.py @@ -103,9 +103,7 @@ class TestLabelListCreateAPIEndpoint: assert created_label.external_source == "github" @pytest.mark.django_db - def test_create_label_duplicate_external_id( - self, api_key_client, workspace, project - ): + def test_create_label_duplicate_external_id(self, api_key_client, workspace, project): """Test creating label with duplicate external ID""" url = self.get_label_url(workspace.slug, project.id) @@ -131,19 +129,13 @@ class TestLabelListCreateAPIEndpoint: assert "same external id" in response.data["error"] @pytest.mark.django_db - def test_list_labels_success( - self, api_key_client, workspace, project, create_label - ): + def test_list_labels_success(self, api_key_client, workspace, project, create_label): """Test successful label listing""" url = self.get_label_url(workspace.slug, project.id) # Create additional labels - Label.objects.create( - name="Label 2", project=project, workspace=workspace, color="#00FF00" - ) - Label.objects.create( - name="Label 3", project=project, workspace=workspace, color="#0000FF" - ) + Label.objects.create(name="Label 2", project=project, workspace=workspace, color="#00FF00") + Label.objects.create(name="Label 3", project=project, workspace=workspace, color="#0000FF") response = api_key_client.get(url) @@ -184,9 +176,7 @@ class TestLabelDetailAPIEndpoint: assert response.status_code == status.HTTP_404_NOT_FOUND @pytest.mark.django_db - def test_update_label_success( - self, api_key_client, workspace, project, create_label - ): + def test_update_label_success(self, api_key_client, workspace, project, create_label): """Test successful label update""" url = self.get_label_detail_url(workspace.slug, project.id, create_label.id) @@ -202,9 +192,7 @@ class TestLabelDetailAPIEndpoint: assert create_label.name == update_data["name"] @pytest.mark.django_db - def test_update_label_invalid_data( - self, api_key_client, workspace, project, create_label - ): + def test_update_label_invalid_data(self, api_key_client, workspace, project, create_label): """Test label update with invalid data""" url = self.get_label_detail_url(workspace.slug, project.id, create_label.id) @@ -215,9 +203,7 @@ class TestLabelDetailAPIEndpoint: assert response.status_code in [status.HTTP_400_BAD_REQUEST, status.HTTP_200_OK] @pytest.mark.django_db - def test_delete_label_success( - self, api_key_client, workspace, project, create_label - ): + def test_delete_label_success(self, api_key_client, workspace, project, create_label): """Test successful label deletion""" url = self.get_label_detail_url(workspace.slug, project.id, create_label.id) diff --git a/apps/api/plane/tests/contract/app/test_api_token.py b/apps/api/plane/tests/contract/app/test_api_token.py index 5160788de..35d92b11e 100644 --- a/apps/api/plane/tests/contract/app/test_api_token.py +++ b/apps/api/plane/tests/contract/app/test_api_token.py @@ -14,9 +14,7 @@ class TestApiTokenEndpoint: # POST /user/api-tokens/ tests @pytest.mark.django_db - def test_create_api_token_success( - self, session_client, create_user, api_token_data - ): + def test_create_api_token_success(self, session_client, create_user, api_token_data): """Test successful API token creation""" # Arrange session_client.force_authenticate(user=create_user) @@ -38,9 +36,7 @@ class TestApiTokenEndpoint: assert token.label == api_token_data["label"] @pytest.mark.django_db - def test_create_api_token_for_bot_user( - self, session_client, create_bot_user, api_token_data - ): + def test_create_api_token_for_bot_user(self, session_client, create_bot_user, api_token_data): """Test API token creation for bot user""" # Arrange session_client.force_authenticate(user=create_bot_user) @@ -111,9 +107,7 @@ class TestApiTokenEndpoint: APIToken.objects.create(label="Token 1", user=create_user, user_type=0) APIToken.objects.create(label="Token 2", user=create_user, user_type=0) # Create a service token (should be excluded) - APIToken.objects.create( - label="Service Token", user=create_user, user_type=0, is_service=True - ) + APIToken.objects.create(label="Service Token", user=create_user, user_type=0, is_service=True) url = reverse("api-tokens") # Act @@ -140,9 +134,7 @@ class TestApiTokenEndpoint: # GET /user/api-tokens// tests @pytest.mark.django_db - def test_get_specific_api_token( - self, session_client, create_user, create_api_token_for_user - ): + def test_get_specific_api_token(self, session_client, create_user, create_api_token_for_user): """Test retrieving a specific API token""" # Arrange session_client.force_authenticate(user=create_user) @@ -155,9 +147,7 @@ class TestApiTokenEndpoint: assert response.status_code == status.HTTP_200_OK assert str(response.data["id"]) == str(create_api_token_for_user.pk) assert response.data["label"] == create_api_token_for_user.label - assert ( - "token" not in response.data - ) # Token should not be visible in read serializer + assert "token" not in response.data # Token should not be visible in read serializer @pytest.mark.django_db def test_get_nonexistent_api_token(self, session_client, create_user): @@ -182,9 +172,7 @@ class TestApiTokenEndpoint: unique_email = f"other-{unique_id}@plane.so" unique_username = f"other_user_{unique_id}" other_user = User.objects.create(email=unique_email, username=unique_username) - other_token = APIToken.objects.create( - label="Other Token", user=other_user, user_type=0 - ) + other_token = APIToken.objects.create(label="Other Token", user=other_user, user_type=0) session_client.force_authenticate(user=create_user) url = reverse("api-tokens", kwargs={"pk": other_token.pk}) @@ -196,9 +184,7 @@ class TestApiTokenEndpoint: # DELETE /user/api-tokens// tests @pytest.mark.django_db - def test_delete_api_token_success( - self, session_client, create_user, create_api_token_for_user - ): + def test_delete_api_token_success(self, session_client, create_user, create_api_token_for_user): """Test successful API token deletion""" # Arrange session_client.force_authenticate(user=create_user) @@ -234,9 +220,7 @@ class TestApiTokenEndpoint: unique_email = f"delete-other-{unique_id}@plane.so" unique_username = f"delete_other_user_{unique_id}" other_user = User.objects.create(email=unique_email, username=unique_username) - other_token = APIToken.objects.create( - label="Other Token", user=other_user, user_type=0 - ) + other_token = APIToken.objects.create(label="Other Token", user=other_user, user_type=0) session_client.force_authenticate(user=create_user) url = reverse("api-tokens", kwargs={"pk": other_token.pk}) @@ -252,9 +236,7 @@ class TestApiTokenEndpoint: def test_delete_service_api_token_forbidden(self, session_client, create_user): """Test deleting a service API token (should fail)""" # Arrange - service_token = APIToken.objects.create( - label="Service Token", user=create_user, user_type=0, is_service=True - ) + service_token = APIToken.objects.create(label="Service Token", user=create_user, user_type=0, is_service=True) session_client.force_authenticate(user=create_user) url = reverse("api-tokens", kwargs={"pk": service_token.pk}) @@ -268,9 +250,7 @@ class TestApiTokenEndpoint: # PATCH /user/api-tokens// tests @pytest.mark.django_db - def test_patch_api_token_success( - self, session_client, create_user, create_api_token_for_user - ): + def test_patch_api_token_success(self, session_client, create_user, create_api_token_for_user): """Test successful API token update""" # Arrange session_client.force_authenticate(user=create_user) @@ -294,9 +274,7 @@ class TestApiTokenEndpoint: assert create_api_token_for_user.description == update_data["description"] @pytest.mark.django_db - def test_patch_api_token_partial_update( - self, session_client, create_user, create_api_token_for_user - ): + def test_patch_api_token_partial_update(self, session_client, create_user, create_api_token_for_user): """Test partial API token update""" # Arrange session_client.force_authenticate(user=create_user) @@ -336,9 +314,7 @@ class TestApiTokenEndpoint: unique_email = f"patch-other-{unique_id}@plane.so" unique_username = f"patch_other_user_{unique_id}" other_user = User.objects.create(email=unique_email, username=unique_username) - other_token = APIToken.objects.create( - label="Other Token", user=other_user, user_type=0 - ) + other_token = APIToken.objects.create(label="Other Token", user=other_user, user_type=0) session_client.force_authenticate(user=create_user) url = reverse("api-tokens", kwargs={"pk": other_token.pk}) update_data = {"label": "Hacked Label"} diff --git a/apps/api/plane/tests/contract/app/test_authentication.py b/apps/api/plane/tests/contract/app/test_authentication.py index b44f5f3fc..1c044f192 100644 --- a/apps/api/plane/tests/contract/app/test_authentication.py +++ b/apps/api/plane/tests/contract/app/test_authentication.py @@ -16,9 +16,7 @@ from plane.license.models import Instance @pytest.fixture def setup_instance(db): """Create and configure an instance for authentication tests""" - instance_id = ( - uuid.uuid4() if not Instance.objects.exists() else Instance.objects.first().id - ) + instance_id = uuid.uuid4() if not Instance.objects.exists() else Instance.objects.first().id # Create or update instance with all required fields instance, _ = Instance.objects.update_or_create( @@ -38,9 +36,7 @@ def setup_instance(db): @pytest.fixture def django_client(): """Return a Django test client with User-Agent header for handling redirects""" - client = Client( - HTTP_USER_AGENT="Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:15.0) Gecko/20100101 Firefox/15.0.1" - ) + client = Client(HTTP_USER_AGENT="Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:15.0) Gecko/20100101 Firefox/15.0.1") return client @@ -83,9 +79,7 @@ class TestMagicLinkGenerate: @pytest.mark.django_db @patch("plane.bgtasks.magic_link_code_task.magic_link.delay") - def test_magic_generate( - self, mock_magic_link, api_client, setup_user, setup_instance - ): + def test_magic_generate(self, mock_magic_link, api_client, setup_user, setup_instance): """Test successful magic link generation""" url = reverse("magic-generate") @@ -103,9 +97,7 @@ class TestMagicLinkGenerate: @pytest.mark.django_db @patch("plane.bgtasks.magic_link_code_task.magic_link.delay") - def test_max_generate_attempt( - self, mock_magic_link, api_client, setup_user, setup_instance - ): + def test_max_generate_attempt(self, mock_magic_link, api_client, setup_user, setup_instance): """Test exceeding maximum magic link generation attempts""" url = reverse("magic-generate") @@ -145,9 +137,7 @@ class TestSignInEndpoint: def test_email_validity(self, django_client, setup_user, setup_instance): """Test sign-in with invalid email format""" url = reverse("sign-in") - response = django_client.post( - url, {"email": "useremail.com", "password": "user@123"}, follow=True - ) + response = django_client.post(url, {"email": "useremail.com", "password": "user@123"}, follow=True) # Check redirect contains error code assert "INVALID_EMAIL_SIGN_IN" in response.redirect_chain[-1][0] @@ -156,9 +146,7 @@ class TestSignInEndpoint: def test_user_exists(self, django_client, setup_user, setup_instance): """Test sign-in with non-existent user""" url = reverse("sign-in") - response = django_client.post( - url, {"email": "user@email.so", "password": "user123"}, follow=True - ) + response = django_client.post(url, {"email": "user@email.so", "password": "user123"}, follow=True) # Check redirect contains error code assert "USER_DOES_NOT_EXIST" in response.redirect_chain[-1][0] @@ -167,9 +155,7 @@ class TestSignInEndpoint: def test_password_validity(self, django_client, setup_user, setup_instance): """Test sign-in with incorrect password""" url = reverse("sign-in") - response = django_client.post( - url, {"email": "user@plane.so", "password": "user123"}, follow=True - ) + response = django_client.post(url, {"email": "user@plane.so", "password": "user123"}, follow=True) # Check for the specific authentication error in the URL redirect_urls = [url for url, _ in response.redirect_chain] @@ -184,9 +170,7 @@ class TestSignInEndpoint: url = reverse("sign-in") # First make the request without following redirects - response = django_client.post( - url, {"email": "user@plane.so", "password": "user@123"}, follow=False - ) + response = django_client.post(url, {"email": "user@plane.so", "password": "user@123"}, follow=False) # Check that the initial response is a redirect (302) without error code assert response.status_code == 302 @@ -243,27 +227,20 @@ class TestMagicSignIn: assert "MAGIC_SIGN_IN_EMAIL_CODE_REQUIRED" in response.redirect_chain[-1][0] @pytest.mark.django_db - def test_expired_invalid_magic_link( - self, django_client, setup_user, setup_instance - ): + def test_expired_invalid_magic_link(self, django_client, setup_user, setup_instance): """Test magic link sign-in with expired/invalid link""" ri = redis_instance() ri.delete("magic_user@plane.so") url = reverse("magic-sign-in") - response = django_client.post( - url, {"email": "user@plane.so", "code": "xxxx-xxxxx-xxxx"}, follow=False - ) + response = django_client.post(url, {"email": "user@plane.so", "code": "xxxx-xxxxx-xxxx"}, follow=False) # Check that we get a redirect assert response.status_code == 302 # The actual error code is EXPIRED_MAGIC_CODE_SIGN_IN (when key doesn't exist) # or INVALID_MAGIC_CODE_SIGN_IN (when key exists but code doesn't match) - assert ( - "EXPIRED_MAGIC_CODE_SIGN_IN" in response.url - or "INVALID_MAGIC_CODE_SIGN_IN" in response.url - ) + assert "EXPIRED_MAGIC_CODE_SIGN_IN" in response.url or "INVALID_MAGIC_CODE_SIGN_IN" in response.url @pytest.mark.django_db def test_user_does_not_exist(self, django_client, setup_instance): @@ -280,9 +257,7 @@ class TestMagicSignIn: @pytest.mark.django_db @patch("plane.bgtasks.magic_link_code_task.magic_link.delay") - def test_magic_code_sign_in( - self, mock_magic_link, django_client, api_client, setup_user, setup_instance - ): + def test_magic_code_sign_in(self, mock_magic_link, django_client, api_client, setup_user, setup_instance): """Test successful magic link sign-in process""" # First generate a magic link token gen_url = reverse("magic-generate") @@ -298,9 +273,7 @@ class TestMagicSignIn: # Use Django client to test the redirect flow without following redirects url = reverse("magic-sign-in") - response = django_client.post( - url, {"email": "user@plane.so", "code": token}, follow=False - ) + response = django_client.post(url, {"email": "user@plane.so", "code": token}, follow=False) # Check that the initial response is a redirect without error code assert response.status_code == 302 @@ -311,9 +284,7 @@ class TestMagicSignIn: @pytest.mark.django_db @patch("plane.bgtasks.magic_link_code_task.magic_link.delay") - def test_magic_sign_in_with_next_path( - self, mock_magic_link, django_client, api_client, setup_user, setup_instance - ): + def test_magic_sign_in_with_next_path(self, mock_magic_link, django_client, api_client, setup_user, setup_instance): """Test magic sign-in with next_path parameter""" # First generate a magic link token gen_url = reverse("magic-generate") @@ -367,9 +338,7 @@ class TestMagicSignUp: User.objects.create(email="existing@plane.so") url = reverse("magic-sign-up") - response = django_client.post( - url, {"email": "existing@plane.so", "code": "xxxx-xxxxx-xxxx"}, follow=True - ) + response = django_client.post(url, {"email": "existing@plane.so", "code": "xxxx-xxxxx-xxxx"}, follow=True) # Check redirect contains error code assert "USER_ALREADY_EXIST" in response.redirect_chain[-1][0] @@ -378,25 +347,18 @@ class TestMagicSignUp: def test_expired_invalid_magic_link(self, django_client, setup_instance): """Test magic link sign-up with expired/invalid link""" url = reverse("magic-sign-up") - response = django_client.post( - url, {"email": "new@plane.so", "code": "xxxx-xxxxx-xxxx"}, follow=False - ) + response = django_client.post(url, {"email": "new@plane.so", "code": "xxxx-xxxxx-xxxx"}, follow=False) # Check that we get a redirect assert response.status_code == 302 # The actual error code is EXPIRED_MAGIC_CODE_SIGN_UP (when key doesn't exist) # or INVALID_MAGIC_CODE_SIGN_UP (when key exists but code doesn't match) - assert ( - "EXPIRED_MAGIC_CODE_SIGN_UP" in response.url - or "INVALID_MAGIC_CODE_SIGN_UP" in response.url - ) + assert "EXPIRED_MAGIC_CODE_SIGN_UP" in response.url or "INVALID_MAGIC_CODE_SIGN_UP" in response.url @pytest.mark.django_db @patch("plane.bgtasks.magic_link_code_task.magic_link.delay") - def test_magic_code_sign_up( - self, mock_magic_link, django_client, api_client, setup_instance - ): + def test_magic_code_sign_up(self, mock_magic_link, django_client, api_client, setup_instance): """Test successful magic link sign-up process""" email = "newuser@plane.so" @@ -414,9 +376,7 @@ class TestMagicSignUp: # Use Django client to test the redirect flow without following redirects url = reverse("magic-sign-up") - response = django_client.post( - url, {"email": email, "code": token}, follow=False - ) + response = django_client.post(url, {"email": email, "code": token}, follow=False) # Check that the initial response is a redirect without error code assert response.status_code == 302 @@ -430,9 +390,7 @@ class TestMagicSignUp: @pytest.mark.django_db @patch("plane.bgtasks.magic_link_code_task.magic_link.delay") - def test_magic_sign_up_with_next_path( - self, mock_magic_link, django_client, api_client, setup_instance - ): + def test_magic_sign_up_with_next_path(self, mock_magic_link, django_client, api_client, setup_instance): """Test magic sign-up with next_path parameter""" email = "newuser2@plane.so" @@ -451,9 +409,7 @@ class TestMagicSignUp: # Use Django client to test the redirect flow without following redirects url = reverse("magic-sign-up") next_path = "onboarding" - response = django_client.post( - url, {"email": email, "code": token, "next_path": next_path}, follow=False - ) + response = django_client.post(url, {"email": email, "code": token, "next_path": next_path}, follow=False) # Check that the initial response is a redirect without error code assert response.status_code == 302 diff --git a/apps/api/plane/tests/contract/app/test_project_app.py b/apps/api/plane/tests/contract/app/test_project_app.py index 78bcd7aea..38b0f51f3 100644 --- a/apps/api/plane/tests/contract/app/test_project_app.py +++ b/apps/api/plane/tests/contract/app/test_project_app.py @@ -14,9 +14,7 @@ from plane.db.models import ( class TestProjectBase: - def get_project_url( - self, workspace_slug: str, pk: uuid.UUID = None, details: bool = False - ) -> str: + def get_project_url(self, workspace_slug: str, pk: uuid.UUID = None, details: bool = False) -> str: """ Constructs the project endpoint URL for the given workspace as reverse() is unreliable due to duplicate 'name' values in URL patterns ('api' and 'app'). @@ -80,9 +78,7 @@ class TestProjectAPIPost(TestProjectBase): # Check if the member is created with the correct role assert ProjectMember.objects.count() == 1 - project_member = ProjectMember.objects.filter( - project=project, member=user - ).first() + project_member = ProjectMember.objects.filter(project=project, member=user).first() assert project_member.role == 20 # Administrator assert project_member.is_active is True @@ -97,19 +93,13 @@ class TestProjectAPIPost(TestProjectBase): assert set(state_names) == set(expected_states) @pytest.mark.django_db - def test_create_project_with_project_lead( - self, session_client, workspace, create_user - ): + def test_create_project_with_project_lead(self, session_client, workspace, create_user): """Test creating project with a different project lead""" # Create another user to be project lead - project_lead = User.objects.create_user( - email="lead@example.com", username="projectlead" - ) + project_lead = User.objects.create_user(email="lead@example.com", username="projectlead") # Add project lead to workspace - WorkspaceMember.objects.create( - workspace=workspace, member=project_lead, role=15 - ) + WorkspaceMember.objects.create(workspace=workspace, member=project_lead, role=15) url = self.get_project_url(workspace.slug) project_data = { @@ -132,9 +122,7 @@ class TestProjectAPIPost(TestProjectBase): @pytest.mark.django_db def test_create_project_guest_forbidden(self, session_client, workspace): """Test that guests cannot create projects""" - guest_user = User.objects.create_user( - email="guest@example.com", username="guest" - ) + guest_user = User.objects.create_user(email="guest@example.com", username="guest") WorkspaceMember.objects.create(workspace=workspace, member=guest_user, role=5) session_client.force_authenticate(user=guest_user) @@ -164,14 +152,10 @@ class TestProjectAPIPost(TestProjectBase): assert response.status_code == status.HTTP_401_UNAUTHORIZED @pytest.mark.django_db - def test_create_project_duplicate_name( - self, session_client, workspace, create_user - ): + def test_create_project_duplicate_name(self, session_client, workspace, create_user): """Test creating project with duplicate name""" # Create first project - Project.objects.create( - name="Duplicate Name", identifier="DN1", workspace=workspace - ) + Project.objects.create(name="Duplicate Name", identifier="DN1", workspace=workspace) url = self.get_project_url(workspace.slug) project_data = { @@ -184,13 +168,9 @@ class TestProjectAPIPost(TestProjectBase): assert response.status_code == status.HTTP_400_BAD_REQUEST @pytest.mark.django_db - def test_create_project_duplicate_identifier( - self, session_client, workspace, create_user - ): + def test_create_project_duplicate_identifier(self, session_client, workspace, create_user): """Test creating project with duplicate identifier""" - Project.objects.create( - name="First Project", identifier="DUP", workspace=workspace - ) + Project.objects.create(name="First Project", identifier="DUP", workspace=workspace) url = self.get_project_url(workspace.slug) project_data = { @@ -203,9 +183,7 @@ class TestProjectAPIPost(TestProjectBase): assert response.status_code == status.HTTP_400_BAD_REQUEST @pytest.mark.django_db - def test_create_project_missing_required_fields( - self, session_client, workspace, create_user - ): + def test_create_project_missing_required_fields(self, session_client, workspace, create_user): """Test validation with missing required fields""" url = self.get_project_url(workspace.slug) @@ -214,15 +192,11 @@ class TestProjectAPIPost(TestProjectBase): assert response.status_code == status.HTTP_400_BAD_REQUEST # Test missing identifier - response = session_client.post( - url, {"name": "Missing Identifier"}, format="json" - ) + response = session_client.post(url, {"name": "Missing Identifier"}, format="json") assert response.status_code == status.HTTP_400_BAD_REQUEST @pytest.mark.django_db - def test_create_project_with_all_optional_fields( - self, session_client, workspace, create_user - ): + def test_create_project_with_all_optional_fields(self, session_client, workspace, create_user): """Test creating project with all optional fields""" url = self.get_project_url(workspace.slug) project_data = { @@ -256,19 +230,13 @@ class TestProjectAPIGet(TestProjectBase): """Test project GET operations""" @pytest.mark.django_db - def test_list_projects_authenticated_admin( - self, session_client, workspace, create_user - ): + def test_list_projects_authenticated_admin(self, session_client, workspace, create_user): """Test listing projects as workspace admin""" # Create a project - project = Project.objects.create( - name="Test Project", identifier="TP", workspace=workspace - ) + project = Project.objects.create(name="Test Project", identifier="TP", workspace=workspace) # Add user as project member - ProjectMember.objects.create( - project=project, member=create_user, role=20, is_active=True - ) + ProjectMember.objects.create(project=project, member=create_user, role=20, is_active=True) url = self.get_project_url(workspace.slug) response = session_client.get(url) @@ -283,24 +251,16 @@ class TestProjectAPIGet(TestProjectBase): def test_list_projects_authenticated_guest(self, session_client, workspace): """Test listing projects as workspace guest""" # Create a guest user - guest_user = User.objects.create_user( - email="guest@example.com", username="guest" - ) - WorkspaceMember.objects.create( - workspace=workspace, member=guest_user, role=5, is_active=True - ) + guest_user = User.objects.create_user(email="guest@example.com", username="guest") + WorkspaceMember.objects.create(workspace=workspace, member=guest_user, role=5, is_active=True) # Create projects - project1 = Project.objects.create( - name="Project 1", identifier="P1", workspace=workspace - ) + project1 = Project.objects.create(name="Project 1", identifier="P1", workspace=workspace) Project.objects.create(name="Project 2", identifier="P2", workspace=workspace) # Add guest to only one project - ProjectMember.objects.create( - project=project1, member=guest_user, role=10, is_active=True - ) + ProjectMember.objects.create(project=project1, member=guest_user, role=10, is_active=True) session_client.force_authenticate(user=guest_user) @@ -333,9 +293,7 @@ class TestProjectAPIGet(TestProjectBase): ) # Add user as project member - ProjectMember.objects.create( - project=project, member=create_user, role=20, is_active=True - ) + ProjectMember.objects.create(project=project, member=create_user, role=20, is_active=True) url = self.get_project_url(workspace.slug, details=True) response = session_client.get(url) @@ -358,9 +316,7 @@ class TestProjectAPIGet(TestProjectBase): ) # Add user as project member - ProjectMember.objects.create( - project=project, member=create_user, role=20, is_active=True - ) + ProjectMember.objects.create(project=project, member=create_user, role=20, is_active=True) url = self.get_project_url(workspace.slug, pk=project.id) response = session_client.get(url) @@ -392,9 +348,7 @@ class TestProjectAPIGet(TestProjectBase): ) # Add user as project member - ProjectMember.objects.create( - project=project, member=create_user, role=20, is_active=True - ) + ProjectMember.objects.create(project=project, member=create_user, role=20, is_active=True) url = self.get_project_url(workspace.slug, pk=project.id) response = session_client.get(url) @@ -407,9 +361,7 @@ class TestProjectAPIPatchDelete(TestProjectBase): """Test project PATCH, and DELETE operations""" @pytest.mark.django_db - def test_partial_update_project_success( - self, session_client, workspace, create_user - ): + def test_partial_update_project_success(self, session_client, workspace, create_user): """Test successful partial update of project""" # Create a project project = Project.objects.create( @@ -420,9 +372,7 @@ class TestProjectAPIPatchDelete(TestProjectBase): ) # Add user as project administrator - ProjectMember.objects.create( - project=project, member=create_user, role=20, is_active=True - ) + ProjectMember.objects.create(project=project, member=create_user, role=20, is_active=True) url = self.get_project_url(workspace.slug, pk=project.id) update_data = { @@ -444,25 +394,15 @@ class TestProjectAPIPatchDelete(TestProjectBase): assert project.module_view is False @pytest.mark.django_db - def test_partial_update_project_forbidden_non_admin( - self, session_client, workspace - ): + def test_partial_update_project_forbidden_non_admin(self, session_client, workspace): """Test that non-admin project members cannot update project""" # Create a project - project = Project.objects.create( - name="Protected Project", identifier="PP", workspace=workspace - ) + project = Project.objects.create(name="Protected Project", identifier="PP", workspace=workspace) # Create a member user (not admin) - member_user = User.objects.create_user( - email="member@example.com", username="member" - ) - WorkspaceMember.objects.create( - workspace=workspace, member=member_user, role=15, is_active=True - ) - ProjectMember.objects.create( - project=project, member=member_user, role=15, is_active=True - ) + member_user = User.objects.create_user(email="member@example.com", username="member") + WorkspaceMember.objects.create(workspace=workspace, member=member_user, role=15, is_active=True) + ProjectMember.objects.create(project=project, member=member_user, role=15, is_active=True) session_client.force_authenticate(user=member_user) @@ -474,19 +414,13 @@ class TestProjectAPIPatchDelete(TestProjectBase): assert response.status_code == status.HTTP_403_FORBIDDEN @pytest.mark.django_db - def test_partial_update_duplicate_name_conflict( - self, session_client, workspace, create_user - ): + def test_partial_update_duplicate_name_conflict(self, session_client, workspace, create_user): """Test updating project with duplicate name returns conflict""" # Create two projects Project.objects.create(name="Project One", identifier="P1", workspace=workspace) - project2 = Project.objects.create( - name="Project Two", identifier="P2", workspace=workspace - ) + project2 = Project.objects.create(name="Project Two", identifier="P2", workspace=workspace) - ProjectMember.objects.create( - project=project2, member=create_user, role=20, is_active=True - ) + ProjectMember.objects.create(project=project2, member=create_user, role=20, is_active=True) url = self.get_project_url(workspace.slug, pk=project2.id) update_data = {"name": "Project One"} # Duplicate name @@ -496,19 +430,13 @@ class TestProjectAPIPatchDelete(TestProjectBase): assert response.status_code == status.HTTP_400_BAD_REQUEST @pytest.mark.django_db - def test_partial_update_duplicate_identifier_conflict( - self, session_client, workspace, create_user - ): + def test_partial_update_duplicate_identifier_conflict(self, session_client, workspace, create_user): """Test updating project with duplicate identifier returns conflict""" # Create two projects Project.objects.create(name="Project One", identifier="P1", workspace=workspace) - project2 = Project.objects.create( - name="Project Two", identifier="P2", workspace=workspace - ) + project2 = Project.objects.create(name="Project Two", identifier="P2", workspace=workspace) - ProjectMember.objects.create( - project=project2, member=create_user, role=20, is_active=True - ) + ProjectMember.objects.create(project=project2, member=create_user, role=20, is_active=True) url = self.get_project_url(workspace.slug, pk=project2.id) update_data = {"identifier": "P1"} # Duplicate identifier @@ -520,13 +448,9 @@ class TestProjectAPIPatchDelete(TestProjectBase): @pytest.mark.django_db def test_partial_update_invalid_data(self, session_client, workspace, create_user): """Test partial update with invalid data""" - project = Project.objects.create( - name="Valid Project", identifier="VP", workspace=workspace - ) + project = Project.objects.create(name="Valid Project", identifier="VP", workspace=workspace) - ProjectMember.objects.create( - project=project, member=create_user, role=20, is_active=True - ) + ProjectMember.objects.create(project=project, member=create_user, role=20, is_active=True) url = self.get_project_url(workspace.slug, pk=project.id) update_data = {"name": ""} @@ -536,17 +460,11 @@ class TestProjectAPIPatchDelete(TestProjectBase): assert response.status_code == status.HTTP_400_BAD_REQUEST @pytest.mark.django_db - def test_delete_project_success_project_admin( - self, session_client, workspace, create_user - ): + def test_delete_project_success_project_admin(self, session_client, workspace, create_user): """Test successful project deletion by project admin""" - project = Project.objects.create( - name="Delete Me", identifier="DM", workspace=workspace - ) + project = Project.objects.create(name="Delete Me", identifier="DM", workspace=workspace) - ProjectMember.objects.create( - project=project, member=create_user, role=20, is_active=True - ) + ProjectMember.objects.create(project=project, member=create_user, role=20, is_active=True) url = self.get_project_url(workspace.slug, pk=project.id) response = session_client.delete(url) @@ -558,16 +476,10 @@ class TestProjectAPIPatchDelete(TestProjectBase): def test_delete_project_success_workspace_admin(self, session_client, workspace): """Test successful project deletion by workspace admin""" # Create workspace admin user - workspace_admin = User.objects.create_user( - email="admin@example.com", username="admin" - ) - WorkspaceMember.objects.create( - workspace=workspace, member=workspace_admin, role=20, is_active=True - ) + workspace_admin = User.objects.create_user(email="admin@example.com", username="admin") + WorkspaceMember.objects.create(workspace=workspace, member=workspace_admin, role=20, is_active=True) - project = Project.objects.create( - name="Delete Me", identifier="DM", workspace=workspace - ) + project = Project.objects.create(name="Delete Me", identifier="DM", workspace=workspace) session_client.force_authenticate(user=workspace_admin) @@ -581,20 +493,12 @@ class TestProjectAPIPatchDelete(TestProjectBase): def test_delete_project_forbidden_non_admin(self, session_client, workspace): """Test that non-admin users cannot delete projects""" # Create a member user (not admin) - member_user = User.objects.create_user( - email="member@example.com", username="member" - ) - WorkspaceMember.objects.create( - workspace=workspace, member=member_user, role=15, is_active=True - ) + member_user = User.objects.create_user(email="member@example.com", username="member") + WorkspaceMember.objects.create(workspace=workspace, member=member_user, role=15, is_active=True) - project = Project.objects.create( - name="Protected Project", identifier="PP", workspace=workspace - ) + project = Project.objects.create(name="Protected Project", identifier="PP", workspace=workspace) - ProjectMember.objects.create( - project=project, member=member_user, role=15, is_active=True - ) + ProjectMember.objects.create(project=project, member=member_user, role=15, is_active=True) session_client.force_authenticate(user=member_user) @@ -607,9 +511,7 @@ class TestProjectAPIPatchDelete(TestProjectBase): @pytest.mark.django_db def test_delete_project_unauthenticated(self, client, workspace): """Test unauthenticated project deletion""" - project = Project.objects.create( - name="Protected Project", identifier="PP", workspace=workspace - ) + project = Project.objects.create(name="Protected Project", identifier="PP", workspace=workspace) url = self.get_project_url(workspace.slug, pk=project.id) response = client.delete(url) diff --git a/apps/api/plane/tests/contract/app/test_workspace_app.py b/apps/api/plane/tests/contract/app/test_workspace_app.py index 9d4c560e5..47b049795 100644 --- a/apps/api/plane/tests/contract/app/test_workspace_app.py +++ b/apps/api/plane/tests/contract/app/test_workspace_app.py @@ -21,9 +21,7 @@ class TestWorkspaceAPI: @pytest.mark.django_db @patch("plane.bgtasks.workspace_seed_task.workspace_seed.delay") - def test_create_workspace_valid_data( - self, mock_workspace_seed, session_client, create_user - ): + def test_create_workspace_valid_data(self, mock_workspace_seed, session_client, create_user): """Test creating a workspace with valid data""" url = reverse("workspace") user = create_user # Use the create_user fixture directly as it returns a user object @@ -49,9 +47,7 @@ class TestWorkspaceAPI: # Check other values workspace = Workspace.objects.get(slug=workspace_data["slug"]) - workspace_member = WorkspaceMember.objects.filter( - workspace=workspace, member=user - ).first() + workspace_member = WorkspaceMember.objects.filter(workspace=workspace, member=user).first() assert workspace.owner == user assert workspace_member.role == 20 @@ -68,9 +64,7 @@ class TestWorkspaceAPI: session_client.post(url, {"name": "Plane", "slug": "pla-ne"}, format="json") # Try to create a workspace with the same slug - response = session_client.post( - url, {"name": "Plane", "slug": "pla-ne"}, format="json" - ) + response = session_client.post(url, {"name": "Plane", "slug": "pla-ne"}, format="json") # The API returns 400 BAD REQUEST for duplicate slugs, not 409 CONFLICT assert response.status_code == status.HTTP_400_BAD_REQUEST diff --git a/apps/api/plane/tests/smoke/test_auth_smoke.py b/apps/api/plane/tests/smoke/test_auth_smoke.py index 85ca476b4..c5a671e9a 100644 --- a/apps/api/plane/tests/smoke/test_auth_smoke.py +++ b/apps/api/plane/tests/smoke/test_auth_smoke.py @@ -15,15 +15,11 @@ class TestAuthSmoke: url = f"{plane_server.url}{relative_url}" # 1. Test bad login - test with wrong password - response = requests.post( - url, data={"email": user_data["email"], "password": "wrong-password"} - ) + response = requests.post(url, data={"email": user_data["email"], "password": "wrong-password"}) # For bad credentials, any of these status codes would be valid # The test shouldn't be brittle to minor implementation changes - assert response.status_code != 500, ( - "Authentication should not cause server errors" - ) + assert response.status_code != 500, "Authentication should not cause server errors" assert response.status_code != 404, "Authentication endpoint should exist" if response.status_code == 200: @@ -33,10 +29,7 @@ class TestAuthSmoke: data = response.json() # JSON response might indicate error in its structure assert ( - "error" in data - or "error_code" in data - or "detail" in data - or response.url.endswith("sign-in") + "error" in data or "error_code" in data or "detail" in data or response.url.endswith("sign-in") ), "Error response should contain error details" except ValueError: # It's ok if response isn't JSON format @@ -75,20 +68,17 @@ class TestAuthSmoke: data = response.json() # If it's a token response if "access_token" in data: - assert "refresh_token" in data, ( - "JWT auth should return both access and refresh tokens" - ) + assert "refresh_token" in data, "JWT auth should return both access and refresh tokens" # If it's a user session response elif "user" in data: - assert ( - "is_authenticated" in data and data["is_authenticated"] - ), "User session response should indicate authentication" + assert "is_authenticated" in data and data["is_authenticated"], ( + "User session response should indicate authentication" + ) # Otherwise it should at least indicate success else: - assert not any( - error_key in data - for error_key in ["error", "error_code", "detail"] - ), "Success response should not contain error keys" + assert not any(error_key in data for error_key in ["error", "error_code", "detail"]), ( + "Success response should not contain error keys" + ) except ValueError: # Non-JSON is acceptable if it's a redirect or HTML response pass diff --git a/apps/api/plane/tests/unit/bg_tasks/test_copy_s3_objects.py b/apps/api/plane/tests/unit/bg_tasks/test_copy_s3_objects.py index a7c178b0e..988603659 100644 --- a/apps/api/plane/tests/unit/bg_tasks/test_copy_s3_objects.py +++ b/apps/api/plane/tests/unit/bg_tasks/test_copy_s3_objects.py @@ -14,9 +14,7 @@ class TestCopyS3Objects: @pytest.fixture def project(self, create_user, workspace): - project = Project.objects.create( - name="Test Project", identifier="test-project", workspace=workspace - ) + project = Project.objects.create(name="Test Project", identifier="test-project", workspace=workspace) ProjectMember.objects.create(project=project, member=create_user) return project @@ -27,7 +25,7 @@ class TestCopyS3Objects: name="Test Issue", workspace=workspace, project_id=project.id, - description_html='
', + description_html='
', # noqa: E501 ) @pytest.fixture @@ -72,18 +70,14 @@ class TestCopyS3Objects: mock_s3_storage.return_value = mock_storage_instance # Mock the external service call to avoid actual HTTP requests - with patch( - "plane.bgtasks.copy_s3_object.sync_with_external_service" - ) as mock_sync: + with patch("plane.bgtasks.copy_s3_object.sync_with_external_service") as mock_sync: mock_sync.return_value = { "description": "test description", "description_binary": base64.b64encode(b"test binary").decode(), } # Call the actual function (not .delay()) - copy_s3_objects_of_description_and_assets( - "ISSUE", issue.id, project.id, "test-workspace", create_user.id - ) + copy_s3_objects_of_description_and_assets("ISSUE", issue.id, project.id, "test-workspace", create_user.id) # Assert that copy_object was called for each asset assert mock_storage_instance.copy_object.call_count == 2 @@ -100,9 +94,7 @@ class TestCopyS3Objects: @pytest.mark.django_db @patch("plane.bgtasks.copy_s3_object.S3Storage") - def test_copy_assets_successful( - self, mock_s3_storage, workspace, project, issue, file_asset - ): + def test_copy_assets_successful(self, mock_s3_storage, workspace, project, issue, file_asset): """Test successful copying of assets""" # Arrange mock_storage_instance = MagicMock() @@ -136,9 +128,7 @@ class TestCopyS3Objects: @pytest.mark.django_db @patch("plane.bgtasks.copy_s3_object.S3Storage") - def test_copy_assets_empty_asset_ids( - self, mock_s3_storage, workspace, project, issue - ): + def test_copy_assets_empty_asset_ids(self, mock_s3_storage, workspace, project, issue): """Test copying with empty asset_ids list""" # Arrange mock_storage_instance = MagicMock() @@ -159,9 +149,7 @@ class TestCopyS3Objects: @pytest.mark.django_db @patch("plane.bgtasks.copy_s3_object.S3Storage") - def test_copy_assets_nonexistent_asset( - self, mock_s3_storage, workspace, project, issue - ): + def test_copy_assets_nonexistent_asset(self, mock_s3_storage, workspace, project, issue): """Test copying with non-existent asset ID""" # Arrange mock_storage_instance = MagicMock() diff --git a/apps/api/plane/tests/unit/middleware/test_db_routing.py b/apps/api/plane/tests/unit/middleware/test_db_routing.py index 73f222140..5ac71696a 100644 --- a/apps/api/plane/tests/unit/middleware/test_db_routing.py +++ b/apps/api/plane/tests/unit/middleware/test_db_routing.py @@ -111,9 +111,7 @@ class TestReadReplicaRoutingMiddleware: mock_clear.assert_called_once() @patch("plane.middleware.db_routing.clear_read_replica_context") - def test_call_cleans_up_context_on_exception( - self, mock_clear, middleware, get_request, mock_get_response - ): + def test_call_cleans_up_context_on_exception(self, mock_clear, middleware, get_request, mock_get_response): """Test __call__ cleans up context even if get_response raises.""" mock_get_response.side_effect = Exception("Test exception") @@ -139,9 +137,7 @@ class TestProcessView: assert result is None @patch("plane.middleware.db_routing.set_use_read_replica") - def test_with_read_method_and_replica_false( - self, mock_set, middleware, get_request - ): + def test_with_read_method_and_replica_false(self, mock_set, middleware, get_request): """Test process_view with GET request and use_read_replica=False.""" view_func = Mock() view_func.use_read_replica = False @@ -152,9 +148,7 @@ class TestProcessView: assert result is None @patch("plane.middleware.db_routing.set_use_read_replica") - def test_with_read_method_and_no_replica_attribute( - self, mock_set, middleware, get_request - ): + def test_with_read_method_and_no_replica_attribute(self, mock_set, middleware, get_request): """Test process_view with GET request and no use_read_replica attr.""" view_func = Mock(spec=[]) # No use_read_replica attribute @@ -287,9 +281,7 @@ class TestAttributeDetection: (None, False), ], ) - def test_should_use_read_replica_truthy_falsy_values( - self, middleware, value, expected - ): + def test_should_use_read_replica_truthy_falsy_values(self, middleware, value, expected): """Test _should_use_read_replica with various truthy/falsy values.""" # Create a real object to test the attribute handling @@ -309,9 +301,7 @@ class TestExceptionHandling: """Test cases for exception handling and cleanup.""" @patch("plane.middleware.db_routing.clear_read_replica_context") - def test_process_exception_cleans_up_context( - self, mock_clear, middleware, request_factory - ): + def test_process_exception_cleans_up_context(self, mock_clear, middleware, request_factory): """Test process_exception cleans up context.""" request = request_factory.get("/api/test/") exception = Exception("Test exception") @@ -323,9 +313,7 @@ class TestExceptionHandling: @patch("plane.middleware.db_routing.set_use_read_replica") @patch("plane.middleware.db_routing.clear_read_replica_context") - def test_integration_full_request_cycle( - self, mock_clear, mock_set, middleware, request_factory, mock_get_response - ): + def test_integration_full_request_cycle(self, mock_clear, mock_set, middleware, request_factory, mock_get_response): """Test complete request cycle from __call__ through process_view.""" request = request_factory.get("/api/test/") view_func = Mock() @@ -410,9 +398,7 @@ class TestEdgeCases: def __getattr__(self, name): if name == "use_read_replica": raise AttributeError("Simulated attribute error") - raise AttributeError( - f"'{type(self).__name__}' object has no attribute '{name}'" - ) + raise AttributeError(f"'{type(self).__name__}' object has no attribute '{name}'") view_func = ProblematicView() diff --git a/apps/api/plane/tests/unit/serializers/test_issue_recent_visit.py b/apps/api/plane/tests/unit/serializers/test_issue_recent_visit.py index 72d1f3384..eac92384b 100644 --- a/apps/api/plane/tests/unit/serializers/test_issue_recent_visit.py +++ b/apps/api/plane/tests/unit/serializers/test_issue_recent_visit.py @@ -20,9 +20,7 @@ class TestIssueRecentVisitSerializer: def test_issue_recent_visit_serializer_fields(self, db): """Test that the serializer includes the correct fields""" - test_user_1 = User.objects.create( - email="test_user_1@example.com", first_name="Test", last_name="User" - ) + test_user_1 = User.objects.create(email="test_user_1@example.com", first_name="Test", last_name="User") # To test for deleted issue assignee test_user_2 = User.objects.create( @@ -32,15 +30,11 @@ class TestIssueRecentVisitSerializer: username="some user name", ) - workspace = Workspace.objects.create( - name="Test Workspace", slug="test-workspace", owner=test_user_1 - ) + workspace = Workspace.objects.create(name="Test Workspace", slug="test-workspace", owner=test_user_1) WorkspaceMember.objects.create(member=test_user_2, role=15, workspace=workspace) - project = Project.objects.create( - name="Test Project", identifier="test-project", workspace=workspace - ) + project = Project.objects.create(name="Test Project", identifier="test-project", workspace=workspace) ProjectMember.objects.create(project=project, member=test_user_2) issue = Issue.objects.create( diff --git a/apps/api/plane/tests/unit/serializers/test_workspace.py b/apps/api/plane/tests/unit/serializers/test_workspace.py index 28e6c8d75..21844c714 100644 --- a/apps/api/plane/tests/unit/serializers/test_workspace.py +++ b/apps/api/plane/tests/unit/serializers/test_workspace.py @@ -12,15 +12,11 @@ class TestWorkspaceLiteSerializer: def test_workspace_lite_serializer_fields(self, db): """Test that the serializer includes the correct fields""" # Create a user to be the owner - owner = User.objects.create( - email="test@example.com", first_name="Test", last_name="User" - ) + owner = User.objects.create(email="test@example.com", first_name="Test", last_name="User") # Create a workspace with explicit ID to test serialization workspace_id = uuid4() - workspace = Workspace.objects.create( - name="Test Workspace", slug="test-workspace", id=workspace_id, owner=owner - ) + workspace = Workspace.objects.create(name="Test Workspace", slug="test-workspace", id=workspace_id, owner=owner) # Serialize the workspace serialized_data = WorkspaceLiteSerializer(workspace).data @@ -37,19 +33,13 @@ class TestWorkspaceLiteSerializer: def test_workspace_lite_serializer_read_only(self, db): """Test that the serializer fields are read-only""" # Create a user to be the owner - owner = User.objects.create( - email="test2@example.com", first_name="Test", last_name="User" - ) + owner = User.objects.create(email="test2@example.com", first_name="Test", last_name="User") # Create a workspace - workspace = Workspace.objects.create( - name="Test Workspace", slug="test-workspace", id=uuid4(), owner=owner - ) + workspace = Workspace.objects.create(name="Test Workspace", slug="test-workspace", id=uuid4(), owner=owner) # Try to update via serializer - serializer = WorkspaceLiteSerializer( - workspace, data={"name": "Updated Name", "slug": "updated-slug"} - ) + serializer = WorkspaceLiteSerializer(workspace, data={"name": "Updated Name", "slug": "updated-slug"}) # Serializer should be valid (since read-only fields are ignored) assert serializer.is_valid() diff --git a/apps/api/plane/tests/unit/utils/test_url.py b/apps/api/plane/tests/unit/utils/test_url.py index 440211eb0..465cb3023 100644 --- a/apps/api/plane/tests/unit/utils/test_url.py +++ b/apps/api/plane/tests/unit/utils/test_url.py @@ -61,9 +61,7 @@ class TestContainsURL: assert contains_url("example.c") is False # TLD too short assert contains_url("999.999.999.999") is False # Invalid IP (octets > 255) assert contains_url("just-a-hyphen") is False # No domain - assert ( - contains_url("www.") is False - ) # Incomplete www - needs at least one char after dot + assert contains_url("www.") is False # Incomplete www - needs at least one char after dot def test_contains_url_length_limit_under_1000(self): """Test contains_url with input under 1000 characters containing URLs""" @@ -108,9 +106,7 @@ class TestContainsURL: assert contains_url(multiline_short) is True # Multiple lines under total limit - multiline_text = ( - "a" * 200 + "\n" + "b" * 200 + "https://example.com\n" + "c" * 200 - ) + multiline_text = "a" * 200 + "\n" + "b" * 200 + "https://example.com\n" + "c" * 200 assert len(multiline_text) < 1000 assert contains_url(multiline_text) is True @@ -207,9 +203,7 @@ class TestNormalizeURLPath: def test_normalize_url_path_with_query_and_fragment(self): """Test normalize_url_path preserves query and fragment""" - result = normalize_url_path( - "https://example.com//foo///bar//baz?x=1&y=2#fragment" - ) + result = normalize_url_path("https://example.com//foo///bar//baz?x=1&y=2#fragment") assert result == "https://example.com/foo/bar/baz?x=1&y=2#fragment" def test_normalize_url_path_with_no_redundant_slashes(self): @@ -230,9 +224,7 @@ class TestNormalizeURLPath: def test_normalize_url_path_with_complex_path(self): """Test normalize_url_path with complex path structure""" - result = normalize_url_path( - "https://example.com///api//v1///users//123//profile" - ) + result = normalize_url_path("https://example.com///api//v1///users//123//profile") assert result == "https://example.com/api/v1/users/123/profile" def test_normalize_url_path_with_different_schemes(self): diff --git a/apps/api/plane/tests/unit/utils/test_uuid.py b/apps/api/plane/tests/unit/utils/test_uuid.py index 5503f2bc3..d47e59c4b 100644 --- a/apps/api/plane/tests/unit/utils/test_uuid.py +++ b/apps/api/plane/tests/unit/utils/test_uuid.py @@ -19,9 +19,7 @@ class TestUUIDUtils: assert is_valid_uuid("not-a-uuid") is False assert is_valid_uuid("123456789") is False assert is_valid_uuid("") is False - assert ( - is_valid_uuid("00000000-0000-0000-0000-000000000000") is False - ) # This is a valid UUID but version 1 + assert is_valid_uuid("00000000-0000-0000-0000-000000000000") is False # This is a valid UUID but version 1 def test_convert_uuid_to_integer(self): """Test convert_uuid_to_integer function""" @@ -48,6 +46,4 @@ class TestUUIDUtils: test_uuid = uuid.UUID(test_uuid_str) # Should get the same result whether passing UUID or string - assert convert_uuid_to_integer(test_uuid) == convert_uuid_to_integer( - test_uuid_str - ) + assert convert_uuid_to_integer(test_uuid) == convert_uuid_to_integer(test_uuid_str) diff --git a/apps/api/plane/urls.py b/apps/api/plane/urls.py index c06e67158..4b1062559 100644 --- a/apps/api/plane/urls.py +++ b/apps/api/plane/urls.py @@ -38,8 +38,6 @@ if settings.DEBUG: try: import debug_toolbar - urlpatterns = [ - re_path(r"^__debug__/", include(debug_toolbar.urls)) - ] + urlpatterns + urlpatterns = [re_path(r"^__debug__/", include(debug_toolbar.urls))] + urlpatterns except ImportError: pass diff --git a/apps/api/plane/utils/analytics_plot.py b/apps/api/plane/utils/analytics_plot.py index 43c465e7c..12fa39cc0 100644 --- a/apps/api/plane/utils/analytics_plot.py +++ b/apps/api/plane/utils/analytics_plot.py @@ -73,30 +73,19 @@ def build_graph_plot(queryset, x_axis, y_axis, segment=None): dimension_ex=Coalesce("dimension", Value("null")), ).values("dimension") queryset = queryset.annotate(segment=F(segment)) if segment else queryset - queryset = ( - queryset.values("dimension", "segment") - if segment - else queryset.values("dimension") - ) + queryset = queryset.values("dimension", "segment") if segment else queryset.values("dimension") queryset = queryset.annotate(count=Count("*")).order_by("dimension") # Estimate else: - queryset = queryset.annotate( - estimate=Sum(Cast("estimate_point__value", FloatField())) - ).order_by(x_axis) + queryset = queryset.annotate(estimate=Sum(Cast("estimate_point__value", FloatField()))).order_by(x_axis) queryset = queryset.annotate(segment=F(segment)) if segment else queryset queryset = ( - queryset.values("dimension", "segment", "estimate") - if segment - else queryset.values("dimension", "estimate") + queryset.values("dimension", "segment", "estimate") if segment else queryset.values("dimension", "estimate") ) result_values = list(queryset) - grouped_data = { - str(key): list(items) - for key, items in groupby(result_values, key=lambda x: x[str("dimension")]) - } + grouped_data = {str(key): list(items) for key, items in groupby(result_values, key=lambda x: x[str("dimension")])} return sort_data(grouped_data, temp_axis) @@ -140,9 +129,7 @@ def burndown_plot(queryset, slug, project_id, plot_type, cycle_id=None, module_i # Get all dates between the two dates date_range = [ (queryset.start_date + timedelta(days=x)).date() - for x in range( - (queryset.end_date.date() - queryset.start_date.date()).days + 1 - ) + for x in range((queryset.end_date.date() - queryset.start_date.date()).days + 1) ] else: date_range = [] diff --git a/apps/api/plane/utils/build_chart.py b/apps/api/plane/utils/build_chart.py index be5bb7753..9a2d9c3a0 100644 --- a/apps/api/plane/utils/build_chart.py +++ b/apps/api/plane/utils/build_chart.py @@ -82,11 +82,7 @@ def process_grouped_data( if key not in response: response[key] = { "key": key if key else "none", - "name": ( - item.get("display_name", key) - if item.get("display_name", key) - else "None" - ), + "name": (item.get("display_name", key) if item.get("display_name", key) else "None"), "count": 0, } group_key = str(item["group_key"]) if item["group_key"] else "none" @@ -104,9 +100,7 @@ def build_number_chart_response( y_axis: str, aggregate_func: Aggregate, ) -> List[Dict[str, Any]]: - count = ( - queryset.filter(**y_axis_filter).aggregate(total=aggregate_func).get("total", 0) - ) + count = queryset.filter(**y_axis_filter).aggregate(total=aggregate_func).get("total", 0) return [{"key": y_axis, "name": y_axis, "count": count}] @@ -136,9 +130,7 @@ def build_simple_chart_response( queryset: QuerySet, id_field: str, name_field: str, aggregate_func: Aggregate ) -> List[Dict[str, Any]]: data = ( - queryset.annotate( - key=F(id_field), display_name=F(name_field) if name_field else F(id_field) - ) + queryset.annotate(key=F(id_field), display_name=F(name_field) if name_field else F(id_field)) .values("key", "display_name") .annotate(count=aggregate_func) .order_by("key") @@ -170,12 +162,8 @@ def build_analytics_chart( field_mapping = get_x_axis_field() - id_field, name_field, additional_filter = field_mapping.get( - x_axis, (None, None, {}) - ) - group_field, group_name_field, group_additional_filter = field_mapping.get( - group_by, (None, None, {}) - ) + id_field, name_field, additional_filter = field_mapping.get(x_axis, (None, None, {})) + group_field, group_name_field, group_additional_filter = field_mapping.get(group_by, (None, None, {})) # Apply additional filters if they exist if additional_filter or {}: @@ -196,9 +184,7 @@ def build_analytics_chart( aggregate_func, ) else: - response = build_simple_chart_response( - queryset, id_field, name_field, aggregate_func - ) + response = build_simple_chart_response(queryset, id_field, name_field, aggregate_func) schema = {} return {"data": response, "schema": schema} diff --git a/apps/api/plane/utils/cache.py b/apps/api/plane/utils/cache.py index 1b3e2cb1c..da3fd4517 100644 --- a/apps/api/plane/utils/cache.py +++ b/apps/api/plane/utils/cache.py @@ -25,13 +25,7 @@ def cache_response(timeout=60 * 60, path=None, user=True): @wraps(view_func) def _wrapped_view(instance, request, *args, **kwargs): # Function to generate cache key - auth_header = ( - None - if request.user.is_anonymous - else str(request.user.id) - if user - else None - ) + auth_header = None if request.user.is_anonymous else str(request.user.id) if user else None custom_path = path if path is not None else request.get_full_path() key = generate_cache_key(custom_path, auth_header) cached_result = cache.get(key) @@ -53,9 +47,7 @@ def cache_response(timeout=60 * 60, path=None, user=True): return decorator -def invalidate_cache_directly( - path=None, url_params=False, user=True, request=None, multiple=False -): +def invalidate_cache_directly(path=None, url_params=False, user=True, request=None, multiple=False): if url_params and path: path_with_values = path # Assuming `kwargs` could be passed directly if needed, otherwise, skip this part @@ -64,13 +56,7 @@ def invalidate_cache_directly( custom_path = path_with_values else: custom_path = path if path is not None else request.get_full_path() - auth_header = ( - None - if request and request.user.is_anonymous - else str(request.user.id) - if user - else None - ) + auth_header = None if request and request.user.is_anonymous else str(request.user.id) if user else None key = generate_cache_key(custom_path, auth_header) if multiple: diff --git a/apps/api/plane/utils/content_validator.py b/apps/api/plane/utils/content_validator.py index cf7c235ee..c352205aa 100644 --- a/apps/api/plane/utils/content_validator.py +++ b/apps/api/plane/utils/content_validator.py @@ -54,9 +54,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/core/dbrouters.py b/apps/api/plane/utils/core/dbrouters.py index 2c5b67a27..e17568331 100644 --- a/apps/api/plane/utils/core/dbrouters.py +++ b/apps/api/plane/utils/core/dbrouters.py @@ -53,9 +53,7 @@ class ReadReplicaRouter: logger.debug(f"Routing write for {model._meta.label} to primary database") return "default" - def allow_migrate( - self, db: str, app_label: str, model_name: str = None, **hints - ) -> bool: + def allow_migrate(self, db: str, app_label: str, model_name: str = None, **hints) -> bool: """ Ensure migrations only run on the primary database. Args: diff --git a/apps/api/plane/utils/date_utils.py b/apps/api/plane/utils/date_utils.py index 4225e70b5..f15e7f119 100644 --- a/apps/api/plane/utils/date_utils.py +++ b/apps/api/plane/utils/date_utils.py @@ -42,44 +42,30 @@ def get_analytics_date_range( "lte": datetime.combine(today, datetime.max.time()), }, "previous": { - "gte": datetime.combine( - today - timedelta(days=14), datetime.min.time() - ), + "gte": datetime.combine(today - timedelta(days=14), datetime.min.time()), "lte": datetime.combine(today - timedelta(days=8), datetime.max.time()), }, } elif date_filter == "last_30_days": return { "current": { - "gte": datetime.combine( - today - timedelta(days=30), datetime.min.time() - ), + "gte": datetime.combine(today - timedelta(days=30), datetime.min.time()), "lte": datetime.combine(today, datetime.max.time()), }, "previous": { - "gte": datetime.combine( - today - timedelta(days=60), datetime.min.time() - ), - "lte": datetime.combine( - today - timedelta(days=31), datetime.max.time() - ), + "gte": datetime.combine(today - timedelta(days=60), datetime.min.time()), + "lte": datetime.combine(today - timedelta(days=31), datetime.max.time()), }, } elif date_filter == "last_3_months": return { "current": { - "gte": datetime.combine( - today - timedelta(days=90), datetime.min.time() - ), + "gte": datetime.combine(today - timedelta(days=90), datetime.min.time()), "lte": datetime.combine(today, datetime.max.time()), }, "previous": { - "gte": datetime.combine( - today - timedelta(days=180), datetime.min.time() - ), - "lte": datetime.combine( - today - timedelta(days=91), datetime.max.time() - ), + "gte": datetime.combine(today - timedelta(days=180), datetime.min.time()), + "lte": datetime.combine(today - timedelta(days=91), datetime.max.time()), }, } elif date_filter == "custom" and start_date and end_date: diff --git a/apps/api/plane/utils/filters/converters.py b/apps/api/plane/utils/filters/converters.py index 13a5221b8..f7693b40e 100644 --- a/apps/api/plane/utils/filters/converters.py +++ b/apps/api/plane/utils/filters/converters.py @@ -187,9 +187,7 @@ class LegacyToRichFiltersConverter: return self._validate_date(value) return True # No specific validation needed - def _filter_valid_values( - self, rich_field_name: str, values: List[Any] - ) -> List[Any]: + def _filter_valid_values(self, rich_field_name: str, values: List[Any]) -> List[Any]: """Filter out invalid values from a list and return only valid ones""" valid_values = [] for value in values: @@ -197,25 +195,19 @@ class LegacyToRichFiltersConverter: valid_values.append(value) return valid_values - def _add_validation_error( - self, strict: bool, validation_errors: List[str], message: str - ) -> None: + def _add_validation_error(self, strict: bool, validation_errors: List[str], message: str) -> None: """Add validation error if in strict mode.""" if strict: validation_errors.append(message) - def _add_rich_filter( - self, rich_filters: Dict[str, Any], field_name: str, operator: str, value: Any - ) -> None: + def _add_rich_filter(self, rich_filters: Dict[str, Any], field_name: str, operator: str, value: Any) -> None: """Add a rich filter with proper field name formatting.""" # Convert lists to comma-separated strings for 'in' and 'range' operations if operator in ("in", "range") and isinstance(value, list): value = ",".join(str(v) for v in value) rich_filters[f"{field_name}__{operator}"] = value - def _handle_value_error( - self, e: ValueError, strict: bool, validation_errors: List[str] - ) -> None: + def _handle_value_error(self, e: ValueError, strict: bool, validation_errors: List[str]) -> None: """Handle ValueError with consistent strict/non-strict behavior.""" if strict: validation_errors.append(str(e)) @@ -234,9 +226,7 @@ class LegacyToRichFiltersConverter: return False try: - date_filter_result = self._convert_date_value( - rich_field_name, values, strict - ) + date_filter_result = self._convert_date_value(rich_field_name, values, strict) if date_filter_result: rich_filters.update(date_filter_result) return True @@ -244,9 +234,7 @@ class LegacyToRichFiltersConverter: self._handle_value_error(e, strict, validation_errors) return True - def _convert_date_value( - self, field_name: str, values: List[str], strict: bool = False - ) -> Dict[str, Any]: + def _convert_date_value(self, field_name: str, values: List[str], strict: bool = False) -> Dict[str, Any]: """ Convert legacy date values to rich filter format - basic implementation. @@ -349,9 +337,7 @@ class LegacyToRichFiltersConverter: # Skip if legacy key is not in our mappings (not supported in filterset) if legacy_key not in self.FIELD_MAPPINGS: - self._add_validation_error( - strict, validation_errors, f"Unsupported filter key: {legacy_key}" - ) + self._add_validation_error(strict, validation_errors, f"Unsupported filter key: {legacy_key}") continue # Get the new field name @@ -360,9 +346,7 @@ class LegacyToRichFiltersConverter: # Handle list values if isinstance(value, list): # Process date fields with helper method - if self._process_date_field( - rich_field_name, value, strict, validation_errors, rich_filters - ): + if self._process_date_field(rich_field_name, value, strict, validation_errors, rich_filters): continue # Regular non-date field processing @@ -392,9 +376,7 @@ class LegacyToRichFiltersConverter: else: # Handle single values # Process date fields with helper method - if self._process_date_field( - rich_field_name, [value], strict, validation_errors, rich_filters - ): + if self._process_date_field(rich_field_name, [value], strict, validation_errors, rich_filters): continue # For non-list values, use __exact operator for non-date fields diff --git a/apps/api/plane/utils/filters/filter_backend.py b/apps/api/plane/utils/filters/filter_backend.py index d9d8429e9..e2faca1a6 100644 --- a/apps/api/plane/utils/filters/filter_backend.py +++ b/apps/api/plane/utils/filters/filter_backend.py @@ -55,9 +55,7 @@ class ComplexFilterBackend(filters.BaseFilterBackend): return raw_filter raise ValidationError(f"'{source_label}' must be a dict or a JSON string.") except json.JSONDecodeError: - raise ValidationError( - f"Invalid JSON for '{source_label}'. Expected a valid JSON object." - ) + raise ValidationError(f"Invalid JSON for '{source_label}'. Expected a valid JSON object.") def _apply_json_filter(self, queryset, filter_data, view): """Process a JSON filter structure using OR/AND/NOT set operations.""" @@ -80,14 +78,10 @@ class ComplexFilterBackend(filters.BaseFilterBackend): def _validate_fields(self, filter_data, view): """Validate that filtered fields are defined in the view's FilterSet.""" filterset_class = getattr(view, "filterset_class", None) - allowed_fields = ( - set(filterset_class.base_filters.keys()) if filterset_class else None - ) + allowed_fields = set(filterset_class.base_filters.keys()) if filterset_class else None if not allowed_fields: # If no FilterSet is configured, reject filtering to avoid unintended exposure # noqa: E501 - raise ValidationError( - "Filtering is not enabled for this endpoint (missing filterset_class)" - ) + raise ValidationError("Filtering is not enabled for this endpoint (missing filterset_class)") # Extract field names from the filter data fields = self._extract_field_names(filter_data) @@ -188,11 +182,7 @@ class ComplexFilterBackend(filters.BaseFilterBackend): continue # Check if this child contains logical operators - has_logical = any( - k.lower() in ("or", "and", "not") - for k in child.keys() - if isinstance(k, str) - ) + has_logical = any(k.lower() in ("or", "and", "not") for k in child.keys() if isinstance(k, str)) if has_logical: # This child has logical operators, evaluate separately @@ -208,9 +198,7 @@ class ComplexFilterBackend(filters.BaseFilterBackend): # Apply collected field conditions together if any exist if collected_conditions: - result_qs = self._filter_leaf_via_backend( - collected_conditions, result_qs, view - ) + result_qs = self._filter_leaf_via_backend(collected_conditions, result_qs, view) if result_qs is None: return None @@ -284,10 +272,7 @@ class ComplexFilterBackend(filters.BaseFilterBackend): - Depth must not exceed max_depth """ if current_depth > max_depth: - raise ValidationError( - f"Filter nesting is too deep (max {max_depth}); found depth" - f" {current_depth}" - ) + raise ValidationError(f"Filter nesting is too deep (max {max_depth}); found depth {current_depth}") if not isinstance(node, dict): raise ValidationError("Each filter node must be a JSON object") @@ -295,40 +280,26 @@ class ComplexFilterBackend(filters.BaseFilterBackend): if not node: raise ValidationError("Filter objects must not be empty") - logical_keys = [ - k - for k in node.keys() - if isinstance(k, str) and k.lower() in ("or", "and", "not") - ] + logical_keys = [k for k in node.keys() if isinstance(k, str) and k.lower() in ("or", "and", "not")] if len(logical_keys) > 1: - raise ValidationError( - "A filter object cannot contain multiple logical operators at" - " the same level" - ) + raise ValidationError("A filter object cannot contain multiple logical operators at the same level") if len(logical_keys) == 1: op_key = logical_keys[0] # must not mix operator with other keys if len(node) != 1: - raise ValidationError( - f"Cannot mix logical operator '{op_key}' with field keys at" - f" the same level" - ) + raise ValidationError(f"Cannot mix logical operator '{op_key}' with field keys at the same level") op = op_key.lower() value = node[op_key] if op in ("or", "and"): if not isinstance(value, list) or len(value) == 0: - raise ValidationError( - f"'{op}' must be a non-empty list of filter objects" - ) + raise ValidationError(f"'{op}' must be a non-empty list of filter objects") for child in value: if not isinstance(child, dict): - raise ValidationError( - f"All children of '{op}' must be JSON objects" - ) + raise ValidationError(f"All children of '{op}' must be JSON objects") self._validate_structure( child, max_depth=max_depth, @@ -339,9 +310,7 @@ class ComplexFilterBackend(filters.BaseFilterBackend): if op == "not": if not isinstance(value, dict): raise ValidationError("'not' must be a single JSON object") - self._validate_structure( - value, max_depth=max_depth, current_depth=current_depth + 1 - ) + self._validate_structure(value, max_depth=max_depth, current_depth=current_depth + 1) return # Leaf node: validate fields and values @@ -354,9 +323,7 @@ class ComplexFilterBackend(filters.BaseFilterBackend): for key, value in leaf.items(): if isinstance(key, str) and key.lower() in ("or", "and", "not"): - raise ValidationError( - "Logical operators cannot appear in a leaf filter object" - ) + raise ValidationError("Logical operators cannot appear in a leaf filter object") # Lists/Tuples must contain only scalar values if isinstance(value, (list, tuple)): @@ -364,17 +331,12 @@ class ComplexFilterBackend(filters.BaseFilterBackend): raise ValidationError(f"List value for '{key}' must not be empty") for item in value: if not self._is_scalar(item): - raise ValidationError( - f"List value for '{key}' must contain only scalar items" - ) + raise ValidationError(f"List value for '{key}' must contain only scalar items") continue # Scalars and None are allowed if not self._is_scalar(value): - raise ValidationError( - f"Value for '{key}' must be a scalar, null, or list/tuple of" - f" scalars" - ) + raise ValidationError(f"Value for '{key}' must be a scalar, null, or list/tuple of scalars") def _is_scalar(self, value): return value is None or isinstance(value, (str, int, float, bool)) diff --git a/apps/api/plane/utils/filters/filter_migrations.py b/apps/api/plane/utils/filters/filter_migrations.py index baf31c432..3e424b6e6 100644 --- a/apps/api/plane/utils/filters/filter_migrations.py +++ b/apps/api/plane/utils/filters/filter_migrations.py @@ -48,17 +48,13 @@ def migrate_single_model_filters( updated_records.append(record) except Exception as e: - logger.warning( - f"Failed to convert filters for {model_name} ID {record.id}: {str(e)}" - ) + logger.warning(f"Failed to convert filters for {model_name} ID {record.id}: {str(e)}") conversion_errors += 1 continue # Bulk update all successfully converted records if updated_records: - model_class.objects.bulk_update( - updated_records, ["rich_filters"], batch_size=1000 - ) + model_class.objects.bulk_update(updated_records, ["rich_filters"], batch_size=1000) logger.info(f"Successfully updated {len(updated_records)} {model_name} records") return len(updated_records), conversion_errors @@ -87,9 +83,7 @@ def migrate_models_filters_to_rich_filters( for model_name, model_class in models_to_migrate.items(): try: - updated_count, error_count = migrate_single_model_filters( - model_class, model_name, converter - ) + updated_count, error_count = migrate_single_model_filters(model_class, model_name, converter) results[model_name] = (updated_count, error_count) total_updated += updated_count @@ -102,10 +96,7 @@ def migrate_models_filters_to_rich_filters( continue # Log final summary - logger.info( - f"Migration completed for all models. Total updated: {total_updated}, " - f"Total errors: {total_errors}" - ) + logger.info(f"Migration completed for all models. Total updated: {total_updated}, Total errors: {total_errors}") return results @@ -128,14 +119,10 @@ def clear_models_rich_filters(models_to_clear: Dict[str, Any]) -> Dict[str, int] for model_name, model_class in models_to_clear.items(): try: # Clear rich_filters for all records that have them - updated_count = model_class.objects.exclude(rich_filters={}).update( - rich_filters={} - ) + updated_count = model_class.objects.exclude(rich_filters={}).update(rich_filters={}) results[model_name] = updated_count total_cleared += updated_count - logger.info( - f"Cleared rich_filters for {updated_count} {model_name} records" - ) + logger.info(f"Cleared rich_filters for {updated_count} {model_name} records") except Exception as e: logger.error(f"Failed to clear rich_filters for {model_name}: {str(e)}") diff --git a/apps/api/plane/utils/grouper.py b/apps/api/plane/utils/grouper.py index 05b78da0b..1ec004e95 100644 --- a/apps/api/plane/utils/grouper.py +++ b/apps/api/plane/utils/grouper.py @@ -71,15 +71,9 @@ def issue_queryset_grouper( ) annotations_map: Dict[str, Tuple[str, Q]] = { - "assignee_ids": Coalesce( - issue_assignee_subquery, Value([], output_field=ArrayField(UUIDField())) - ), - "label_ids": Coalesce( - issue_label_subquery, Value([], output_field=ArrayField(UUIDField())) - ), - "module_ids": Coalesce( - issue_module_subquery, Value([], output_field=ArrayField(UUIDField())) - ), + "assignee_ids": Coalesce(issue_assignee_subquery, Value([], output_field=ArrayField(UUIDField()))), + "label_ids": Coalesce(issue_label_subquery, Value([], output_field=ArrayField(UUIDField()))), + "module_ids": Coalesce(issue_module_subquery, Value([], output_field=ArrayField(UUIDField()))), } default_annotations: Dict[str, Any] = {} @@ -151,17 +145,13 @@ def issue_group_values( queryset: Optional[QuerySet] = None, ) -> List[Union[str, Any]]: if field == "state_id": - queryset = State.objects.filter( - is_triage=False, workspace__slug=slug - ).values_list("id", flat=True) + queryset = State.objects.filter(is_triage=False, workspace__slug=slug).values_list("id", flat=True) if project_id: return list(queryset.filter(project_id=project_id)) return list(queryset) if field == "labels__id": - queryset = Label.objects.filter(workspace__slug=slug).values_list( - "id", flat=True - ) + queryset = Label.objects.filter(workspace__slug=slug).values_list("id", flat=True) if project_id: return list(queryset.filter(project_id=project_id)) + ["None"] return list(queryset) + ["None"] @@ -169,36 +159,28 @@ def issue_group_values( if field == "assignees__id": if project_id: return list( - ProjectMember.objects.filter( - workspace__slug=slug, project_id=project_id, is_active=True - ).values_list("member_id", flat=True) + ProjectMember.objects.filter(workspace__slug=slug, project_id=project_id, is_active=True).values_list( + "member_id", flat=True + ) ) return list( - WorkspaceMember.objects.filter( - workspace__slug=slug, is_active=True - ).values_list("member_id", flat=True) + WorkspaceMember.objects.filter(workspace__slug=slug, is_active=True).values_list("member_id", flat=True) ) if field == "issue_module__module_id": - queryset = Module.objects.filter(workspace__slug=slug).values_list( - "id", flat=True - ) + queryset = Module.objects.filter(workspace__slug=slug).values_list("id", flat=True) if project_id: return list(queryset.filter(project_id=project_id)) + ["None"] return list(queryset) + ["None"] if field == "cycle_id": - queryset = Cycle.objects.filter(workspace__slug=slug).values_list( - "id", flat=True - ) + queryset = Cycle.objects.filter(workspace__slug=slug).values_list("id", flat=True) if project_id: return list(queryset.filter(project_id=project_id)) + ["None"] return list(queryset) + ["None"] if field == "project_id": - queryset = Project.objects.filter(workspace__slug=slug).values_list( - "id", flat=True - ) + queryset = Project.objects.filter(workspace__slug=slug).values_list("id", flat=True) return list(queryset) if field == "priority": diff --git a/apps/api/plane/utils/issue_filters.py b/apps/api/plane/utils/issue_filters.py index 9136367b6..8d56bc389 100644 --- a/apps/api/plane/utils/issue_filters.py +++ b/apps/api/plane/utils/issue_filters.py @@ -27,22 +27,14 @@ def string_date_filter(issue_filter, duration, subsequent, term, date_filter, of if term == "months": if subsequent == "after": if offset == "fromnow": - issue_filter[f"{date_filter}__gte"] = now + timedelta( - days=duration * 30 - ) + issue_filter[f"{date_filter}__gte"] = now + timedelta(days=duration * 30) else: - issue_filter[f"{date_filter}__gte"] = now - timedelta( - days=duration * 30 - ) + issue_filter[f"{date_filter}__gte"] = now - timedelta(days=duration * 30) else: if offset == "fromnow": - issue_filter[f"{date_filter}__lte"] = now + timedelta( - days=duration * 30 - ) + issue_filter[f"{date_filter}__lte"] = now + timedelta(days=duration * 30) else: - issue_filter[f"{date_filter}__lte"] = now - timedelta( - days=duration * 30 - ) + issue_filter[f"{date_filter}__lte"] = now - timedelta(days=duration * 30) if term == "weeks": if subsequent == "after": if offset == "fromnow": @@ -92,37 +84,25 @@ def filter_state(params, issue_filter, method, prefix=""): if len(states) and "" not in states: issue_filter[f"{prefix}state__in"] = states else: - if ( - params.get("state", None) - and len(params.get("state")) - and params.get("state") != "null" - ): + if params.get("state", None) and len(params.get("state")) and params.get("state") != "null": issue_filter[f"{prefix}state__in"] = params.get("state") return issue_filter def filter_state_group(params, issue_filter, method, prefix=""): if method == "GET": - state_group = [ - item for item in params.get("state_group").split(",") if item != "null" - ] + state_group = [item for item in params.get("state_group").split(",") if item != "null"] if len(state_group) and "" not in state_group: issue_filter[f"{prefix}state__group__in"] = state_group else: - if ( - params.get("state_group", None) - and len(params.get("state_group")) - and params.get("state_group") != "null" - ): + if params.get("state_group", None) and len(params.get("state_group")) and params.get("state_group") != "null": issue_filter[f"{prefix}state__group__in"] = params.get("state_group") return issue_filter def filter_estimate_point(params, issue_filter, method, prefix=""): if method == "GET": - estimate_points = [ - item for item in params.get("estimate_point").split(",") if item != "null" - ] + estimate_points = [item for item in params.get("estimate_point").split(",") if item != "null"] if len(estimate_points) and "" not in estimate_points: issue_filter[f"{prefix}estimate_point__in"] = estimate_points else: @@ -137,17 +117,11 @@ def filter_estimate_point(params, issue_filter, method, prefix=""): def filter_priority(params, issue_filter, method, prefix=""): if method == "GET": - priorities = [ - item for item in params.get("priority").split(",") if item != "null" - ] + priorities = [item for item in params.get("priority").split(",") if item != "null"] if len(priorities) and "" not in priorities: issue_filter[f"{prefix}priority__in"] = priorities else: - if ( - params.get("priority", None) - and len(params.get("priority")) - and params.get("priority") != "null" - ): + if params.get("priority", None) and len(params.get("priority")) and params.get("priority") != "null": issue_filter[f"{prefix}priority__in"] = params.get("priority") return issue_filter @@ -161,11 +135,7 @@ def filter_parent(params, issue_filter, method, prefix=""): if len(parents) and "" not in parents: issue_filter[f"{prefix}parent__in"] = parents else: - if ( - params.get("parent", None) - and len(params.get("parent")) - and params.get("parent") != "null" - ): + if params.get("parent", None) and len(params.get("parent")) and params.get("parent") != "null": issue_filter[f"{prefix}parent__in"] = params.get("parent") return issue_filter @@ -179,11 +149,7 @@ def filter_labels(params, issue_filter, method, prefix=""): if len(labels) and "" not in labels: issue_filter[f"{prefix}labels__in"] = labels else: - if ( - params.get("labels", None) - and len(params.get("labels")) - and params.get("labels") != "null" - ): + if params.get("labels", None) and len(params.get("labels")) and params.get("labels") != "null": issue_filter[f"{prefix}labels__in"] = params.get("labels") issue_filter[f"{prefix}label_issue__deleted_at__isnull"] = True return issue_filter @@ -191,20 +157,14 @@ def filter_labels(params, issue_filter, method, prefix=""): def filter_assignees(params, issue_filter, method, prefix=""): if method == "GET": - assignees = [ - item for item in params.get("assignees").split(",") if item != "null" - ] + assignees = [item for item in params.get("assignees").split(",") if item != "null"] if "None" in assignees: issue_filter[f"{prefix}assignees__isnull"] = True assignees = filter_valid_uuids(assignees) if len(assignees) and "" not in assignees: issue_filter[f"{prefix}assignees__in"] = assignees else: - if ( - params.get("assignees", None) - and len(params.get("assignees")) - and params.get("assignees") != "null" - ): + if params.get("assignees", None) and len(params.get("assignees")) and params.get("assignees") != "null": issue_filter[f"{prefix}assignees__in"] = params.get("assignees") issue_filter[f"{prefix}issue_assignee__deleted_at__isnull"] = True return issue_filter @@ -212,40 +172,26 @@ def filter_assignees(params, issue_filter, method, prefix=""): def filter_mentions(params, issue_filter, method, prefix=""): if method == "GET": - mentions = [ - item for item in params.get("mentions").split(",") if item != "null" - ] + mentions = [item for item in params.get("mentions").split(",") if item != "null"] mentions = filter_valid_uuids(mentions) if len(mentions) and "" not in mentions: issue_filter[f"{prefix}issue_mention__mention__id__in"] = mentions else: - if ( - params.get("mentions", None) - and len(params.get("mentions")) - and params.get("mentions") != "null" - ): - issue_filter[f"{prefix}issue_mention__mention__id__in"] = params.get( - "mentions" - ) + if params.get("mentions", None) and len(params.get("mentions")) and params.get("mentions") != "null": + issue_filter[f"{prefix}issue_mention__mention__id__in"] = params.get("mentions") return issue_filter def filter_created_by(params, issue_filter, method, prefix=""): if method == "GET": - created_bys = [ - item for item in params.get("created_by").split(",") if item != "null" - ] + created_bys = [item for item in params.get("created_by").split(",") if item != "null"] if "None" in created_bys: issue_filter[f"{prefix}created_by__isnull"] = True created_bys = filter_valid_uuids(created_bys) if len(created_bys) and "" not in created_bys: issue_filter[f"{prefix}created_by__in"] = created_bys else: - if ( - params.get("created_by", None) - and len(params.get("created_by")) - and params.get("created_by") != "null" - ): + if params.get("created_by", None) and len(params.get("created_by")) and params.get("created_by") != "null": issue_filter[f"{prefix}created_by__in"] = params.get("created_by") return issue_filter @@ -362,11 +308,7 @@ def filter_project(params, issue_filter, method, prefix=""): if len(projects) and "" not in projects: issue_filter[f"{prefix}project__in"] = projects else: - if ( - params.get("project", None) - and len(params.get("project")) - and params.get("project") != "null" - ): + if params.get("project", None) and len(params.get("project")) and params.get("project") != "null": issue_filter[f"{prefix}project__in"] = params.get("project") return issue_filter @@ -380,11 +322,7 @@ def filter_cycle(params, issue_filter, method, prefix=""): if len(cycles) and "" not in cycles: issue_filter[f"{prefix}issue_cycle__cycle_id__in"] = cycles else: - if ( - params.get("cycle", None) - and len(params.get("cycle")) - and params.get("cycle") != "null" - ): + if params.get("cycle", None) and len(params.get("cycle")) and params.get("cycle") != "null": issue_filter[f"{prefix}issue_cycle__cycle_id__in"] = params.get("cycle") issue_filter[f"{prefix}issue_cycle__deleted_at__isnull"] = True return issue_filter @@ -399,11 +337,7 @@ def filter_module(params, issue_filter, method, prefix=""): if len(modules) and "" not in modules: issue_filter[f"{prefix}issue_module__module_id__in"] = modules else: - if ( - params.get("module", None) - and len(params.get("module")) - and params.get("module") != "null" - ): + if params.get("module", None) and len(params.get("module")) and params.get("module") != "null": issue_filter[f"{prefix}issue_module__module_id__in"] = params.get("module") issue_filter[f"{prefix}issue_module__deleted_at__isnull"] = True return issue_filter @@ -411,9 +345,7 @@ def filter_module(params, issue_filter, method, prefix=""): def filter_intake_status(params, issue_filter, method, prefix=""): if method == "GET": - status = [ - item for item in params.get("intake_status").split(",") if item != "null" - ] + status = [item for item in params.get("intake_status").split(",") if item != "null"] if len(status) and "" not in status: issue_filter[f"{prefix}issue_intake__status__in"] = status else: @@ -422,17 +354,13 @@ def filter_intake_status(params, issue_filter, method, prefix=""): and len(params.get("intake_status")) and params.get("intake_status") != "null" ): - issue_filter[f"{prefix}issue_intake__status__in"] = params.get( - "inbox_status" - ) + issue_filter[f"{prefix}issue_intake__status__in"] = params.get("inbox_status") return issue_filter def filter_inbox_status(params, issue_filter, method, prefix=""): if method == "GET": - status = [ - item for item in params.get("inbox_status").split(",") if item != "null" - ] + status = [item for item in params.get("inbox_status").split(",") if item != "null"] if len(status) and "" not in status: issue_filter[f"{prefix}issue_intake__status__in"] = status else: @@ -441,9 +369,7 @@ def filter_inbox_status(params, issue_filter, method, prefix=""): and len(params.get("inbox_status")) and params.get("inbox_status") != "null" ): - issue_filter[f"{prefix}issue_intake__status__in"] = params.get( - "inbox_status" - ) + issue_filter[f"{prefix}issue_intake__status__in"] = params.get("inbox_status") return issue_filter @@ -461,21 +387,13 @@ def filter_sub_issue_toggle(params, issue_filter, method, prefix=""): def filter_subscribed_issues(params, issue_filter, method, prefix=""): if method == "GET": - subscribers = [ - item for item in params.get("subscriber").split(",") if item != "null" - ] + subscribers = [item for item in params.get("subscriber").split(",") if item != "null"] subscribers = filter_valid_uuids(subscribers) if len(subscribers) and "" not in subscribers: issue_filter[f"{prefix}issue_subscribers__subscriber_id__in"] = subscribers else: - if ( - params.get("subscriber", None) - and len(params.get("subscriber")) - and params.get("subscriber") != "null" - ): - issue_filter[f"{prefix}issue_subscribers__subscriber_id__in"] = params.get( - "subscriber" - ) + if params.get("subscriber", None) and len(params.get("subscriber")) and params.get("subscriber") != "null": + issue_filter[f"{prefix}issue_subscribers__subscriber_id__in"] = params.get("subscriber") issue_filter[f"{prefix}issue_subscribers__deleted_at__isnull"] = True return issue_filter @@ -491,20 +409,14 @@ def filter_start_target_date_issues(params, issue_filter, method, prefix=""): def filter_logged_by(params, issue_filter, method, prefix=""): if method == "GET": - logged_bys = [ - item for item in params.get("logged_by").split(",") if item != "null" - ] + logged_bys = [item for item in params.get("logged_by").split(",") if item != "null"] if "None" in logged_bys: issue_filter[f"{prefix}logged_by__isnull"] = True logged_bys = filter_valid_uuids(logged_bys) if len(logged_bys) and "" not in logged_bys: issue_filter[f"{prefix}logged_by__in"] = logged_bys else: - if ( - params.get("logged_by", None) - and len(params.get("logged_by")) - and params.get("logged_by") != "null" - ): + if params.get("logged_by", None) and len(params.get("logged_by")) and params.get("logged_by") != "null": issue_filter[f"{prefix}logged_by__in"] = params.get("logged_by") return issue_filter diff --git a/apps/api/plane/utils/logging.py b/apps/api/plane/utils/logging.py index 8021689e9..083132f16 100644 --- a/apps/api/plane/utils/logging.py +++ b/apps/api/plane/utils/logging.py @@ -20,9 +20,7 @@ class SizedTimedRotatingFileHandler(handlers.TimedRotatingFileHandler): interval=1, utc=False, ): - handlers.TimedRotatingFileHandler.__init__( - self, filename, when, interval, backupCount, encoding, delay, utc - ) + handlers.TimedRotatingFileHandler.__init__(self, filename, when, interval, backupCount, encoding, delay, utc) self.maxBytes = maxBytes def shouldRollover(self, record): diff --git a/apps/api/plane/utils/openapi/auth.py b/apps/api/plane/utils/openapi/auth.py index e6012cc4e..9434956fe 100644 --- a/apps/api/plane/utils/openapi/auth.py +++ b/apps/api/plane/utils/openapi/auth.py @@ -10,7 +10,8 @@ from drf_spectacular.extensions import OpenApiAuthenticationExtension class APIKeyAuthenticationExtension(OpenApiAuthenticationExtension): """ - OpenAPI authentication extension for plane.api.middleware.api_authentication.APIKeyAuthentication + OpenAPI authentication extension for + plane.api.middleware.api_authentication.APIKeyAuthentication """ target_class = "plane.api.middleware.api_authentication.APIKeyAuthentication" @@ -25,5 +26,5 @@ class APIKeyAuthenticationExtension(OpenApiAuthenticationExtension): "type": "apiKey", "in": "header", "name": "X-API-Key", - "description": "API key authentication. Provide your API key in the X-API-Key header.", + "description": "API key authentication. Provide your API key in the X-API-Key header.", # noqa: E501 } diff --git a/apps/api/plane/utils/openapi/hooks.py b/apps/api/plane/utils/openapi/hooks.py index 3cd7eaf7a..f136324c0 100644 --- a/apps/api/plane/utils/openapi/hooks.py +++ b/apps/api/plane/utils/openapi/hooks.py @@ -14,11 +14,7 @@ def preprocess_filter_api_v1_paths(endpoints): filtered = [] for path, path_regex, method, callback in endpoints: # Only include paths that start with /api/v1/ and exclude PUT methods - if ( - path.startswith("/api/v1/") - and method.upper() != "PUT" - and "server" not in path.lower() - ): + if path.startswith("/api/v1/") and method.upper() != "PUT" and "server" not in path.lower(): filtered.append((path, path_regex, method, callback)) return filtered @@ -46,11 +42,11 @@ def generate_operation_summary(method, path, tag): # Handle specific cases if "archive" in path.lower(): if method == "POST": - return f'Archive {tag.rstrip("s")}' + return f"Archive {tag.rstrip('s')}" elif method == "DELETE": - return f'Unarchive {tag.rstrip("s")}' + return f"Unarchive {tag.rstrip('s')}" if "transfer" in path.lower(): - return f'Transfer {tag.rstrip("s")}' + return f"Transfer {tag.rstrip('s')}" return method_summaries.get(method, f"{method} {resource}") diff --git a/apps/api/plane/utils/openapi/responses.py b/apps/api/plane/utils/openapi/responses.py index a812cd307..2a569e377 100644 --- a/apps/api/plane/utils/openapi/responses.py +++ b/apps/api/plane/utils/openapi/responses.py @@ -402,9 +402,7 @@ def create_paginated_response( # Asset-specific Responses -PRESIGNED_URL_SUCCESS_RESPONSE = OpenApiResponse( - description="Presigned URL generated successfully" -) +PRESIGNED_URL_SUCCESS_RESPONSE = OpenApiResponse(description="Presigned URL generated successfully") GENERIC_ASSET_UPLOAD_SUCCESS_RESPONSE = OpenApiResponse( description="Presigned URL generated successfully", @@ -474,9 +472,7 @@ ASSET_DOWNLOAD_SUCCESS_RESPONSE = OpenApiResponse( ASSET_DOWNLOAD_ERROR_RESPONSE = OpenApiResponse( description="Bad request", examples=[ - OpenApiExample( - name="Asset not uploaded", value={"error": "Asset not yet uploaded"} - ), + OpenApiExample(name="Asset not uploaded", value={"error": "Asset not yet uploaded"}), ], ) @@ -486,7 +482,5 @@ ASSET_DELETED_RESPONSE = OpenApiResponse(description="Asset deleted successfully ASSET_NOT_FOUND_RESPONSE = OpenApiResponse( description="Asset not found", - examples=[ - OpenApiExample(name="Asset not found", value={"error": "Asset not found"}) - ], + examples=[OpenApiExample(name="Asset not found", value={"error": "Asset not found"})], ) diff --git a/apps/api/plane/utils/order_queryset.py b/apps/api/plane/utils/order_queryset.py index 9138cb31e..167cd0693 100644 --- a/apps/api/plane/utils/order_queryset.py +++ b/apps/api/plane/utils/order_queryset.py @@ -10,36 +10,22 @@ def order_issue_queryset(issue_queryset, order_by_param="-created_at"): if order_by_param == "priority" or order_by_param == "-priority": issue_queryset = issue_queryset.annotate( priority_order=Case( - *[ - When(priority=p, then=Value(i)) - for i, p in enumerate(PRIORITY_ORDER) - ], + *[When(priority=p, then=Value(i)) for i, p in enumerate(PRIORITY_ORDER)], output_field=CharField(), ) ).order_by("priority_order", "-created_at") - order_by_param = ( - "priority_order" if order_by_param.startswith("-") else "-priority_order" - ) + order_by_param = "priority_order" if order_by_param.startswith("-") else "-priority_order" # State Ordering elif order_by_param in ["state__group", "-state__group"]: - state_order = ( - STATE_ORDER - if order_by_param in ["state__name", "state__group"] - else STATE_ORDER[::-1] - ) + state_order = STATE_ORDER if order_by_param in ["state__name", "state__group"] else STATE_ORDER[::-1] issue_queryset = issue_queryset.annotate( state_order=Case( - *[ - When(state__group=state_group, then=Value(i)) - for i, state_group in enumerate(state_order) - ], + *[When(state__group=state_group, then=Value(i)) for i, state_group in enumerate(state_order)], default=Value(len(state_order)), output_field=CharField(), ) ).order_by("state_order", "-created_at") - order_by_param = ( - "-state_order" if order_by_param.startswith("-") else "state_order" - ) + order_by_param = "-state_order" if order_by_param.startswith("-") else "state_order" # assignee and label ordering elif order_by_param in [ "labels__name", @@ -50,18 +36,12 @@ def order_issue_queryset(issue_queryset, order_by_param="-created_at"): "-issue_module__module__name", ]: issue_queryset = issue_queryset.annotate( - min_values=Min( - order_by_param[1::] - if order_by_param.startswith("-") - else order_by_param - ) + min_values=Min(order_by_param[1::] if order_by_param.startswith("-") else order_by_param) ).order_by( "-min_values" if order_by_param.startswith("-") else "min_values", "-created_at", ) - order_by_param = ( - "-min_values" if order_by_param.startswith("-") else "min_values" - ) + order_by_param = "-min_values" if order_by_param.startswith("-") else "min_values" else: # If the order_by_param is created_at, then don't add the -created_at if "created_at" in order_by_param: diff --git a/apps/api/plane/utils/paginator.py b/apps/api/plane/utils/paginator.py index 8f18e40eb..f3a794756 100644 --- a/apps/api/plane/utils/paginator.py +++ b/apps/api/plane/utils/paginator.py @@ -29,8 +29,7 @@ class Cursor: # Return the cursor value def __eq__(self, other): return all( - getattr(self, attr) == getattr(other, attr) - for attr in ("value", "offset", "is_prev", "has_results") + getattr(self, attr) == getattr(other, attr) for attr in ("value", "offset", "is_prev", "has_results") ) # Return the representation of the cursor @@ -131,11 +130,7 @@ class OffsetPaginator: queryset = self.queryset if self.key: queryset = queryset.order_by( - ( - F(*self.key).desc(nulls_last=True) - if self.desc - else F(*self.key).asc(nulls_last=True) - ), + (F(*self.key).desc(nulls_last=True) if self.desc else F(*self.key).asc(nulls_last=True)), "-created_at", ) # The current page @@ -157,11 +152,7 @@ class OffsetPaginator: if cursor.value != limit and cursor.is_prev: results = results[-(limit + 1) :] - total_count = ( - self.total_count_queryset.count() - if self.total_count_queryset - else queryset.count() - ) + total_count = self.total_count_queryset.count() if self.total_count_queryset else queryset.count() # Check if there are more results available after the current page @@ -257,9 +248,7 @@ class GroupedOffsetPaginator(OffsetPaginator): partition_by=[F(self.group_by_field_name)], order_by=( ( - F(*self.key).desc( - nulls_last=True - ) # order by desc if desc is set + F(*self.key).desc(nulls_last=True) # order by desc if desc is set if self.desc else F(*self.key).asc(nulls_last=True) # Order by asc if set ), @@ -269,18 +258,12 @@ class GroupedOffsetPaginator(OffsetPaginator): ) # Filter the results by row number results = queryset.filter(row_number__gt=offset, row_number__lt=stop).order_by( - ( - F(*self.key).desc(nulls_last=True) - if self.desc - else F(*self.key).asc(nulls_last=True) - ), + (F(*self.key).desc(nulls_last=True) if self.desc else F(*self.key).asc(nulls_last=True)), F("created_at").desc(), ) # Adjust cursors based on the grouped results for pagination - next_cursor = Cursor( - limit, page + 1, False, queryset.filter(row_number__gte=stop).exists() - ) + next_cursor = Cursor(limit, page + 1, False, queryset.filter(row_number__gte=stop).exists()) # Add previous cursors prev_cursor = Cursor(limit, page - 1, True, page > 0) @@ -319,10 +302,9 @@ class GroupedOffsetPaginator(OffsetPaginator): # Convert the total into dictionary of keys as group name and value as the total total_group_dict = {} for group in self.__get_total_queryset(): - total_group_dict[str(group.get(self.group_by_field_name))] = ( - total_group_dict.get(str(group.get(self.group_by_field_name)), 0) - + (1 if group.get("count") == 0 else group.get("count")) - ) + total_group_dict[str(group.get(self.group_by_field_name))] = total_group_dict.get( + str(group.get(self.group_by_field_name)), 0 + ) + (1 if group.get("count") == 0 else group.get("count")) return total_group_dict def __get_field_dict(self): @@ -362,14 +344,10 @@ class GroupedOffsetPaginator(OffsetPaginator): for result in results: result_id = result["id"] group_ids = list(result_group_mapping[str(result_id)]) - result[self.FIELD_MAPPER.get(self.group_by_field_name)] = ( - [] if "None" in group_ids else group_ids - ) + result[self.FIELD_MAPPER.get(self.group_by_field_name)] = [] if "None" in group_ids else group_ids # If a result belongs to multiple groups, add it to each group for group_id in group_ids: - if not self.__result_already_added( - result, grouped_by_field_name[group_id] - ): + if not self.__result_already_added(result, grouped_by_field_name[group_id]): grouped_by_field_name[group_id].append(result) # Convert grouped_by_field_name back to a list for each group @@ -477,11 +455,7 @@ class SubGroupedOffsetPaginator(OffsetPaginator): F(self.sub_group_by_field_name), ], order_by=( - ( - F(*self.key).desc(nulls_last=True) - if self.desc - else F(*self.key).asc(nulls_last=True) - ), + (F(*self.key).desc(nulls_last=True) if self.desc else F(*self.key).asc(nulls_last=True)), "-created_at", ), ) @@ -489,18 +463,12 @@ class SubGroupedOffsetPaginator(OffsetPaginator): # Filter the results results = queryset.filter(row_number__gt=offset, row_number__lt=stop).order_by( - ( - F(*self.key).desc(nulls_last=True) - if self.desc - else F(*self.key).asc(nulls_last=True) - ), + (F(*self.key).desc(nulls_last=True) if self.desc else F(*self.key).asc(nulls_last=True)), F("created_at").desc(), ) # Adjust cursors based on the grouped results for pagination - next_cursor = Cursor( - limit, page + 1, False, queryset.filter(row_number__gte=stop).exists() - ) + next_cursor = Cursor(limit, page + 1, False, queryset.filter(row_number__gte=stop).exists()) # Add previous cursors prev_cursor = Cursor(limit, page - 1, True, page > 0) @@ -550,10 +518,9 @@ class SubGroupedOffsetPaginator(OffsetPaginator): total_group_dict = {} total_sub_group_dict = {} for group in self.__get_group_total_queryset(): - total_group_dict[str(group.get(self.group_by_field_name))] = ( - total_group_dict.get(str(group.get(self.group_by_field_name)), 0) - + (1 if group.get("count") == 0 else group.get("count")) - ) + total_group_dict[str(group.get(self.group_by_field_name))] = total_group_dict.get( + str(group.get(self.group_by_field_name)), 0 + ) + (1 if group.get("count") == 0 else group.get("count")) # Sub group total values for item in self.__get_subgroup_total_queryset(): @@ -584,9 +551,7 @@ class SubGroupedOffsetPaginator(OffsetPaginator): "results": { str(sub_group): { "results": [], - "total_results": total_sub_group_dict.get(str(group)).get( - str(sub_group), 0 - ), + "total_results": total_sub_group_dict.get(str(group)).get(str(sub_group), 0), } for sub_group in total_sub_group_dict.get(str(group), []) }, @@ -624,16 +589,11 @@ class SubGroupedOffsetPaginator(OffsetPaginator): # Check if the group value is in the processed results result_id = result["id"] - if ( - group_value in processed_results - and sub_group_value in processed_results[str(group_value)]["results"] - ): + if group_value in processed_results and sub_group_value in processed_results[str(group_value)]["results"]: if self.group_by_field_name in self.FIELD_MAPPER: # for multi grouper group_ids = list(result_group_mapping[str(result_id)]) - result[self.FIELD_MAPPER.get(self.group_by_field_name)] = ( - [] if "None" in group_ids else group_ids - ) + result[self.FIELD_MAPPER.get(self.group_by_field_name)] = [] if "None" in group_ids else group_ids if self.sub_group_by_field_name in self.FIELD_MAPPER: sub_group_ids = list(result_sub_group_mapping[str(result_id)]) # for multi groups @@ -641,9 +601,7 @@ class SubGroupedOffsetPaginator(OffsetPaginator): [] if "None" in sub_group_ids else sub_group_ids ) # If a result belongs to multiple groups, add it to each group - processed_results[str(group_value)]["results"][str(sub_group_value)][ - "results" - ].append(result) + processed_results[str(group_value)]["results"][str(sub_group_value)]["results"].append(result) return processed_results @@ -653,18 +611,13 @@ class SubGroupedOffsetPaginator(OffsetPaginator): for result in results: group_value = str(result.get(self.group_by_field_name)) sub_group_value = str(result.get(self.sub_group_by_field_name)) - processed_results[group_value]["results"][sub_group_value][ - "results" - ].append(result) + processed_results[group_value]["results"][sub_group_value]["results"].append(result) return processed_results def process_results(self, results): if results: - if ( - self.group_by_field_name in self.FIELD_MAPPER - or self.sub_group_by_field_name in self.FIELD_MAPPER - ): + if self.group_by_field_name in self.FIELD_MAPPER or self.sub_group_by_field_name in self.FIELD_MAPPER: # if the grouping is done through m2m then processed_results = self.__query_multi_grouper(results=results) else: @@ -690,9 +643,7 @@ class BasePaginator: max_per_page = max(max_per_page, default_per_page) if per_page > max_per_page: - raise ParseError( - detail=f"Invalid per_page value. Cannot exceed {max_per_page}." - ) + raise ParseError(detail=f"Invalid per_page value. Cannot exceed {max_per_page}.") return per_page @@ -720,9 +671,7 @@ class BasePaginator: # Convert the cursor value to integer and float from string input_cursor = None try: - input_cursor = cursor_cls.from_string( - request.GET.get(self.cursor_name, f"{per_page}:0:0") - ) + input_cursor = cursor_cls.from_string(request.GET.get(self.cursor_name, f"{per_page}:0:0")) except ValueError: raise ParseError(detail="Invalid cursor parameter.") @@ -733,9 +682,7 @@ class BasePaginator: paginator_kwargs["count_filter"] = count_filter if sub_group_by_field_name: - paginator_kwargs["sub_group_by_field_name"] = ( - sub_group_by_field_name - ) + paginator_kwargs["sub_group_by_field_name"] = sub_group_by_field_name paginator_kwargs["sub_group_by_fields"] = sub_group_by_fields paginator_kwargs["total_count_queryset"] = total_count_queryset diff --git a/apps/api/plane/utils/path_validator.py b/apps/api/plane/utils/path_validator.py index ccb67d868..ede3f1161 100644 --- a/apps/api/plane/utils/path_validator.py +++ b/apps/api/plane/utils/path_validator.py @@ -9,37 +9,37 @@ from urllib.parse import urlparse def _contains_suspicious_patterns(path: str) -> bool: """ Check for suspicious patterns that might indicate malicious intent. - + Args: path (str): The path to check - + Returns: bool: True if suspicious patterns found, False otherwise """ suspicious_patterns = [ - r'javascript:', # JavaScript injection - r'data:', # Data URLs - r'vbscript:', # VBScript injection - r'file:', # File protocol - r'ftp:', # FTP protocol - r'%2e%2e', # URL encoded path traversal - r'%2f%2f', # URL encoded double slash - r'%5c%5c', # URL encoded backslashes - r'