Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion apps/api/plane/api/serializers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,4 +55,4 @@
)
from .invite import WorkspaceInviteSerializer
from .member import ProjectMemberSerializer
from .sticky import StickySerializer
from .sticky import StickySerializer
2 changes: 1 addition & 1 deletion apps/api/plane/api/serializers/project.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
from .base import BaseSerializer


class ProjectCreateSerializer(BaseSerializer):
class ProjectCreateSerializer(BaseSerializer):
"""
Serializer for creating projects with workspace validation.

Expand Down
2 changes: 1 addition & 1 deletion apps/api/plane/api/urls/invite.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,4 @@
# Wrap the router URLs with the workspace slug path
urlpatterns = [
path("workspaces/<str:slug>/", include(router.urls)),
]
]
97 changes: 24 additions & 73 deletions apps/api/plane/api/views/cycle.py
Original file line number Diff line number Diff line change
Expand Up @@ -195,9 +195,7 @@ def get(self, request, slug, project_id):

# Current Cycle
if cycle_view == "current":
queryset = queryset.filter(
start_date__lte=timezone.now(), end_date__gte=timezone.now()
)
queryset = queryset.filter(start_date__lte=timezone.now(), end_date__gte=timezone.now())
data = CycleSerializer(
queryset,
many=True,
Expand Down Expand Up @@ -254,9 +252,7 @@ def get(self, request, slug, project_id):

# Incomplete Cycles
if cycle_view == "incomplete":
queryset = queryset.filter(
Q(end_date__gte=timezone.now()) | Q(end_date__isnull=True)
)
queryset = queryset.filter(Q(end_date__gte=timezone.now()) | Q(end_date__isnull=True))
return self.paginate(
request=request,
queryset=(queryset),
Expand Down Expand Up @@ -302,17 +298,10 @@ def post(self, request, slug, project_id):
Create a new development cycle with specified name, description, and date range.
Supports external ID tracking for integration purposes.
"""
if (
request.data.get("start_date", None) is None
and request.data.get("end_date", None) is None
) or (
request.data.get("start_date", None) is not None
and request.data.get("end_date", None) is not None
if (request.data.get("start_date", None) is None and request.data.get("end_date", None) is None) or (
request.data.get("start_date", None) is not None and request.data.get("end_date", None) is not None
):

serializer = CycleCreateSerializer(
data=request.data, context={"request": request}
)
serializer = CycleCreateSerializer(data=request.data, context={"request": request})
if serializer.is_valid():
if (
request.data.get("external_id")
Expand Down Expand Up @@ -355,9 +344,7 @@ def post(self, request, slug, project_id):
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
else:
return Response(
{
"error": "Both start date and end date are either required or are to be null"
},
{"error": "Both start date and end date are either required or are to be null"},
status=status.HTTP_400_BAD_REQUEST,
)

Expand Down Expand Up @@ -505,9 +492,7 @@ def patch(self, request, slug, project_id, pk):
"""
cycle = Cycle.objects.get(workspace__slug=slug, project_id=project_id, pk=pk)

current_instance = json.dumps(
CycleSerializer(cycle).data, cls=DjangoJSONEncoder
)
current_instance = json.dumps(CycleSerializer(cycle).data, cls=DjangoJSONEncoder)

if cycle.archived_at:
return Response(
Expand All @@ -520,30 +505,22 @@ def patch(self, request, slug, project_id, pk):
if cycle.end_date is not None and cycle.end_date < timezone.now():
if "sort_order" in request_data:
# Can only change sort order
request_data = {
"sort_order": request_data.get("sort_order", cycle.sort_order)
}
request_data = {"sort_order": request_data.get("sort_order", cycle.sort_order)}
Copy link

Copilot AI Dec 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Variable request_data is not used.

Copilot uses AI. Check for mistakes.
else:
return Response(
{
"error": "The Cycle has already been completed so it cannot be edited"
},
{"error": "The Cycle has already been completed so it cannot be edited"},
status=status.HTTP_400_BAD_REQUEST,
)

serializer = CycleUpdateSerializer(
cycle, data=request.data, partial=True, context={"request": request}
)
serializer = CycleUpdateSerializer(cycle, data=request.data, partial=True, context={"request": request})
if serializer.is_valid():
if (
request.data.get("external_id")
and (cycle.external_id != request.data.get("external_id"))
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()
):
Expand Down Expand Up @@ -600,11 +577,7 @@ def delete(self, request, slug, project_id, pk):
status=status.HTTP_403_FORBIDDEN,
)

cycle_issues = list(
CycleIssue.objects.filter(cycle_id=self.kwargs.get("pk")).values_list(
"issue", flat=True
)
)
cycle_issues = list(CycleIssue.objects.filter(cycle_id=self.kwargs.get("pk")).values_list("issue", flat=True))

issue_activity.delay(
type="cycle.activity.deleted",
Expand All @@ -624,9 +597,7 @@ def delete(self, request, slug, project_id, pk):
# Delete the cycle
cycle.delete()
# Delete the user favorite cycle
UserFavorite.objects.filter(
entity_type="cycle", entity_identifier=pk, project_id=project_id
).delete()
UserFavorite.objects.filter(entity_type="cycle", entity_identifier=pk, project_id=project_id).delete()
return Response(status=status.HTTP_204_NO_CONTENT)


Expand Down Expand Up @@ -764,9 +735,7 @@ def get(self, request, slug, project_id):
return self.paginate(
request=request,
queryset=(self.get_queryset()),
on_results=lambda cycles: CycleSerializer(
cycles, many=True, fields=self.fields, expand=self.expand
).data,
on_results=lambda cycles: CycleSerializer(cycles, many=True, fields=self.fields, expand=self.expand).data,
)

@cycle_docs(
Expand All @@ -785,9 +754,7 @@ def post(self, request, slug, project_id, cycle_id):
Move a completed cycle to archived status for historical tracking.
Only cycles that have ended can be archived.
"""
cycle = Cycle.objects.get(
pk=cycle_id, project_id=project_id, workspace__slug=slug
)
cycle = Cycle.objects.get(pk=cycle_id, project_id=project_id, workspace__slug=slug)
if cycle.end_date >= timezone.now():
return Response(
{"error": "Only completed cycles can be archived"},
Expand Down Expand Up @@ -818,9 +785,7 @@ def delete(self, request, slug, project_id, cycle_id):
Restore an archived cycle to active status, making it available for regular use.
The cycle will reappear in active cycle lists.
"""
cycle = Cycle.objects.get(
pk=cycle_id, project_id=project_id, workspace__slug=slug
)
cycle = Cycle.objects.get(pk=cycle_id, project_id=project_id, workspace__slug=slug)
cycle.archived_at = None
cycle.save()
return Response(status=status.HTTP_204_NO_CONTENT)
Expand Down Expand Up @@ -883,9 +848,7 @@ def get(self, request, slug, project_id, cycle_id):
# List
order_by = request.GET.get("order_by", "created_at")
issues = (
Issue.issue_objects.filter(
issue_cycle__cycle_id=cycle_id, issue_cycle__deleted_at__isnull=True
)
Issue.issue_objects.filter(issue_cycle__cycle_id=cycle_id, issue_cycle__deleted_at__isnull=True)
.annotate(
sub_issues_count=Issue.issue_objects.filter(parent=OuterRef("id"))
.order_by()
Expand Down Expand Up @@ -922,9 +885,7 @@ def get(self, request, slug, project_id, cycle_id):
return self.paginate(
request=request,
queryset=(issues),
on_results=lambda issues: IssueSerializer(
issues, many=True, fields=self.fields, expand=self.expand
).data,
on_results=lambda issues: IssueSerializer(issues, many=True, fields=self.fields, expand=self.expand).data,
)

@cycle_docs(
Expand Down Expand Up @@ -958,9 +919,7 @@ def post(self, request, slug, project_id, cycle_id):
status=status.HTTP_400_BAD_REQUEST,
)

cycle = Cycle.objects.get(
workspace__slug=slug, project_id=project_id, pk=cycle_id
)
cycle = Cycle.objects.get(workspace__slug=slug, project_id=project_id, pk=cycle_id)

if cycle.end_date is not None and cycle.end_date < timezone.now():
return Response(
Expand All @@ -972,13 +931,9 @@ def post(self, request, slug, project_id, cycle_id):
)

# Get all CycleWorkItems already created
cycle_issues = list(
CycleIssue.objects.filter(~Q(cycle_id=cycle_id), issue_id__in=issues)
)
cycle_issues = list(CycleIssue.objects.filter(~Q(cycle_id=cycle_id), issue_id__in=issues))
existing_issues = [
str(cycle_issue.issue_id)
for cycle_issue in cycle_issues
if str(cycle_issue.issue_id) in issues
str(cycle_issue.issue_id) for cycle_issue in cycle_issues if str(cycle_issue.issue_id) in issues
]
new_issues = list(set(issues) - set(existing_issues))

Expand Down Expand Up @@ -1029,9 +984,7 @@ def post(self, request, slug, project_id, cycle_id):
current_instance=json.dumps(
{
"updated_cycle_issues": update_cycle_issue_activity,
"created_cycle_issues": serializers.serialize(
"json", created_records
),
"created_cycle_issues": serializers.serialize("json", created_records),
}
),
epoch=int(timezone.now().timestamp()),
Expand Down Expand Up @@ -1107,9 +1060,7 @@ def get(self, request, slug, project_id, cycle_id, issue_id):
cycle_id=cycle_id,
issue_id=issue_id,
)
serializer = CycleIssueSerializer(
cycle_issue, fields=self.fields, expand=self.expand
)
serializer = CycleIssueSerializer(cycle_issue, fields=self.fields, expand=self.expand)
return Response(serializer.data, status=status.HTTP_200_OK)

@cycle_docs(
Expand Down Expand Up @@ -1214,7 +1165,7 @@ def post(self, request, slug, project_id, cycle_id):
{"error": "New Cycle Id is required"},
status=status.HTTP_400_BAD_REQUEST,
)

old_cycle = Cycle.objects.get(
workspace__slug=slug,
project_id=project_id,
Expand Down
1 change: 0 additions & 1 deletion apps/api/plane/api/views/member.py
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,6 @@ def post(self, request, slug, project_id):

# API endpoint to get and update a project member
class ProjectMemberDetailAPIEndpoint(ProjectMemberListCreateAPIEndpoint):

@extend_schema(
operation_id="get_project_member",
summary="Get project member",
Expand Down
2 changes: 1 addition & 1 deletion apps/api/plane/app/urls/exporter.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,4 @@
ExportIssuesEndpoint.as_view(),
name="export-issues",
),
]
]
Loading
Loading