[WEB-5044] fix: ruff lint and format errors (#7868)

* fix: lint errors

* fix: file formatting

* fix: code refactor
This commit is contained in:
sriram veeraghanta 2025-09-29 19:15:32 +05:30 committed by GitHub
parent 1fb22bd252
commit 9237f568dd
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
261 changed files with 2199 additions and 6378 deletions

View file

@ -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),

View file

@ -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")

View file

@ -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,
)

View file

@ -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)

View file

@ -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},

View file

@ -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)

View file

@ -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

View file

@ -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)

View file

@ -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",

View file

@ -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()),

View file

@ -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()

View file

@ -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(

View file

@ -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."},

View file

@ -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)

View file

@ -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",
)
)

View file

@ -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)

View file

@ -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

View file

@ -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())),
),

View file

@ -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",

View file

@ -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()
):

View file

@ -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)}),

View file

@ -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),

View file

@ -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",

View file

@ -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
_ = [

View file

@ -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)

View file

@ -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)

View file

@ -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
)

View file

@ -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),

View file

@ -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)

View file

@ -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())),
)
)

View file

@ -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)

View file

@ -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")
)

View file

@ -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)

View file

@ -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)

View file

@ -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"),

View file

@ -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:

View file

@ -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(

View file

@ -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),

View file

@ -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,
)

View file

@ -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,

View file

@ -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)

View file

@ -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

View file

@ -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()),

View file

@ -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")

View file

@ -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)

View file

@ -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)

View file

@ -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(

View file

@ -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)

View file

@ -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)

View file

@ -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)

View file

@ -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")

View file

@ -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,

View file

@ -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)