chore: formatting all python files using black formatter (#3366)
This commit is contained in:
parent
4b0d48b290
commit
11f84a986c
235 changed files with 12967 additions and 4168 deletions
|
|
@ -2,4 +2,4 @@ from django.apps import AppConfig
|
|||
|
||||
|
||||
class ApiConfig(AppConfig):
|
||||
name = "plane.api"
|
||||
name = "plane.api"
|
||||
|
|
|
|||
|
|
@ -25,7 +25,10 @@ class APIKeyAuthentication(authentication.BaseAuthentication):
|
|||
def validate_api_token(self, token):
|
||||
try:
|
||||
api_token = APIToken.objects.get(
|
||||
Q(Q(expired_at__gt=timezone.now()) | Q(expired_at__isnull=True)),
|
||||
Q(
|
||||
Q(expired_at__gt=timezone.now())
|
||||
| Q(expired_at__isnull=True)
|
||||
),
|
||||
token=token,
|
||||
is_active=True,
|
||||
)
|
||||
|
|
@ -44,4 +47,4 @@ class APIKeyAuthentication(authentication.BaseAuthentication):
|
|||
|
||||
# Validate the API token
|
||||
user, token = self.validate_api_token(token)
|
||||
return user, token
|
||||
return user, token
|
||||
|
|
|
|||
|
|
@ -1,17 +1,18 @@
|
|||
from rest_framework.throttling import SimpleRateThrottle
|
||||
|
||||
|
||||
class ApiKeyRateThrottle(SimpleRateThrottle):
|
||||
scope = 'api_key'
|
||||
rate = '60/minute'
|
||||
scope = "api_key"
|
||||
rate = "60/minute"
|
||||
|
||||
def get_cache_key(self, request, view):
|
||||
# Retrieve the API key from the request header
|
||||
api_key = request.headers.get('X-Api-Key')
|
||||
api_key = request.headers.get("X-Api-Key")
|
||||
if not api_key:
|
||||
return None # Allow the request if there's no API key
|
||||
|
||||
# Use the API key as part of the cache key
|
||||
return f'{self.scope}:{api_key}'
|
||||
return f"{self.scope}:{api_key}"
|
||||
|
||||
def allow_request(self, request, view):
|
||||
allowed = super().allow_request(request, view)
|
||||
|
|
@ -24,7 +25,7 @@ class ApiKeyRateThrottle(SimpleRateThrottle):
|
|||
# Remove old histories
|
||||
while history and history[-1] <= now - self.duration:
|
||||
history.pop()
|
||||
|
||||
|
||||
# Calculate the requests
|
||||
num_requests = len(history)
|
||||
|
||||
|
|
@ -35,7 +36,7 @@ class ApiKeyRateThrottle(SimpleRateThrottle):
|
|||
reset_time = int(now + self.duration)
|
||||
|
||||
# Add headers
|
||||
request.META['X-RateLimit-Remaining'] = max(0, available)
|
||||
request.META['X-RateLimit-Reset'] = reset_time
|
||||
request.META["X-RateLimit-Remaining"] = max(0, available)
|
||||
request.META["X-RateLimit-Reset"] = reset_time
|
||||
|
||||
return allowed
|
||||
return allowed
|
||||
|
|
|
|||
|
|
@ -13,5 +13,9 @@ from .issue import (
|
|||
)
|
||||
from .state import StateLiteSerializer, StateSerializer
|
||||
from .cycle import CycleSerializer, CycleIssueSerializer, CycleLiteSerializer
|
||||
from .module import ModuleSerializer, ModuleIssueSerializer, ModuleLiteSerializer
|
||||
from .inbox import InboxIssueSerializer
|
||||
from .module import (
|
||||
ModuleSerializer,
|
||||
ModuleIssueSerializer,
|
||||
ModuleLiteSerializer,
|
||||
)
|
||||
from .inbox import InboxIssueSerializer
|
||||
|
|
|
|||
|
|
@ -100,6 +100,8 @@ class BaseSerializer(serializers.ModelSerializer):
|
|||
response[expand] = exp_serializer.data
|
||||
else:
|
||||
# You might need to handle this case differently
|
||||
response[expand] = getattr(instance, f"{expand}_id", None)
|
||||
response[expand] = getattr(
|
||||
instance, f"{expand}_id", None
|
||||
)
|
||||
|
||||
return response
|
||||
return response
|
||||
|
|
|
|||
|
|
@ -23,7 +23,9 @@ class CycleSerializer(BaseSerializer):
|
|||
and data.get("end_date", None) is not None
|
||||
and data.get("start_date", None) > data.get("end_date", None)
|
||||
):
|
||||
raise serializers.ValidationError("Start date cannot exceed end date")
|
||||
raise serializers.ValidationError(
|
||||
"Start date cannot exceed end date"
|
||||
)
|
||||
return data
|
||||
|
||||
class Meta:
|
||||
|
|
@ -55,7 +57,6 @@ class CycleIssueSerializer(BaseSerializer):
|
|||
|
||||
|
||||
class CycleLiteSerializer(BaseSerializer):
|
||||
|
||||
class Meta:
|
||||
model = Cycle
|
||||
fields = "__all__"
|
||||
fields = "__all__"
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@
|
|||
from .base import BaseSerializer
|
||||
from plane.db.models import InboxIssue
|
||||
|
||||
class InboxIssueSerializer(BaseSerializer):
|
||||
|
||||
class InboxIssueSerializer(BaseSerializer):
|
||||
class Meta:
|
||||
model = InboxIssue
|
||||
fields = "__all__"
|
||||
|
|
@ -16,4 +16,4 @@ class InboxIssueSerializer(BaseSerializer):
|
|||
"updated_by",
|
||||
"created_at",
|
||||
"updated_at",
|
||||
]
|
||||
]
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ from .module import ModuleSerializer, ModuleLiteSerializer
|
|||
from .user import UserLiteSerializer
|
||||
from .state import StateLiteSerializer
|
||||
|
||||
|
||||
class IssueSerializer(BaseSerializer):
|
||||
assignees = serializers.ListField(
|
||||
child=serializers.PrimaryKeyRelatedField(
|
||||
|
|
@ -66,14 +67,16 @@ class IssueSerializer(BaseSerializer):
|
|||
and data.get("target_date", None) is not None
|
||||
and data.get("start_date", None) > data.get("target_date", None)
|
||||
):
|
||||
raise serializers.ValidationError("Start date cannot exceed target date")
|
||||
|
||||
raise serializers.ValidationError(
|
||||
"Start date cannot exceed target date"
|
||||
)
|
||||
|
||||
try:
|
||||
if(data.get("description_html", None) is not None):
|
||||
if data.get("description_html", None) is not None:
|
||||
parsed = html.fromstring(data["description_html"])
|
||||
parsed_str = html.tostring(parsed, encoding='unicode')
|
||||
parsed_str = html.tostring(parsed, encoding="unicode")
|
||||
data["description_html"] = parsed_str
|
||||
|
||||
|
||||
except Exception as e:
|
||||
raise serializers.ValidationError(f"Invalid HTML: {str(e)}")
|
||||
|
||||
|
|
@ -96,7 +99,8 @@ class IssueSerializer(BaseSerializer):
|
|||
if (
|
||||
data.get("state")
|
||||
and not State.objects.filter(
|
||||
project_id=self.context.get("project_id"), pk=data.get("state").id
|
||||
project_id=self.context.get("project_id"),
|
||||
pk=data.get("state").id,
|
||||
).exists()
|
||||
):
|
||||
raise serializers.ValidationError(
|
||||
|
|
@ -107,7 +111,8 @@ class IssueSerializer(BaseSerializer):
|
|||
if (
|
||||
data.get("parent")
|
||||
and not Issue.objects.filter(
|
||||
workspace_id=self.context.get("workspace_id"), pk=data.get("parent").id
|
||||
workspace_id=self.context.get("workspace_id"),
|
||||
pk=data.get("parent").id,
|
||||
).exists()
|
||||
):
|
||||
raise serializers.ValidationError(
|
||||
|
|
@ -238,9 +243,13 @@ class IssueSerializer(BaseSerializer):
|
|||
]
|
||||
if "labels" in self.fields:
|
||||
if "labels" in self.expand:
|
||||
data["labels"] = LabelSerializer(instance.labels.all(), many=True).data
|
||||
data["labels"] = LabelSerializer(
|
||||
instance.labels.all(), many=True
|
||||
).data
|
||||
else:
|
||||
data["labels"] = [str(label.id) for label in instance.labels.all()]
|
||||
data["labels"] = [
|
||||
str(label.id) for label in instance.labels.all()
|
||||
]
|
||||
|
||||
return data
|
||||
|
||||
|
|
@ -278,7 +287,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")
|
||||
url=validated_data.get("url"),
|
||||
issue_id=validated_data.get("issue_id"),
|
||||
).exists():
|
||||
raise serializers.ValidationError(
|
||||
{"error": "URL already exists for this Issue"}
|
||||
|
|
@ -324,11 +334,11 @@ class IssueCommentSerializer(BaseSerializer):
|
|||
|
||||
def validate(self, data):
|
||||
try:
|
||||
if(data.get("comment_html", None) is not None):
|
||||
if data.get("comment_html", None) is not None:
|
||||
parsed = html.fromstring(data["comment_html"])
|
||||
parsed_str = html.tostring(parsed, encoding='unicode')
|
||||
parsed_str = html.tostring(parsed, encoding="unicode")
|
||||
data["comment_html"] = parsed_str
|
||||
|
||||
|
||||
except Exception as e:
|
||||
raise serializers.ValidationError(f"Invalid HTML: {str(e)}")
|
||||
return data
|
||||
|
|
@ -362,7 +372,6 @@ class ModuleIssueSerializer(BaseSerializer):
|
|||
|
||||
|
||||
class LabelLiteSerializer(BaseSerializer):
|
||||
|
||||
class Meta:
|
||||
model = Label
|
||||
fields = [
|
||||
|
|
|
|||
|
|
@ -52,7 +52,9 @@ class ModuleSerializer(BaseSerializer):
|
|||
and data.get("target_date", None) is not None
|
||||
and data.get("start_date", None) > data.get("target_date", None)
|
||||
):
|
||||
raise serializers.ValidationError("Start date cannot exceed target date")
|
||||
raise serializers.ValidationError(
|
||||
"Start date cannot exceed target date"
|
||||
)
|
||||
|
||||
if data.get("members", []):
|
||||
data["members"] = ProjectMember.objects.filter(
|
||||
|
|
@ -146,16 +148,16 @@ 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")
|
||||
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)
|
||||
|
||||
|
||||
|
||||
class ModuleLiteSerializer(BaseSerializer):
|
||||
|
||||
class Meta:
|
||||
model = Module
|
||||
fields = "__all__"
|
||||
fields = "__all__"
|
||||
|
|
|
|||
|
|
@ -2,12 +2,17 @@
|
|||
from rest_framework import serializers
|
||||
|
||||
# Module imports
|
||||
from plane.db.models import Project, ProjectIdentifier, WorkspaceMember, State, Estimate
|
||||
from plane.db.models import (
|
||||
Project,
|
||||
ProjectIdentifier,
|
||||
WorkspaceMember,
|
||||
State,
|
||||
Estimate,
|
||||
)
|
||||
from .base import BaseSerializer
|
||||
|
||||
|
||||
class ProjectSerializer(BaseSerializer):
|
||||
|
||||
total_members = serializers.IntegerField(read_only=True)
|
||||
total_cycles = serializers.IntegerField(read_only=True)
|
||||
total_modules = serializers.IntegerField(read_only=True)
|
||||
|
|
@ -21,7 +26,7 @@ class ProjectSerializer(BaseSerializer):
|
|||
fields = "__all__"
|
||||
read_only_fields = [
|
||||
"id",
|
||||
'emoji',
|
||||
"emoji",
|
||||
"workspace",
|
||||
"created_at",
|
||||
"updated_at",
|
||||
|
|
@ -59,12 +64,16 @@ class ProjectSerializer(BaseSerializer):
|
|||
def create(self, validated_data):
|
||||
identifier = validated_data.get("identifier", "").strip().upper()
|
||||
if identifier == "":
|
||||
raise serializers.ValidationError(detail="Project Identifier is required")
|
||||
raise serializers.ValidationError(
|
||||
detail="Project Identifier is required"
|
||||
)
|
||||
|
||||
if ProjectIdentifier.objects.filter(
|
||||
name=identifier, workspace_id=self.context["workspace_id"]
|
||||
).exists():
|
||||
raise serializers.ValidationError(detail="Project Identifier is taken")
|
||||
raise serializers.ValidationError(
|
||||
detail="Project Identifier is taken"
|
||||
)
|
||||
|
||||
project = Project.objects.create(
|
||||
**validated_data, workspace_id=self.context["workspace_id"]
|
||||
|
|
@ -89,4 +98,4 @@ class ProjectLiteSerializer(BaseSerializer):
|
|||
"emoji",
|
||||
"description",
|
||||
]
|
||||
read_only_fields = fields
|
||||
read_only_fields = fields
|
||||
|
|
|
|||
|
|
@ -7,9 +7,9 @@ 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:
|
||||
|
|
@ -35,4 +35,4 @@ class StateLiteSerializer(BaseSerializer):
|
|||
"color",
|
||||
"group",
|
||||
]
|
||||
read_only_fields = fields
|
||||
read_only_fields = fields
|
||||
|
|
|
|||
|
|
@ -13,4 +13,4 @@ class UserLiteSerializer(BaseSerializer):
|
|||
"avatar",
|
||||
"display_name",
|
||||
]
|
||||
read_only_fields = fields
|
||||
read_only_fields = fields
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ from .base import BaseSerializer
|
|||
|
||||
class WorkspaceLiteSerializer(BaseSerializer):
|
||||
"""Lite serializer with only required fields"""
|
||||
|
||||
class Meta:
|
||||
model = Workspace
|
||||
fields = [
|
||||
|
|
@ -12,4 +13,4 @@ class WorkspaceLiteSerializer(BaseSerializer):
|
|||
"slug",
|
||||
"id",
|
||||
]
|
||||
read_only_fields = fields
|
||||
read_only_fields = fields
|
||||
|
|
|
|||
|
|
@ -12,4 +12,4 @@ urlpatterns = [
|
|||
*cycle_patterns,
|
||||
*module_patterns,
|
||||
*inbox_patterns,
|
||||
]
|
||||
]
|
||||
|
|
|
|||
|
|
@ -32,4 +32,4 @@ urlpatterns = [
|
|||
TransferCycleIssueAPIEndpoint.as_view(),
|
||||
name="transfer-issues",
|
||||
),
|
||||
]
|
||||
]
|
||||
|
|
|
|||
|
|
@ -14,4 +14,4 @@ urlpatterns = [
|
|||
InboxIssueAPIEndpoint.as_view(),
|
||||
name="inbox-issue",
|
||||
),
|
||||
]
|
||||
]
|
||||
|
|
|
|||
|
|
@ -23,4 +23,4 @@ urlpatterns = [
|
|||
ModuleIssueAPIEndpoint.as_view(),
|
||||
name="module-issues",
|
||||
),
|
||||
]
|
||||
]
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ from django.urls import path
|
|||
from plane.api.views import ProjectAPIEndpoint
|
||||
|
||||
urlpatterns = [
|
||||
path(
|
||||
path(
|
||||
"workspaces/<str:slug>/projects/",
|
||||
ProjectAPIEndpoint.as_view(),
|
||||
name="project",
|
||||
|
|
@ -13,4 +13,4 @@ urlpatterns = [
|
|||
ProjectAPIEndpoint.as_view(),
|
||||
name="project",
|
||||
),
|
||||
]
|
||||
]
|
||||
|
|
|
|||
|
|
@ -13,4 +13,4 @@ urlpatterns = [
|
|||
StateAPIEndpoint.as_view(),
|
||||
name="states",
|
||||
),
|
||||
]
|
||||
]
|
||||
|
|
|
|||
|
|
@ -18,4 +18,4 @@ from .cycle import (
|
|||
|
||||
from .module import ModuleAPIEndpoint, ModuleIssueAPIEndpoint
|
||||
|
||||
from .inbox import InboxIssueAPIEndpoint
|
||||
from .inbox import InboxIssueAPIEndpoint
|
||||
|
|
|
|||
|
|
@ -41,7 +41,9 @@ class WebhookMixin:
|
|||
bulk = False
|
||||
|
||||
def finalize_response(self, request, response, *args, **kwargs):
|
||||
response = super().finalize_response(request, response, *args, **kwargs)
|
||||
response = super().finalize_response(
|
||||
request, response, *args, **kwargs
|
||||
)
|
||||
|
||||
# Check for the case should webhook be sent
|
||||
if (
|
||||
|
|
@ -139,7 +141,9 @@ class BaseAPIView(TimezoneMixin, APIView, BasePaginator):
|
|||
|
||||
def finalize_response(self, request, response, *args, **kwargs):
|
||||
# Call super to get the default response
|
||||
response = super().finalize_response(request, response, *args, **kwargs)
|
||||
response = super().finalize_response(
|
||||
request, response, *args, **kwargs
|
||||
)
|
||||
|
||||
# Add custom headers if they exist in the request META
|
||||
ratelimit_remaining = request.META.get("X-RateLimit-Remaining")
|
||||
|
|
@ -163,13 +167,17 @@ class BaseAPIView(TimezoneMixin, APIView, BasePaginator):
|
|||
@property
|
||||
def fields(self):
|
||||
fields = [
|
||||
field for field in self.request.GET.get("fields", "").split(",") if field
|
||||
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
|
||||
for expand in self.request.GET.get("expand", "").split(",")
|
||||
if expand
|
||||
]
|
||||
return expand if expand else None
|
||||
|
|
|
|||
|
|
@ -12,7 +12,13 @@ from rest_framework import status
|
|||
|
||||
# Module imports
|
||||
from .base import BaseAPIView, WebhookMixin
|
||||
from plane.db.models import Cycle, Issue, CycleIssue, IssueLink, IssueAttachment
|
||||
from plane.db.models import (
|
||||
Cycle,
|
||||
Issue,
|
||||
CycleIssue,
|
||||
IssueLink,
|
||||
IssueAttachment,
|
||||
)
|
||||
from plane.app.permissions import ProjectEntityPermission
|
||||
from plane.api.serializers import (
|
||||
CycleSerializer,
|
||||
|
|
@ -102,7 +108,9 @@ class CycleAPIEndpoint(WebhookMixin, BaseAPIView):
|
|||
),
|
||||
)
|
||||
)
|
||||
.annotate(total_estimates=Sum("issue_cycle__issue__estimate_point"))
|
||||
.annotate(
|
||||
total_estimates=Sum("issue_cycle__issue__estimate_point")
|
||||
)
|
||||
.annotate(
|
||||
completed_estimates=Sum(
|
||||
"issue_cycle__issue__estimate_point",
|
||||
|
|
@ -201,7 +209,8 @@ class CycleAPIEndpoint(WebhookMixin, BaseAPIView):
|
|||
# Incomplete Cycles
|
||||
if cycle_view == "incomplete":
|
||||
queryset = queryset.filter(
|
||||
Q(end_date__gte=timezone.now().date()) | Q(end_date__isnull=True),
|
||||
Q(end_date__gte=timezone.now().date())
|
||||
| Q(end_date__isnull=True),
|
||||
)
|
||||
return self.paginate(
|
||||
request=request,
|
||||
|
|
@ -238,8 +247,12 @@ class CycleAPIEndpoint(WebhookMixin, BaseAPIView):
|
|||
project_id=project_id,
|
||||
owned_by=request.user,
|
||||
)
|
||||
return Response(serializer.data, status=status.HTTP_201_CREATED)
|
||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
return Response(
|
||||
serializer.data, status=status.HTTP_201_CREATED
|
||||
)
|
||||
return Response(
|
||||
serializer.errors, status=status.HTTP_400_BAD_REQUEST
|
||||
)
|
||||
else:
|
||||
return Response(
|
||||
{
|
||||
|
|
@ -249,15 +262,22 @@ class CycleAPIEndpoint(WebhookMixin, BaseAPIView):
|
|||
)
|
||||
|
||||
def patch(self, request, slug, project_id, pk):
|
||||
cycle = Cycle.objects.get(workspace__slug=slug, project_id=project_id, pk=pk)
|
||||
cycle = Cycle.objects.get(
|
||||
workspace__slug=slug, project_id=project_id, pk=pk
|
||||
)
|
||||
|
||||
request_data = request.data
|
||||
|
||||
if cycle.end_date is not None and cycle.end_date < timezone.now().date():
|
||||
if (
|
||||
cycle.end_date is not None
|
||||
and cycle.end_date < timezone.now().date()
|
||||
):
|
||||
if "sort_order" in request_data:
|
||||
# Can only change sort order
|
||||
request_data = {
|
||||
"sort_order": request_data.get("sort_order", cycle.sort_order)
|
||||
"sort_order": request_data.get(
|
||||
"sort_order", cycle.sort_order
|
||||
)
|
||||
}
|
||||
else:
|
||||
return Response(
|
||||
|
|
@ -275,11 +295,13 @@ class CycleAPIEndpoint(WebhookMixin, BaseAPIView):
|
|||
|
||||
def delete(self, request, slug, project_id, pk):
|
||||
cycle_issues = list(
|
||||
CycleIssue.objects.filter(cycle_id=self.kwargs.get("pk")).values_list(
|
||||
"issue", flat=True
|
||||
)
|
||||
CycleIssue.objects.filter(
|
||||
cycle_id=self.kwargs.get("pk")
|
||||
).values_list("issue", flat=True)
|
||||
)
|
||||
cycle = Cycle.objects.get(
|
||||
workspace__slug=slug, project_id=project_id, pk=pk
|
||||
)
|
||||
cycle = Cycle.objects.get(workspace__slug=slug, project_id=project_id, pk=pk)
|
||||
|
||||
issue_activity.delay(
|
||||
type="cycle.activity.deleted",
|
||||
|
|
@ -319,7 +341,9 @@ class CycleIssueAPIEndpoint(WebhookMixin, BaseAPIView):
|
|||
def get_queryset(self):
|
||||
return (
|
||||
CycleIssue.objects.annotate(
|
||||
sub_issues_count=Issue.issue_objects.filter(parent=OuterRef("issue_id"))
|
||||
sub_issues_count=Issue.issue_objects.filter(
|
||||
parent=OuterRef("issue_id")
|
||||
)
|
||||
.order_by()
|
||||
.annotate(count=Func(F("id"), function="Count"))
|
||||
.values("count")
|
||||
|
|
@ -342,7 +366,9 @@ class CycleIssueAPIEndpoint(WebhookMixin, BaseAPIView):
|
|||
issues = (
|
||||
Issue.issue_objects.filter(issue_cycle__cycle_id=cycle_id)
|
||||
.annotate(
|
||||
sub_issues_count=Issue.issue_objects.filter(parent=OuterRef("id"))
|
||||
sub_issues_count=Issue.issue_objects.filter(
|
||||
parent=OuterRef("id")
|
||||
)
|
||||
.order_by()
|
||||
.annotate(count=Func(F("id"), function="Count"))
|
||||
.values("count")
|
||||
|
|
@ -364,7 +390,9 @@ class CycleIssueAPIEndpoint(WebhookMixin, BaseAPIView):
|
|||
.values("count")
|
||||
)
|
||||
.annotate(
|
||||
attachment_count=IssueAttachment.objects.filter(issue=OuterRef("id"))
|
||||
attachment_count=IssueAttachment.objects.filter(
|
||||
issue=OuterRef("id")
|
||||
)
|
||||
.order_by()
|
||||
.annotate(count=Func(F("id"), function="Count"))
|
||||
.values("count")
|
||||
|
|
@ -387,14 +415,18 @@ class CycleIssueAPIEndpoint(WebhookMixin, BaseAPIView):
|
|||
|
||||
if not issues:
|
||||
return Response(
|
||||
{"error": "Issues are required"}, status=status.HTTP_400_BAD_REQUEST
|
||||
{"error": "Issues are required"},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
||||
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().date():
|
||||
if (
|
||||
cycle.end_date is not None
|
||||
and cycle.end_date < timezone.now().date()
|
||||
):
|
||||
return Response(
|
||||
{
|
||||
"error": "The Cycle has already been completed so no new issues can be added"
|
||||
|
|
@ -479,7 +511,10 @@ class CycleIssueAPIEndpoint(WebhookMixin, BaseAPIView):
|
|||
|
||||
def delete(self, request, slug, project_id, cycle_id, issue_id):
|
||||
cycle_issue = CycleIssue.objects.get(
|
||||
issue_id=issue_id, workspace__slug=slug, project_id=project_id, cycle_id=cycle_id
|
||||
issue_id=issue_id,
|
||||
workspace__slug=slug,
|
||||
project_id=project_id,
|
||||
cycle_id=cycle_id,
|
||||
)
|
||||
issue_id = cycle_issue.issue_id
|
||||
cycle_issue.delete()
|
||||
|
|
@ -550,4 +585,4 @@ class TransferCycleIssueAPIEndpoint(BaseAPIView):
|
|||
updated_cycles, ["cycle_id"], batch_size=100
|
||||
)
|
||||
|
||||
return Response({"message": "Success"}, status=status.HTTP_200_OK)
|
||||
return Response({"message": "Success"}, status=status.HTTP_200_OK)
|
||||
|
|
|
|||
|
|
@ -14,7 +14,14 @@ from rest_framework.response import Response
|
|||
from .base import BaseAPIView
|
||||
from plane.app.permissions import ProjectLitePermission
|
||||
from plane.api.serializers import InboxIssueSerializer, IssueSerializer
|
||||
from plane.db.models import InboxIssue, Issue, State, ProjectMember, Project, Inbox
|
||||
from plane.db.models import (
|
||||
InboxIssue,
|
||||
Issue,
|
||||
State,
|
||||
ProjectMember,
|
||||
Project,
|
||||
Inbox,
|
||||
)
|
||||
from plane.bgtasks.issue_activites_task import issue_activity
|
||||
|
||||
|
||||
|
|
@ -43,7 +50,8 @@ class InboxIssueAPIEndpoint(BaseAPIView):
|
|||
).first()
|
||||
|
||||
project = Project.objects.get(
|
||||
workspace__slug=self.kwargs.get("slug"), pk=self.kwargs.get("project_id")
|
||||
workspace__slug=self.kwargs.get("slug"),
|
||||
pk=self.kwargs.get("project_id"),
|
||||
)
|
||||
|
||||
if inbox is None and not project.inbox_view:
|
||||
|
|
@ -51,7 +59,8 @@ class InboxIssueAPIEndpoint(BaseAPIView):
|
|||
|
||||
return (
|
||||
InboxIssue.objects.filter(
|
||||
Q(snoozed_till__gte=timezone.now()) | Q(snoozed_till__isnull=True),
|
||||
Q(snoozed_till__gte=timezone.now())
|
||||
| Q(snoozed_till__isnull=True),
|
||||
workspace__slug=self.kwargs.get("slug"),
|
||||
project_id=self.kwargs.get("project_id"),
|
||||
inbox_id=inbox.id,
|
||||
|
|
@ -87,7 +96,8 @@ class InboxIssueAPIEndpoint(BaseAPIView):
|
|||
def post(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
|
||||
{"error": "Name is required"},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
||||
inbox = Inbox.objects.filter(
|
||||
|
|
@ -117,7 +127,8 @@ class InboxIssueAPIEndpoint(BaseAPIView):
|
|||
"none",
|
||||
]:
|
||||
return Response(
|
||||
{"error": "Invalid priority"}, status=status.HTTP_400_BAD_REQUEST
|
||||
{"error": "Invalid priority"},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
||||
# Create or get state
|
||||
|
|
@ -222,10 +233,14 @@ class InboxIssueAPIEndpoint(BaseAPIView):
|
|||
"description_html": issue_data.get(
|
||||
"description_html", issue.description_html
|
||||
),
|
||||
"description": issue_data.get("description", issue.description),
|
||||
"description": issue_data.get(
|
||||
"description", issue.description
|
||||
),
|
||||
}
|
||||
|
||||
issue_serializer = IssueSerializer(issue, data=issue_data, partial=True)
|
||||
issue_serializer = IssueSerializer(
|
||||
issue, data=issue_data, partial=True
|
||||
)
|
||||
|
||||
if issue_serializer.is_valid():
|
||||
current_instance = issue
|
||||
|
|
@ -266,7 +281,9 @@ class InboxIssueAPIEndpoint(BaseAPIView):
|
|||
project_id=project_id,
|
||||
)
|
||||
state = State.objects.filter(
|
||||
group="cancelled", workspace__slug=slug, project_id=project_id
|
||||
group="cancelled",
|
||||
workspace__slug=slug,
|
||||
project_id=project_id,
|
||||
).first()
|
||||
if state is not None:
|
||||
issue.state = state
|
||||
|
|
@ -284,17 +301,22 @@ class InboxIssueAPIEndpoint(BaseAPIView):
|
|||
if issue.state.name == "Triage":
|
||||
# Move to default state
|
||||
state = State.objects.filter(
|
||||
workspace__slug=slug, project_id=project_id, default=True
|
||||
workspace__slug=slug,
|
||||
project_id=project_id,
|
||||
default=True,
|
||||
).first()
|
||||
if state is not None:
|
||||
issue.state = state
|
||||
issue.save()
|
||||
|
||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
return Response(
|
||||
serializer.errors, status=status.HTTP_400_BAD_REQUEST
|
||||
)
|
||||
else:
|
||||
return Response(
|
||||
InboxIssueSerializer(inbox_issue).data, status=status.HTTP_200_OK
|
||||
InboxIssueSerializer(inbox_issue).data,
|
||||
status=status.HTTP_200_OK,
|
||||
)
|
||||
|
||||
def delete(self, request, slug, project_id, issue_id):
|
||||
|
|
|
|||
|
|
@ -67,7 +67,9 @@ class IssueAPIEndpoint(WebhookMixin, BaseAPIView):
|
|||
def get_queryset(self):
|
||||
return (
|
||||
Issue.issue_objects.annotate(
|
||||
sub_issues_count=Issue.issue_objects.filter(parent=OuterRef("id"))
|
||||
sub_issues_count=Issue.issue_objects.filter(
|
||||
parent=OuterRef("id")
|
||||
)
|
||||
.order_by()
|
||||
.annotate(count=Func(F("id"), function="Count"))
|
||||
.values("count")
|
||||
|
|
@ -86,7 +88,9 @@ class IssueAPIEndpoint(WebhookMixin, BaseAPIView):
|
|||
def get(self, request, slug, project_id, pk=None):
|
||||
if pk:
|
||||
issue = Issue.issue_objects.annotate(
|
||||
sub_issues_count=Issue.issue_objects.filter(parent=OuterRef("id"))
|
||||
sub_issues_count=Issue.issue_objects.filter(
|
||||
parent=OuterRef("id")
|
||||
)
|
||||
.order_by()
|
||||
.annotate(count=Func(F("id"), function="Count"))
|
||||
.values("count")
|
||||
|
|
@ -102,7 +106,13 @@ class IssueAPIEndpoint(WebhookMixin, BaseAPIView):
|
|||
|
||||
# Custom ordering for priority and state
|
||||
priority_order = ["urgent", "high", "medium", "low", "none"]
|
||||
state_order = ["backlog", "unstarted", "started", "completed", "cancelled"]
|
||||
state_order = [
|
||||
"backlog",
|
||||
"unstarted",
|
||||
"started",
|
||||
"completed",
|
||||
"cancelled",
|
||||
]
|
||||
|
||||
order_by_param = request.GET.get("order_by", "-created_at")
|
||||
|
||||
|
|
@ -117,7 +127,9 @@ class IssueAPIEndpoint(WebhookMixin, BaseAPIView):
|
|||
.values("count")
|
||||
)
|
||||
.annotate(
|
||||
attachment_count=IssueAttachment.objects.filter(issue=OuterRef("id"))
|
||||
attachment_count=IssueAttachment.objects.filter(
|
||||
issue=OuterRef("id")
|
||||
)
|
||||
.order_by()
|
||||
.annotate(count=Func(F("id"), function="Count"))
|
||||
.values("count")
|
||||
|
|
@ -127,7 +139,9 @@ class IssueAPIEndpoint(WebhookMixin, BaseAPIView):
|
|||
# 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
|
||||
if order_by_param == "priority"
|
||||
else priority_order[::-1]
|
||||
)
|
||||
issue_queryset = issue_queryset.annotate(
|
||||
priority_order=Case(
|
||||
|
|
@ -175,7 +189,9 @@ class IssueAPIEndpoint(WebhookMixin, BaseAPIView):
|
|||
else order_by_param
|
||||
)
|
||||
).order_by(
|
||||
"-max_values" if order_by_param.startswith("-") else "max_values"
|
||||
"-max_values"
|
||||
if order_by_param.startswith("-")
|
||||
else "max_values"
|
||||
)
|
||||
else:
|
||||
issue_queryset = issue_queryset.order_by(order_by_param)
|
||||
|
|
@ -209,7 +225,9 @@ class IssueAPIEndpoint(WebhookMixin, BaseAPIView):
|
|||
# Track the issue
|
||||
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),
|
||||
|
|
@ -220,7 +238,9 @@ class IssueAPIEndpoint(WebhookMixin, BaseAPIView):
|
|||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
def patch(self, request, slug, project_id, pk=None):
|
||||
issue = Issue.objects.get(workspace__slug=slug, project_id=project_id, pk=pk)
|
||||
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
|
||||
|
|
@ -250,7 +270,9 @@ class IssueAPIEndpoint(WebhookMixin, BaseAPIView):
|
|||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
def delete(self, request, slug, project_id, pk=None):
|
||||
issue = Issue.objects.get(workspace__slug=slug, project_id=project_id, pk=pk)
|
||||
issue = Issue.objects.get(
|
||||
workspace__slug=slug, project_id=project_id, pk=pk
|
||||
)
|
||||
current_instance = json.dumps(
|
||||
IssueSerializer(issue).data, cls=DjangoJSONEncoder
|
||||
)
|
||||
|
|
@ -297,11 +319,17 @@ class LabelAPIEndpoint(BaseAPIView):
|
|||
serializer = LabelSerializer(data=request.data)
|
||||
if serializer.is_valid():
|
||||
serializer.save(project_id=project_id)
|
||||
return Response(serializer.data, status=status.HTTP_201_CREATED)
|
||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
return Response(
|
||||
serializer.data, status=status.HTTP_201_CREATED
|
||||
)
|
||||
return Response(
|
||||
serializer.errors, status=status.HTTP_400_BAD_REQUEST
|
||||
)
|
||||
except IntegrityError:
|
||||
return Response(
|
||||
{"error": "Label with the same name already exists in the project"},
|
||||
{
|
||||
"error": "Label with the same name already exists in the project"
|
||||
},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
||||
|
|
@ -318,7 +346,11 @@ class LabelAPIEndpoint(BaseAPIView):
|
|||
).data,
|
||||
)
|
||||
label = self.get_queryset().get(pk=pk)
|
||||
serializer = LabelSerializer(label, fields=self.fields, expand=self.expand,)
|
||||
serializer = LabelSerializer(
|
||||
label,
|
||||
fields=self.fields,
|
||||
expand=self.expand,
|
||||
)
|
||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||
|
||||
def patch(self, request, slug, project_id, pk=None):
|
||||
|
|
@ -328,7 +360,6 @@ class LabelAPIEndpoint(BaseAPIView):
|
|||
serializer.save()
|
||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
|
||||
def delete(self, request, slug, project_id, pk=None):
|
||||
label = self.get_queryset().get(pk=pk)
|
||||
|
|
@ -395,7 +426,9 @@ class IssueLinkAPIEndpoint(BaseAPIView):
|
|||
)
|
||||
issue_activity.delay(
|
||||
type="link.activity.created",
|
||||
requested_data=json.dumps(serializer.data, cls=DjangoJSONEncoder),
|
||||
requested_data=json.dumps(
|
||||
serializer.data, cls=DjangoJSONEncoder
|
||||
),
|
||||
actor_id=str(self.request.user.id),
|
||||
issue_id=str(self.kwargs.get("issue_id")),
|
||||
project_id=str(self.kwargs.get("project_id")),
|
||||
|
|
@ -407,14 +440,19 @@ class IssueLinkAPIEndpoint(BaseAPIView):
|
|||
|
||||
def patch(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
|
||||
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,
|
||||
)
|
||||
serializer = IssueLinkSerializer(issue_link, data=request.data, partial=True)
|
||||
serializer = IssueLinkSerializer(
|
||||
issue_link, data=request.data, partial=True
|
||||
)
|
||||
if serializer.is_valid():
|
||||
serializer.save()
|
||||
issue_activity.delay(
|
||||
|
|
@ -431,7 +469,10 @@ class IssueLinkAPIEndpoint(BaseAPIView):
|
|||
|
||||
def delete(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
|
||||
workspace__slug=slug,
|
||||
project_id=project_id,
|
||||
issue_id=issue_id,
|
||||
pk=pk,
|
||||
)
|
||||
current_instance = json.dumps(
|
||||
IssueLinkSerializer(issue_link).data,
|
||||
|
|
@ -466,7 +507,9 @@ class IssueCommentAPIEndpoint(WebhookMixin, BaseAPIView):
|
|||
|
||||
def get_queryset(self):
|
||||
return (
|
||||
IssueComment.objects.filter(workspace__slug=self.kwargs.get("slug"))
|
||||
IssueComment.objects.filter(
|
||||
workspace__slug=self.kwargs.get("slug")
|
||||
)
|
||||
.filter(project_id=self.kwargs.get("project_id"))
|
||||
.filter(issue_id=self.kwargs.get("issue_id"))
|
||||
.filter(project__project_projectmember__member=self.request.user)
|
||||
|
|
@ -518,7 +561,9 @@ class IssueCommentAPIEndpoint(WebhookMixin, BaseAPIView):
|
|||
)
|
||||
issue_activity.delay(
|
||||
type="comment.activity.created",
|
||||
requested_data=json.dumps(serializer.data, cls=DjangoJSONEncoder),
|
||||
requested_data=json.dumps(
|
||||
serializer.data, cls=DjangoJSONEncoder
|
||||
),
|
||||
actor_id=str(self.request.user.id),
|
||||
issue_id=str(self.kwargs.get("issue_id")),
|
||||
project_id=str(self.kwargs.get("project_id")),
|
||||
|
|
@ -530,7 +575,10 @@ class IssueCommentAPIEndpoint(WebhookMixin, BaseAPIView):
|
|||
|
||||
def patch(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
|
||||
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(
|
||||
|
|
@ -556,7 +604,10 @@ class IssueCommentAPIEndpoint(WebhookMixin, BaseAPIView):
|
|||
|
||||
def delete(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
|
||||
workspace__slug=slug,
|
||||
project_id=project_id,
|
||||
issue_id=issue_id,
|
||||
pk=pk,
|
||||
)
|
||||
current_instance = json.dumps(
|
||||
IssueCommentSerializer(issue_comment).data,
|
||||
|
|
@ -591,7 +642,7 @@ class IssueActivityAPIEndpoint(BaseAPIView):
|
|||
)
|
||||
.select_related("actor", "workspace", "issue", "project")
|
||||
).order_by(request.GET.get("order_by", "created_at"))
|
||||
|
||||
|
||||
if pk:
|
||||
issue_activities = issue_activities.get(pk=pk)
|
||||
serializer = IssueActivitySerializer(issue_activities)
|
||||
|
|
|
|||
|
|
@ -55,7 +55,9 @@ class ModuleAPIEndpoint(WebhookMixin, BaseAPIView):
|
|||
.prefetch_related(
|
||||
Prefetch(
|
||||
"link_module",
|
||||
queryset=ModuleLink.objects.select_related("module", "created_by"),
|
||||
queryset=ModuleLink.objects.select_related(
|
||||
"module", "created_by"
|
||||
),
|
||||
)
|
||||
)
|
||||
.annotate(
|
||||
|
|
@ -122,17 +124,30 @@ class ModuleAPIEndpoint(WebhookMixin, BaseAPIView):
|
|||
|
||||
def post(self, request, slug, project_id):
|
||||
project = Project.objects.get(pk=project_id, workspace__slug=slug)
|
||||
serializer = ModuleSerializer(data=request.data, context={"project_id": project_id, "workspace_id": project.workspace_id})
|
||||
serializer = ModuleSerializer(
|
||||
data=request.data,
|
||||
context={
|
||||
"project_id": project_id,
|
||||
"workspace_id": project.workspace_id,
|
||||
},
|
||||
)
|
||||
if serializer.is_valid():
|
||||
serializer.save()
|
||||
module = Module.objects.get(pk=serializer.data["id"])
|
||||
serializer = ModuleSerializer(module)
|
||||
return Response(serializer.data, status=status.HTTP_201_CREATED)
|
||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
|
||||
def patch(self, request, slug, project_id, pk):
|
||||
module = Module.objects.get(pk=pk, project_id=project_id, workspace__slug=slug)
|
||||
serializer = ModuleSerializer(module, data=request.data, context={"project_id": project_id}, partial=True)
|
||||
module = Module.objects.get(
|
||||
pk=pk, project_id=project_id, workspace__slug=slug
|
||||
)
|
||||
serializer = ModuleSerializer(
|
||||
module,
|
||||
data=request.data,
|
||||
context={"project_id": project_id},
|
||||
partial=True,
|
||||
)
|
||||
if serializer.is_valid():
|
||||
serializer.save()
|
||||
return Response(serializer.data, status=status.HTTP_201_CREATED)
|
||||
|
|
@ -162,9 +177,13 @@ class ModuleAPIEndpoint(WebhookMixin, BaseAPIView):
|
|||
)
|
||||
|
||||
def delete(self, request, slug, project_id, pk):
|
||||
module = Module.objects.get(workspace__slug=slug, project_id=project_id, pk=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)
|
||||
ModuleIssue.objects.filter(module_id=pk).values_list(
|
||||
"issue", flat=True
|
||||
)
|
||||
)
|
||||
issue_activity.delay(
|
||||
type="module.activity.deleted",
|
||||
|
|
@ -204,7 +223,9 @@ class ModuleIssueAPIEndpoint(WebhookMixin, BaseAPIView):
|
|||
def get_queryset(self):
|
||||
return (
|
||||
ModuleIssue.objects.annotate(
|
||||
sub_issues_count=Issue.issue_objects.filter(parent=OuterRef("issue"))
|
||||
sub_issues_count=Issue.issue_objects.filter(
|
||||
parent=OuterRef("issue")
|
||||
)
|
||||
.order_by()
|
||||
.annotate(count=Func(F("id"), function="Count"))
|
||||
.values("count")
|
||||
|
|
@ -228,7 +249,9 @@ class ModuleIssueAPIEndpoint(WebhookMixin, BaseAPIView):
|
|||
issues = (
|
||||
Issue.issue_objects.filter(issue_module__module_id=module_id)
|
||||
.annotate(
|
||||
sub_issues_count=Issue.issue_objects.filter(parent=OuterRef("id"))
|
||||
sub_issues_count=Issue.issue_objects.filter(
|
||||
parent=OuterRef("id")
|
||||
)
|
||||
.order_by()
|
||||
.annotate(count=Func(F("id"), function="Count"))
|
||||
.values("count")
|
||||
|
|
@ -250,7 +273,9 @@ class ModuleIssueAPIEndpoint(WebhookMixin, BaseAPIView):
|
|||
.values("count")
|
||||
)
|
||||
.annotate(
|
||||
attachment_count=IssueAttachment.objects.filter(issue=OuterRef("id"))
|
||||
attachment_count=IssueAttachment.objects.filter(
|
||||
issue=OuterRef("id")
|
||||
)
|
||||
.order_by()
|
||||
.annotate(count=Func(F("id"), function="Count"))
|
||||
.values("count")
|
||||
|
|
@ -271,7 +296,8 @@ class ModuleIssueAPIEndpoint(WebhookMixin, BaseAPIView):
|
|||
issues = request.data.get("issues", [])
|
||||
if not len(issues):
|
||||
return Response(
|
||||
{"error": "Issues are required"}, status=status.HTTP_400_BAD_REQUEST
|
||||
{"error": "Issues are required"},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
module = Module.objects.get(
|
||||
workspace__slug=slug, project_id=project_id, pk=module_id
|
||||
|
|
@ -354,7 +380,10 @@ class ModuleIssueAPIEndpoint(WebhookMixin, BaseAPIView):
|
|||
|
||||
def delete(self, request, slug, project_id, module_id, issue_id):
|
||||
module_issue = ModuleIssue.objects.get(
|
||||
workspace__slug=slug, project_id=project_id, module_id=module_id, issue_id=issue_id
|
||||
workspace__slug=slug,
|
||||
project_id=project_id,
|
||||
module_id=module_id,
|
||||
issue_id=issue_id,
|
||||
)
|
||||
module_issue.delete()
|
||||
issue_activity.delay(
|
||||
|
|
@ -371,4 +400,4 @@ class ModuleIssueAPIEndpoint(WebhookMixin, BaseAPIView):
|
|||
current_instance=None,
|
||||
epoch=int(timezone.now().timestamp()),
|
||||
)
|
||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||
|
|
|
|||
|
|
@ -39,9 +39,15 @@ class ProjectAPIEndpoint(WebhookMixin, BaseAPIView):
|
|||
def get_queryset(self):
|
||||
return (
|
||||
Project.objects.filter(workspace__slug=self.kwargs.get("slug"))
|
||||
.filter(Q(project_projectmember__member=self.request.user) | Q(network=2))
|
||||
.filter(
|
||||
Q(project_projectmember__member=self.request.user)
|
||||
| Q(network=2)
|
||||
)
|
||||
.select_related(
|
||||
"workspace", "workspace__owner", "default_assignee", "project_lead"
|
||||
"workspace",
|
||||
"workspace__owner",
|
||||
"default_assignee",
|
||||
"project_lead",
|
||||
)
|
||||
.annotate(
|
||||
is_member=Exists(
|
||||
|
|
@ -120,11 +126,18 @@ class ProjectAPIEndpoint(WebhookMixin, BaseAPIView):
|
|||
request=request,
|
||||
queryset=(projects),
|
||||
on_results=lambda projects: ProjectSerializer(
|
||||
projects, many=True, fields=self.fields, expand=self.expand,
|
||||
projects,
|
||||
many=True,
|
||||
fields=self.fields,
|
||||
expand=self.expand,
|
||||
).data,
|
||||
)
|
||||
project = self.get_queryset().get(workspace__slug=slug, pk=project_id)
|
||||
serializer = ProjectSerializer(project, fields=self.fields, expand=self.expand,)
|
||||
serializer = ProjectSerializer(
|
||||
project,
|
||||
fields=self.fields,
|
||||
expand=self.expand,
|
||||
)
|
||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||
|
||||
def post(self, request, slug):
|
||||
|
|
@ -138,7 +151,9 @@ class ProjectAPIEndpoint(WebhookMixin, BaseAPIView):
|
|||
|
||||
# Add the user as Administrator to the project
|
||||
project_member = ProjectMember.objects.create(
|
||||
project_id=serializer.data["id"], member=request.user, role=20
|
||||
project_id=serializer.data["id"],
|
||||
member=request.user,
|
||||
role=20,
|
||||
)
|
||||
# Also create the issue property for the user
|
||||
_ = IssueProperty.objects.create(
|
||||
|
|
@ -211,9 +226,15 @@ class ProjectAPIEndpoint(WebhookMixin, BaseAPIView):
|
|||
]
|
||||
)
|
||||
|
||||
project = self.get_queryset().filter(pk=serializer.data["id"]).first()
|
||||
project = (
|
||||
self.get_queryset()
|
||||
.filter(pk=serializer.data["id"])
|
||||
.first()
|
||||
)
|
||||
serializer = ProjectSerializer(project)
|
||||
return Response(serializer.data, status=status.HTTP_201_CREATED)
|
||||
return Response(
|
||||
serializer.data, status=status.HTTP_201_CREATED
|
||||
)
|
||||
return Response(
|
||||
serializer.errors,
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
|
|
@ -226,7 +247,8 @@ class ProjectAPIEndpoint(WebhookMixin, BaseAPIView):
|
|||
)
|
||||
except Workspace.DoesNotExist as e:
|
||||
return Response(
|
||||
{"error": "Workspace does not exist"}, status=status.HTTP_404_NOT_FOUND
|
||||
{"error": "Workspace does not exist"},
|
||||
status=status.HTTP_404_NOT_FOUND,
|
||||
)
|
||||
except ValidationError as e:
|
||||
return Response(
|
||||
|
|
@ -250,7 +272,9 @@ class ProjectAPIEndpoint(WebhookMixin, BaseAPIView):
|
|||
serializer.save()
|
||||
if serializer.data["inbox_view"]:
|
||||
Inbox.objects.get_or_create(
|
||||
name=f"{project.name} Inbox", project=project, is_default=True
|
||||
name=f"{project.name} Inbox",
|
||||
project=project,
|
||||
is_default=True,
|
||||
)
|
||||
|
||||
# Create the triage state in Backlog group
|
||||
|
|
@ -262,10 +286,16 @@ class ProjectAPIEndpoint(WebhookMixin, BaseAPIView):
|
|||
color="#ff7700",
|
||||
)
|
||||
|
||||
project = self.get_queryset().filter(pk=serializer.data["id"]).first()
|
||||
project = (
|
||||
self.get_queryset()
|
||||
.filter(pk=serializer.data["id"])
|
||||
.first()
|
||||
)
|
||||
serializer = ProjectSerializer(project)
|
||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
return Response(
|
||||
serializer.errors, status=status.HTTP_400_BAD_REQUEST
|
||||
)
|
||||
except IntegrityError as e:
|
||||
if "already exists" in str(e):
|
||||
return Response(
|
||||
|
|
@ -274,7 +304,8 @@ class ProjectAPIEndpoint(WebhookMixin, BaseAPIView):
|
|||
)
|
||||
except (Project.DoesNotExist, Workspace.DoesNotExist):
|
||||
return Response(
|
||||
{"error": "Project does not exist"}, status=status.HTTP_404_NOT_FOUND
|
||||
{"error": "Project does not exist"},
|
||||
status=status.HTTP_404_NOT_FOUND,
|
||||
)
|
||||
except ValidationError as e:
|
||||
return Response(
|
||||
|
|
@ -285,4 +316,4 @@ class ProjectAPIEndpoint(WebhookMixin, BaseAPIView):
|
|||
def delete(self, request, slug, project_id):
|
||||
project = Project.objects.get(pk=project_id, workspace__slug=slug)
|
||||
project.delete()
|
||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||
|
|
|
|||
|
|
@ -34,7 +34,9 @@ class StateAPIEndpoint(BaseAPIView):
|
|||
)
|
||||
|
||||
def post(self, request, slug, project_id):
|
||||
serializer = StateSerializer(data=request.data, context={"project_id": project_id})
|
||||
serializer = StateSerializer(
|
||||
data=request.data, context={"project_id": project_id}
|
||||
)
|
||||
if serializer.is_valid():
|
||||
serializer.save(project_id=project_id)
|
||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||
|
|
@ -64,14 +66,19 @@ class StateAPIEndpoint(BaseAPIView):
|
|||
)
|
||||
|
||||
if state.default:
|
||||
return Response({"error": "Default state cannot be deleted"}, status=status.HTTP_400_BAD_REQUEST)
|
||||
return Response(
|
||||
{"error": "Default state cannot be deleted"},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
||||
# Check for any issues in the state
|
||||
issue_exist = Issue.issue_objects.filter(state=state_id).exists()
|
||||
|
||||
if issue_exist:
|
||||
return Response(
|
||||
{"error": "The state is not empty, only empty states can be deleted"},
|
||||
{
|
||||
"error": "The state is not empty, only empty states can be deleted"
|
||||
},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
||||
|
|
@ -79,9 +86,11 @@ class StateAPIEndpoint(BaseAPIView):
|
|||
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
def patch(self, request, slug, project_id, state_id=None):
|
||||
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():
|
||||
serializer.save()
|
||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue