forked from github/plane
[WEB-780]feat: project cycle module archive (#3990)
* dev: project cycle module archive * fix: update filter logic updated
This commit is contained in:
parent
cb4cfa1dd5
commit
2f883e4939
@ -4,6 +4,7 @@ from plane.api.views.cycle import (
|
|||||||
CycleAPIEndpoint,
|
CycleAPIEndpoint,
|
||||||
CycleIssueAPIEndpoint,
|
CycleIssueAPIEndpoint,
|
||||||
TransferCycleIssueAPIEndpoint,
|
TransferCycleIssueAPIEndpoint,
|
||||||
|
CycleArchiveUnarchiveAPIEndpoint,
|
||||||
)
|
)
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
@ -32,4 +33,14 @@ urlpatterns = [
|
|||||||
TransferCycleIssueAPIEndpoint.as_view(),
|
TransferCycleIssueAPIEndpoint.as_view(),
|
||||||
name="transfer-issues",
|
name="transfer-issues",
|
||||||
),
|
),
|
||||||
|
path(
|
||||||
|
"workspaces/<str:slug>/projects/<uuid:project_id>/cycles/<uuid:pk>/archive/",
|
||||||
|
CycleArchiveUnarchiveAPIEndpoint.as_view(),
|
||||||
|
name="cycle-archive-unarchive",
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"workspaces/<str:slug>/projects/<uuid:project_id>/archived-cycles/",
|
||||||
|
CycleArchiveUnarchiveAPIEndpoint.as_view(),
|
||||||
|
name="cycle-archive-unarchive",
|
||||||
|
),
|
||||||
]
|
]
|
||||||
|
@ -1,6 +1,10 @@
|
|||||||
from django.urls import path
|
from django.urls import path
|
||||||
|
|
||||||
from plane.api.views import ModuleAPIEndpoint, ModuleIssueAPIEndpoint
|
from plane.api.views import (
|
||||||
|
ModuleAPIEndpoint,
|
||||||
|
ModuleIssueAPIEndpoint,
|
||||||
|
ModuleArchiveUnarchiveAPIEndpoint,
|
||||||
|
)
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path(
|
path(
|
||||||
@ -23,4 +27,14 @@ urlpatterns = [
|
|||||||
ModuleIssueAPIEndpoint.as_view(),
|
ModuleIssueAPIEndpoint.as_view(),
|
||||||
name="module-issues",
|
name="module-issues",
|
||||||
),
|
),
|
||||||
|
path(
|
||||||
|
"workspaces/<str:slug>/projects/<uuid:project_id>/modules/<uuid:pk>/archive/",
|
||||||
|
ModuleArchiveUnarchiveAPIEndpoint.as_view(),
|
||||||
|
name="module-archive-unarchive",
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"workspaces/<str:slug>/projects/<uuid:project_id>/archived-modules/",
|
||||||
|
ModuleArchiveUnarchiveAPIEndpoint.as_view(),
|
||||||
|
name="module-archive-unarchive",
|
||||||
|
),
|
||||||
]
|
]
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
from django.urls import path
|
from django.urls import path
|
||||||
|
|
||||||
from plane.api.views import ProjectAPIEndpoint
|
from plane.api.views import (
|
||||||
|
ProjectAPIEndpoint,
|
||||||
|
ProjectArchiveUnarchiveAPIEndpoint,
|
||||||
|
)
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path(
|
path(
|
||||||
@ -13,4 +16,9 @@ urlpatterns = [
|
|||||||
ProjectAPIEndpoint.as_view(),
|
ProjectAPIEndpoint.as_view(),
|
||||||
name="project",
|
name="project",
|
||||||
),
|
),
|
||||||
|
path(
|
||||||
|
"workspaces/<str:slug>/projects/<uuid:project_id>/archive/",
|
||||||
|
ProjectArchiveUnarchiveAPIEndpoint.as_view(),
|
||||||
|
name="project-archive-unarchive",
|
||||||
|
),
|
||||||
]
|
]
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
from .project import ProjectAPIEndpoint
|
from .project import ProjectAPIEndpoint, ProjectArchiveUnarchiveAPIEndpoint
|
||||||
|
|
||||||
from .state import StateAPIEndpoint
|
from .state import StateAPIEndpoint
|
||||||
|
|
||||||
@ -14,8 +14,13 @@ from .cycle import (
|
|||||||
CycleAPIEndpoint,
|
CycleAPIEndpoint,
|
||||||
CycleIssueAPIEndpoint,
|
CycleIssueAPIEndpoint,
|
||||||
TransferCycleIssueAPIEndpoint,
|
TransferCycleIssueAPIEndpoint,
|
||||||
|
CycleArchiveUnarchiveAPIEndpoint,
|
||||||
)
|
)
|
||||||
|
|
||||||
from .module import ModuleAPIEndpoint, ModuleIssueAPIEndpoint
|
from .module import (
|
||||||
|
ModuleAPIEndpoint,
|
||||||
|
ModuleIssueAPIEndpoint,
|
||||||
|
ModuleArchiveUnarchiveAPIEndpoint,
|
||||||
|
)
|
||||||
|
|
||||||
from .inbox import InboxIssueAPIEndpoint
|
from .inbox import InboxIssueAPIEndpoint
|
||||||
|
@ -140,7 +140,9 @@ class CycleAPIEndpoint(WebhookMixin, BaseAPIView):
|
|||||||
|
|
||||||
def get(self, request, slug, project_id, pk=None):
|
def get(self, request, slug, project_id, pk=None):
|
||||||
if pk:
|
if pk:
|
||||||
queryset = self.get_queryset().get(pk=pk)
|
queryset = (
|
||||||
|
self.get_queryset().filter(archived_at__isnull=True).get(pk=pk)
|
||||||
|
)
|
||||||
data = CycleSerializer(
|
data = CycleSerializer(
|
||||||
queryset,
|
queryset,
|
||||||
fields=self.fields,
|
fields=self.fields,
|
||||||
@ -150,7 +152,9 @@ class CycleAPIEndpoint(WebhookMixin, BaseAPIView):
|
|||||||
data,
|
data,
|
||||||
status=status.HTTP_200_OK,
|
status=status.HTTP_200_OK,
|
||||||
)
|
)
|
||||||
queryset = self.get_queryset()
|
queryset = (
|
||||||
|
self.get_queryset().filter(archived_at__isnull=True)
|
||||||
|
)
|
||||||
cycle_view = request.GET.get("cycle_view", "all")
|
cycle_view = request.GET.get("cycle_view", "all")
|
||||||
|
|
||||||
# Current Cycle
|
# Current Cycle
|
||||||
@ -291,6 +295,11 @@ class CycleAPIEndpoint(WebhookMixin, BaseAPIView):
|
|||||||
cycle = Cycle.objects.get(
|
cycle = Cycle.objects.get(
|
||||||
workspace__slug=slug, project_id=project_id, pk=pk
|
workspace__slug=slug, project_id=project_id, pk=pk
|
||||||
)
|
)
|
||||||
|
if cycle.archived_at:
|
||||||
|
return Response(
|
||||||
|
{"error": "Archived cycle cannot be edited"},
|
||||||
|
status=status.HTTP_400_BAD_REQUEST,
|
||||||
|
)
|
||||||
|
|
||||||
request_data = request.data
|
request_data = request.data
|
||||||
|
|
||||||
@ -368,6 +377,139 @@ class CycleAPIEndpoint(WebhookMixin, BaseAPIView):
|
|||||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
|
||||||
|
class CycleArchiveUnarchiveAPIEndpoint(BaseAPIView):
|
||||||
|
|
||||||
|
permission_classes = [
|
||||||
|
ProjectEntityPermission,
|
||||||
|
]
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
return (
|
||||||
|
Cycle.objects.filter(workspace__slug=self.kwargs.get("slug"))
|
||||||
|
.filter(project_id=self.kwargs.get("project_id"))
|
||||||
|
.filter(
|
||||||
|
project__project_projectmember__member=self.request.user,
|
||||||
|
project__project_projectmember__is_active=True,
|
||||||
|
)
|
||||||
|
.filter(archived_at__isnull=False)
|
||||||
|
.select_related("project")
|
||||||
|
.select_related("workspace")
|
||||||
|
.select_related("owned_by")
|
||||||
|
.annotate(
|
||||||
|
total_issues=Count(
|
||||||
|
"issue_cycle",
|
||||||
|
filter=Q(
|
||||||
|
issue_cycle__issue__archived_at__isnull=True,
|
||||||
|
issue_cycle__issue__is_draft=False,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.annotate(
|
||||||
|
completed_issues=Count(
|
||||||
|
"issue_cycle__issue__state__group",
|
||||||
|
filter=Q(
|
||||||
|
issue_cycle__issue__state__group="completed",
|
||||||
|
issue_cycle__issue__archived_at__isnull=True,
|
||||||
|
issue_cycle__issue__is_draft=False,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.annotate(
|
||||||
|
cancelled_issues=Count(
|
||||||
|
"issue_cycle__issue__state__group",
|
||||||
|
filter=Q(
|
||||||
|
issue_cycle__issue__state__group="cancelled",
|
||||||
|
issue_cycle__issue__archived_at__isnull=True,
|
||||||
|
issue_cycle__issue__is_draft=False,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.annotate(
|
||||||
|
started_issues=Count(
|
||||||
|
"issue_cycle__issue__state__group",
|
||||||
|
filter=Q(
|
||||||
|
issue_cycle__issue__state__group="started",
|
||||||
|
issue_cycle__issue__archived_at__isnull=True,
|
||||||
|
issue_cycle__issue__is_draft=False,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.annotate(
|
||||||
|
unstarted_issues=Count(
|
||||||
|
"issue_cycle__issue__state__group",
|
||||||
|
filter=Q(
|
||||||
|
issue_cycle__issue__state__group="unstarted",
|
||||||
|
issue_cycle__issue__archived_at__isnull=True,
|
||||||
|
issue_cycle__issue__is_draft=False,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.annotate(
|
||||||
|
backlog_issues=Count(
|
||||||
|
"issue_cycle__issue__state__group",
|
||||||
|
filter=Q(
|
||||||
|
issue_cycle__issue__state__group="backlog",
|
||||||
|
issue_cycle__issue__archived_at__isnull=True,
|
||||||
|
issue_cycle__issue__is_draft=False,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.annotate(
|
||||||
|
total_estimates=Sum("issue_cycle__issue__estimate_point")
|
||||||
|
)
|
||||||
|
.annotate(
|
||||||
|
completed_estimates=Sum(
|
||||||
|
"issue_cycle__issue__estimate_point",
|
||||||
|
filter=Q(
|
||||||
|
issue_cycle__issue__state__group="completed",
|
||||||
|
issue_cycle__issue__archived_at__isnull=True,
|
||||||
|
issue_cycle__issue__is_draft=False,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.annotate(
|
||||||
|
started_estimates=Sum(
|
||||||
|
"issue_cycle__issue__estimate_point",
|
||||||
|
filter=Q(
|
||||||
|
issue_cycle__issue__state__group="started",
|
||||||
|
issue_cycle__issue__archived_at__isnull=True,
|
||||||
|
issue_cycle__issue__is_draft=False,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.order_by(self.kwargs.get("order_by", "-created_at"))
|
||||||
|
.distinct()
|
||||||
|
)
|
||||||
|
|
||||||
|
def list(self, request, slug, project_id):
|
||||||
|
return self.paginate(
|
||||||
|
request=request,
|
||||||
|
queryset=(self.get_queryset()),
|
||||||
|
on_results=lambda cycles: CycleSerializer(
|
||||||
|
cycles,
|
||||||
|
many=True,
|
||||||
|
fields=self.fields,
|
||||||
|
expand=self.expand,
|
||||||
|
).data,
|
||||||
|
)
|
||||||
|
|
||||||
|
def post(self, request, slug, project_id, pk):
|
||||||
|
cycle = Cycle.objects.get(
|
||||||
|
pk=pk, project_id=project_id, workspace__slug=slug
|
||||||
|
)
|
||||||
|
cycle.archived_at = timezone.now()
|
||||||
|
cycle.save()
|
||||||
|
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
def delete(self, request, slug, project_id, pk):
|
||||||
|
cycle = Cycle.objects.get(
|
||||||
|
pk=pk, project_id=project_id, workspace__slug=slug
|
||||||
|
)
|
||||||
|
cycle.archived_at = None
|
||||||
|
cycle.save()
|
||||||
|
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
|
||||||
class CycleIssueAPIEndpoint(WebhookMixin, BaseAPIView):
|
class CycleIssueAPIEndpoint(WebhookMixin, BaseAPIView):
|
||||||
"""
|
"""
|
||||||
This viewset automatically provides `list`, `create`,
|
This viewset automatically provides `list`, `create`,
|
||||||
|
@ -357,6 +357,7 @@ class LabelAPIEndpoint(BaseAPIView):
|
|||||||
project__project_projectmember__member=self.request.user,
|
project__project_projectmember__member=self.request.user,
|
||||||
project__project_projectmember__is_active=True,
|
project__project_projectmember__is_active=True,
|
||||||
)
|
)
|
||||||
|
.filter(project__archived_at__isnull=True)
|
||||||
.select_related("project")
|
.select_related("project")
|
||||||
.select_related("workspace")
|
.select_related("workspace")
|
||||||
.select_related("parent")
|
.select_related("parent")
|
||||||
@ -489,6 +490,7 @@ class IssueLinkAPIEndpoint(BaseAPIView):
|
|||||||
project__project_projectmember__member=self.request.user,
|
project__project_projectmember__member=self.request.user,
|
||||||
project__project_projectmember__is_active=True,
|
project__project_projectmember__is_active=True,
|
||||||
)
|
)
|
||||||
|
.filter(project__archived_at__isnull=True)
|
||||||
.order_by(self.kwargs.get("order_by", "-created_at"))
|
.order_by(self.kwargs.get("order_by", "-created_at"))
|
||||||
.distinct()
|
.distinct()
|
||||||
)
|
)
|
||||||
@ -618,6 +620,7 @@ class IssueCommentAPIEndpoint(WebhookMixin, BaseAPIView):
|
|||||||
project__project_projectmember__member=self.request.user,
|
project__project_projectmember__member=self.request.user,
|
||||||
project__project_projectmember__is_active=True,
|
project__project_projectmember__is_active=True,
|
||||||
)
|
)
|
||||||
|
.filter(project__archived_at__isnull=True)
|
||||||
.select_related("workspace", "project", "issue", "actor")
|
.select_related("workspace", "project", "issue", "actor")
|
||||||
.annotate(
|
.annotate(
|
||||||
is_member=Exists(
|
is_member=Exists(
|
||||||
@ -793,6 +796,7 @@ class IssueActivityAPIEndpoint(BaseAPIView):
|
|||||||
project__project_projectmember__member=self.request.user,
|
project__project_projectmember__member=self.request.user,
|
||||||
project__project_projectmember__is_active=True,
|
project__project_projectmember__is_active=True,
|
||||||
)
|
)
|
||||||
|
.filter(project__archived_at__isnull=True)
|
||||||
.select_related("actor", "workspace", "issue", "project")
|
.select_related("actor", "workspace", "issue", "project")
|
||||||
).order_by(request.GET.get("order_by", "created_at"))
|
).order_by(request.GET.get("order_by", "created_at"))
|
||||||
|
|
||||||
|
@ -165,6 +165,11 @@ class ModuleAPIEndpoint(WebhookMixin, BaseAPIView):
|
|||||||
module = Module.objects.get(
|
module = Module.objects.get(
|
||||||
pk=pk, project_id=project_id, workspace__slug=slug
|
pk=pk, project_id=project_id, workspace__slug=slug
|
||||||
)
|
)
|
||||||
|
if module.archived_at:
|
||||||
|
return Response(
|
||||||
|
{"error": "Archived module cannot be edited"},
|
||||||
|
status=status.HTTP_400_BAD_REQUEST,
|
||||||
|
)
|
||||||
serializer = ModuleSerializer(
|
serializer = ModuleSerializer(
|
||||||
module,
|
module,
|
||||||
data=request.data,
|
data=request.data,
|
||||||
@ -197,7 +202,9 @@ class ModuleAPIEndpoint(WebhookMixin, BaseAPIView):
|
|||||||
|
|
||||||
def get(self, request, slug, project_id, pk=None):
|
def get(self, request, slug, project_id, pk=None):
|
||||||
if pk:
|
if pk:
|
||||||
queryset = self.get_queryset().get(pk=pk)
|
queryset = (
|
||||||
|
self.get_queryset().filter(archived_at__isnull=True).get(pk=pk)
|
||||||
|
)
|
||||||
data = ModuleSerializer(
|
data = ModuleSerializer(
|
||||||
queryset,
|
queryset,
|
||||||
fields=self.fields,
|
fields=self.fields,
|
||||||
@ -209,7 +216,7 @@ class ModuleAPIEndpoint(WebhookMixin, BaseAPIView):
|
|||||||
)
|
)
|
||||||
return self.paginate(
|
return self.paginate(
|
||||||
request=request,
|
request=request,
|
||||||
queryset=(self.get_queryset()),
|
queryset=(self.get_queryset().filter(archived_at__isnull=True)),
|
||||||
on_results=lambda modules: ModuleSerializer(
|
on_results=lambda modules: ModuleSerializer(
|
||||||
modules,
|
modules,
|
||||||
many=True,
|
many=True,
|
||||||
@ -279,6 +286,7 @@ class ModuleIssueAPIEndpoint(WebhookMixin, BaseAPIView):
|
|||||||
project__project_projectmember__member=self.request.user,
|
project__project_projectmember__member=self.request.user,
|
||||||
project__project_projectmember__is_active=True,
|
project__project_projectmember__is_active=True,
|
||||||
)
|
)
|
||||||
|
.filter(project__archived_at__isnull=True)
|
||||||
.select_related("project")
|
.select_related("project")
|
||||||
.select_related("workspace")
|
.select_related("workspace")
|
||||||
.select_related("module")
|
.select_related("module")
|
||||||
@ -446,3 +454,117 @@ class ModuleIssueAPIEndpoint(WebhookMixin, BaseAPIView):
|
|||||||
epoch=int(timezone.now().timestamp()),
|
epoch=int(timezone.now().timestamp()),
|
||||||
)
|
)
|
||||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
|
||||||
|
class ModuleArchiveUnarchiveAPIEndpoint(BaseAPIView):
|
||||||
|
|
||||||
|
permission_classes = [
|
||||||
|
ProjectEntityPermission,
|
||||||
|
]
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
return (
|
||||||
|
Module.objects.filter(project_id=self.kwargs.get("project_id"))
|
||||||
|
.filter(workspace__slug=self.kwargs.get("slug"))
|
||||||
|
.filter(archived_at__isnull=False)
|
||||||
|
.select_related("project")
|
||||||
|
.select_related("workspace")
|
||||||
|
.select_related("lead")
|
||||||
|
.prefetch_related("members")
|
||||||
|
.prefetch_related(
|
||||||
|
Prefetch(
|
||||||
|
"link_module",
|
||||||
|
queryset=ModuleLink.objects.select_related(
|
||||||
|
"module", "created_by"
|
||||||
|
),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.annotate(
|
||||||
|
total_issues=Count(
|
||||||
|
"issue_module",
|
||||||
|
filter=Q(
|
||||||
|
issue_module__issue__archived_at__isnull=True,
|
||||||
|
issue_module__issue__is_draft=False,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.annotate(
|
||||||
|
completed_issues=Count(
|
||||||
|
"issue_module__issue__state__group",
|
||||||
|
filter=Q(
|
||||||
|
issue_module__issue__state__group="completed",
|
||||||
|
issue_module__issue__archived_at__isnull=True,
|
||||||
|
issue_module__issue__is_draft=False,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.annotate(
|
||||||
|
cancelled_issues=Count(
|
||||||
|
"issue_module__issue__state__group",
|
||||||
|
filter=Q(
|
||||||
|
issue_module__issue__state__group="cancelled",
|
||||||
|
issue_module__issue__archived_at__isnull=True,
|
||||||
|
issue_module__issue__is_draft=False,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.annotate(
|
||||||
|
started_issues=Count(
|
||||||
|
"issue_module__issue__state__group",
|
||||||
|
filter=Q(
|
||||||
|
issue_module__issue__state__group="started",
|
||||||
|
issue_module__issue__archived_at__isnull=True,
|
||||||
|
issue_module__issue__is_draft=False,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.annotate(
|
||||||
|
unstarted_issues=Count(
|
||||||
|
"issue_module__issue__state__group",
|
||||||
|
filter=Q(
|
||||||
|
issue_module__issue__state__group="unstarted",
|
||||||
|
issue_module__issue__archived_at__isnull=True,
|
||||||
|
issue_module__issue__is_draft=False,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.annotate(
|
||||||
|
backlog_issues=Count(
|
||||||
|
"issue_module__issue__state__group",
|
||||||
|
filter=Q(
|
||||||
|
issue_module__issue__state__group="backlog",
|
||||||
|
issue_module__issue__archived_at__isnull=True,
|
||||||
|
issue_module__issue__is_draft=False,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.order_by(self.kwargs.get("order_by", "-created_at"))
|
||||||
|
)
|
||||||
|
|
||||||
|
def list(self, request, slug, project_id):
|
||||||
|
return self.paginate(
|
||||||
|
request=request,
|
||||||
|
queryset=(self.get_queryset()),
|
||||||
|
on_results=lambda modules: ModuleSerializer(
|
||||||
|
modules,
|
||||||
|
many=True,
|
||||||
|
fields=self.fields,
|
||||||
|
expand=self.expand,
|
||||||
|
).data,
|
||||||
|
)
|
||||||
|
|
||||||
|
def post(self, request, slug, project_id, pk):
|
||||||
|
module = Module.objects.get(
|
||||||
|
pk=pk, project_id=project_id, workspace__slug=slug
|
||||||
|
)
|
||||||
|
module.archived_at = timezone.now()
|
||||||
|
module.save()
|
||||||
|
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
def delete(self, request, slug, project_id, pk):
|
||||||
|
module = Module.objects.get(
|
||||||
|
pk=pk, project_id=project_id, workspace__slug=slug
|
||||||
|
)
|
||||||
|
module.archived_at = None
|
||||||
|
module.save()
|
||||||
|
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
# Django imports
|
# Django imports
|
||||||
|
from django.utils import timezone
|
||||||
from django.db import IntegrityError
|
from django.db import IntegrityError
|
||||||
from django.db.models import Exists, OuterRef, Q, F, Func, Subquery, Prefetch
|
from django.db.models import Exists, OuterRef, Q, F, Func, Subquery, Prefetch
|
||||||
|
|
||||||
@ -39,7 +40,10 @@ class ProjectAPIEndpoint(WebhookMixin, BaseAPIView):
|
|||||||
return (
|
return (
|
||||||
Project.objects.filter(workspace__slug=self.kwargs.get("slug"))
|
Project.objects.filter(workspace__slug=self.kwargs.get("slug"))
|
||||||
.filter(
|
.filter(
|
||||||
Q(project_projectmember__member=self.request.user)
|
Q(
|
||||||
|
project_projectmember__member=self.request.user,
|
||||||
|
project_projectmember__is_active=True,
|
||||||
|
)
|
||||||
| Q(network=2)
|
| Q(network=2)
|
||||||
)
|
)
|
||||||
.select_related(
|
.select_related(
|
||||||
@ -260,6 +264,12 @@ class ProjectAPIEndpoint(WebhookMixin, BaseAPIView):
|
|||||||
workspace = Workspace.objects.get(slug=slug)
|
workspace = Workspace.objects.get(slug=slug)
|
||||||
project = Project.objects.get(pk=project_id)
|
project = Project.objects.get(pk=project_id)
|
||||||
|
|
||||||
|
if project.archived_at:
|
||||||
|
return Response(
|
||||||
|
{"error": "Archived project cannot be updated"},
|
||||||
|
status=status.HTTP_400_BAD_REQUEST,
|
||||||
|
)
|
||||||
|
|
||||||
serializer = ProjectSerializer(
|
serializer = ProjectSerializer(
|
||||||
project,
|
project,
|
||||||
data={**request.data},
|
data={**request.data},
|
||||||
@ -316,3 +326,22 @@ class ProjectAPIEndpoint(WebhookMixin, BaseAPIView):
|
|||||||
project = Project.objects.get(pk=project_id, workspace__slug=slug)
|
project = Project.objects.get(pk=project_id, workspace__slug=slug)
|
||||||
project.delete()
|
project.delete()
|
||||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
|
||||||
|
class ProjectArchiveUnarchiveAPIEndpoint(BaseAPIView):
|
||||||
|
|
||||||
|
permission_classes = [
|
||||||
|
ProjectBasePermission,
|
||||||
|
]
|
||||||
|
|
||||||
|
def post(self, request, slug, project_id):
|
||||||
|
project = Project.objects.get(pk=project_id, workspace__slug=slug)
|
||||||
|
project.archived_at = timezone.now()
|
||||||
|
project.save()
|
||||||
|
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
def delete(self, request, slug, project_id):
|
||||||
|
project = Project.objects.get(pk=project_id, workspace__slug=slug)
|
||||||
|
project.archived_at = None
|
||||||
|
project.save()
|
||||||
|
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||||
|
@ -28,6 +28,7 @@ class StateAPIEndpoint(BaseAPIView):
|
|||||||
project__project_projectmember__member=self.request.user,
|
project__project_projectmember__member=self.request.user,
|
||||||
project__project_projectmember__is_active=True,
|
project__project_projectmember__is_active=True,
|
||||||
)
|
)
|
||||||
|
.filter(project__archived_at__isnull=True)
|
||||||
.filter(~Q(name="Triage"))
|
.filter(~Q(name="Triage"))
|
||||||
.select_related("project")
|
.select_related("project")
|
||||||
.select_related("workspace")
|
.select_related("workspace")
|
||||||
|
@ -31,6 +31,7 @@ class CycleWriteSerializer(BaseSerializer):
|
|||||||
"workspace",
|
"workspace",
|
||||||
"project",
|
"project",
|
||||||
"owned_by",
|
"owned_by",
|
||||||
|
"archived_at",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@ -39,6 +39,7 @@ class ModuleWriteSerializer(BaseSerializer):
|
|||||||
"updated_by",
|
"updated_by",
|
||||||
"created_at",
|
"created_at",
|
||||||
"updated_at",
|
"updated_at",
|
||||||
|
"archived_at",
|
||||||
]
|
]
|
||||||
|
|
||||||
def to_representation(self, instance):
|
def to_representation(self, instance):
|
||||||
|
@ -8,6 +8,7 @@ from plane.app.views import (
|
|||||||
CycleFavoriteViewSet,
|
CycleFavoriteViewSet,
|
||||||
TransferCycleIssueEndpoint,
|
TransferCycleIssueEndpoint,
|
||||||
CycleUserPropertiesEndpoint,
|
CycleUserPropertiesEndpoint,
|
||||||
|
CycleArchiveUnarchiveEndpoint,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -90,4 +91,14 @@ urlpatterns = [
|
|||||||
CycleUserPropertiesEndpoint.as_view(),
|
CycleUserPropertiesEndpoint.as_view(),
|
||||||
name="cycle-user-filters",
|
name="cycle-user-filters",
|
||||||
),
|
),
|
||||||
|
path(
|
||||||
|
"workspaces/<str:slug>/projects/<uuid:project_id>/cycles/<uuid:cycle_id>/archive/",
|
||||||
|
CycleArchiveUnarchiveEndpoint.as_view(),
|
||||||
|
name="cycle-archive-unarchive",
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"workspaces/<str:slug>/projects/<uuid:project_id>/archived-cycles/",
|
||||||
|
CycleArchiveUnarchiveEndpoint.as_view(),
|
||||||
|
name="cycle-archive-unarchive",
|
||||||
|
),
|
||||||
]
|
]
|
||||||
|
@ -7,6 +7,7 @@ from plane.app.views import (
|
|||||||
ModuleLinkViewSet,
|
ModuleLinkViewSet,
|
||||||
ModuleFavoriteViewSet,
|
ModuleFavoriteViewSet,
|
||||||
ModuleUserPropertiesEndpoint,
|
ModuleUserPropertiesEndpoint,
|
||||||
|
ModuleArchiveUnarchiveEndpoint,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -110,4 +111,14 @@ urlpatterns = [
|
|||||||
ModuleUserPropertiesEndpoint.as_view(),
|
ModuleUserPropertiesEndpoint.as_view(),
|
||||||
name="cycle-user-filters",
|
name="cycle-user-filters",
|
||||||
),
|
),
|
||||||
|
path(
|
||||||
|
"workspaces/<str:slug>/projects/<uuid:project_id>/modules/<uuid:module_id>/archive/",
|
||||||
|
ModuleArchiveUnarchiveEndpoint.as_view(),
|
||||||
|
name="module-archive-unarchive",
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"workspaces/<str:slug>/projects/<uuid:project_id>/archived-modules/",
|
||||||
|
ModuleArchiveUnarchiveEndpoint.as_view(),
|
||||||
|
name="module-archive-unarchive",
|
||||||
|
),
|
||||||
]
|
]
|
||||||
|
@ -14,6 +14,7 @@ from plane.app.views import (
|
|||||||
ProjectPublicCoverImagesEndpoint,
|
ProjectPublicCoverImagesEndpoint,
|
||||||
ProjectDeployBoardViewSet,
|
ProjectDeployBoardViewSet,
|
||||||
UserProjectRolesEndpoint,
|
UserProjectRolesEndpoint,
|
||||||
|
ProjectArchiveUnarchiveEndpoint,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -175,4 +176,9 @@ urlpatterns = [
|
|||||||
),
|
),
|
||||||
name="project-deploy-board",
|
name="project-deploy-board",
|
||||||
),
|
),
|
||||||
|
path(
|
||||||
|
"workspaces/<str:slug>/projects/<uuid:project_id>/archive/",
|
||||||
|
ProjectArchiveUnarchiveEndpoint.as_view(),
|
||||||
|
name="project-archive-unarchive",
|
||||||
|
),
|
||||||
]
|
]
|
||||||
|
@ -5,6 +5,7 @@ from .project.base import (
|
|||||||
ProjectFavoritesViewSet,
|
ProjectFavoritesViewSet,
|
||||||
ProjectPublicCoverImagesEndpoint,
|
ProjectPublicCoverImagesEndpoint,
|
||||||
ProjectDeployBoardViewSet,
|
ProjectDeployBoardViewSet,
|
||||||
|
ProjectArchiveUnarchiveEndpoint,
|
||||||
)
|
)
|
||||||
|
|
||||||
from .project.invite import (
|
from .project.invite import (
|
||||||
@ -90,6 +91,7 @@ from .cycle.base import (
|
|||||||
CycleDateCheckEndpoint,
|
CycleDateCheckEndpoint,
|
||||||
CycleFavoriteViewSet,
|
CycleFavoriteViewSet,
|
||||||
TransferCycleIssueEndpoint,
|
TransferCycleIssueEndpoint,
|
||||||
|
CycleArchiveUnarchiveEndpoint,
|
||||||
CycleUserPropertiesEndpoint,
|
CycleUserPropertiesEndpoint,
|
||||||
)
|
)
|
||||||
from .cycle.issue import (
|
from .cycle.issue import (
|
||||||
@ -168,6 +170,7 @@ from .module.base import (
|
|||||||
ModuleViewSet,
|
ModuleViewSet,
|
||||||
ModuleLinkViewSet,
|
ModuleLinkViewSet,
|
||||||
ModuleFavoriteViewSet,
|
ModuleFavoriteViewSet,
|
||||||
|
ModuleArchiveUnarchiveEndpoint,
|
||||||
ModuleUserPropertiesEndpoint,
|
ModuleUserPropertiesEndpoint,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -80,6 +80,7 @@ class CycleViewSet(WebhookMixin, BaseViewSet):
|
|||||||
project__project_projectmember__member=self.request.user,
|
project__project_projectmember__member=self.request.user,
|
||||||
project__project_projectmember__is_active=True,
|
project__project_projectmember__is_active=True,
|
||||||
)
|
)
|
||||||
|
.filter(project__archived_at__isnull=True)
|
||||||
.select_related("project", "workspace", "owned_by")
|
.select_related("project", "workspace", "owned_by")
|
||||||
.prefetch_related(
|
.prefetch_related(
|
||||||
Prefetch(
|
Prefetch(
|
||||||
@ -184,13 +185,17 @@ class CycleViewSet(WebhookMixin, BaseViewSet):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def list(self, request, slug, project_id):
|
def list(self, request, slug, project_id):
|
||||||
queryset = self.get_queryset().annotate(
|
queryset = (
|
||||||
total_issues=Count(
|
self.get_queryset()
|
||||||
"issue_cycle",
|
.filter(archived_at__isnull=True)
|
||||||
filter=Q(
|
.annotate(
|
||||||
issue_cycle__issue__archived_at__isnull=True,
|
total_issues=Count(
|
||||||
issue_cycle__issue__is_draft=False,
|
"issue_cycle",
|
||||||
),
|
filter=Q(
|
||||||
|
issue_cycle__issue__archived_at__isnull=True,
|
||||||
|
issue_cycle__issue__is_draft=False,
|
||||||
|
),
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
cycle_view = request.GET.get("cycle_view", "all")
|
cycle_view = request.GET.get("cycle_view", "all")
|
||||||
@ -420,6 +425,11 @@ class CycleViewSet(WebhookMixin, BaseViewSet):
|
|||||||
workspace__slug=slug, project_id=project_id, pk=pk
|
workspace__slug=slug, project_id=project_id, pk=pk
|
||||||
)
|
)
|
||||||
cycle = queryset.first()
|
cycle = queryset.first()
|
||||||
|
if cycle.archived_at:
|
||||||
|
return Response(
|
||||||
|
{"error": "Archived cycle cannot be updated"},
|
||||||
|
status=status.HTTP_400_BAD_REQUEST,
|
||||||
|
)
|
||||||
request_data = request.data
|
request_data = request.data
|
||||||
|
|
||||||
if (
|
if (
|
||||||
@ -478,6 +488,7 @@ class CycleViewSet(WebhookMixin, BaseViewSet):
|
|||||||
def retrieve(self, request, slug, project_id, pk):
|
def retrieve(self, request, slug, project_id, pk):
|
||||||
queryset = (
|
queryset = (
|
||||||
self.get_queryset()
|
self.get_queryset()
|
||||||
|
.filter(archived_at__isnull=True)
|
||||||
.filter(pk=pk)
|
.filter(pk=pk)
|
||||||
.annotate(
|
.annotate(
|
||||||
total_issues=Count(
|
total_issues=Count(
|
||||||
@ -682,6 +693,192 @@ class CycleViewSet(WebhookMixin, BaseViewSet):
|
|||||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
|
||||||
|
class CycleArchiveUnarchiveEndpoint(BaseAPIView):
|
||||||
|
|
||||||
|
permission_classes = [
|
||||||
|
ProjectEntityPermission,
|
||||||
|
]
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
favorite_subquery = CycleFavorite.objects.filter(
|
||||||
|
user=self.request.user,
|
||||||
|
cycle_id=OuterRef("pk"),
|
||||||
|
project_id=self.kwargs.get("project_id"),
|
||||||
|
workspace__slug=self.kwargs.get("slug"),
|
||||||
|
)
|
||||||
|
return self.filter_queryset(
|
||||||
|
super()
|
||||||
|
.get_queryset()
|
||||||
|
.filter(workspace__slug=self.kwargs.get("slug"))
|
||||||
|
.filter(project_id=self.kwargs.get("project_id"))
|
||||||
|
.filter(archived_at__isnull=False)
|
||||||
|
.filter(
|
||||||
|
project__project_projectmember__member=self.request.user,
|
||||||
|
project__project_projectmember__is_active=True,
|
||||||
|
)
|
||||||
|
.filter(project__archived_at__isnull=True)
|
||||||
|
.select_related("project", "workspace", "owned_by")
|
||||||
|
.prefetch_related(
|
||||||
|
Prefetch(
|
||||||
|
"issue_cycle__issue__assignees",
|
||||||
|
queryset=User.objects.only(
|
||||||
|
"avatar", "first_name", "id"
|
||||||
|
).distinct(),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.prefetch_related(
|
||||||
|
Prefetch(
|
||||||
|
"issue_cycle__issue__labels",
|
||||||
|
queryset=Label.objects.only(
|
||||||
|
"name", "color", "id"
|
||||||
|
).distinct(),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.annotate(is_favorite=Exists(favorite_subquery))
|
||||||
|
.annotate(
|
||||||
|
completed_issues=Count(
|
||||||
|
"issue_cycle__issue__state__group",
|
||||||
|
filter=Q(
|
||||||
|
issue_cycle__issue__state__group="completed",
|
||||||
|
issue_cycle__issue__archived_at__isnull=True,
|
||||||
|
issue_cycle__issue__is_draft=False,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.annotate(
|
||||||
|
cancelled_issues=Count(
|
||||||
|
"issue_cycle__issue__state__group",
|
||||||
|
filter=Q(
|
||||||
|
issue_cycle__issue__state__group="cancelled",
|
||||||
|
issue_cycle__issue__archived_at__isnull=True,
|
||||||
|
issue_cycle__issue__is_draft=False,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.annotate(
|
||||||
|
started_issues=Count(
|
||||||
|
"issue_cycle__issue__state__group",
|
||||||
|
filter=Q(
|
||||||
|
issue_cycle__issue__state__group="started",
|
||||||
|
issue_cycle__issue__archived_at__isnull=True,
|
||||||
|
issue_cycle__issue__is_draft=False,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.annotate(
|
||||||
|
unstarted_issues=Count(
|
||||||
|
"issue_cycle__issue__state__group",
|
||||||
|
filter=Q(
|
||||||
|
issue_cycle__issue__state__group="unstarted",
|
||||||
|
issue_cycle__issue__archived_at__isnull=True,
|
||||||
|
issue_cycle__issue__is_draft=False,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.annotate(
|
||||||
|
backlog_issues=Count(
|
||||||
|
"issue_cycle__issue__state__group",
|
||||||
|
filter=Q(
|
||||||
|
issue_cycle__issue__state__group="backlog",
|
||||||
|
issue_cycle__issue__archived_at__isnull=True,
|
||||||
|
issue_cycle__issue__is_draft=False,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.annotate(
|
||||||
|
status=Case(
|
||||||
|
When(
|
||||||
|
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(
|
||||||
|
Q(start_date__isnull=True) & Q(end_date__isnull=True),
|
||||||
|
then=Value("DRAFT"),
|
||||||
|
),
|
||||||
|
default=Value("DRAFT"),
|
||||||
|
output_field=CharField(),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.annotate(
|
||||||
|
assignee_ids=Coalesce(
|
||||||
|
ArrayAgg(
|
||||||
|
"issue_cycle__issue__assignees__id",
|
||||||
|
distinct=True,
|
||||||
|
filter=~Q(
|
||||||
|
issue_cycle__issue__assignees__id__isnull=True
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Value([], output_field=ArrayField(UUIDField())),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.order_by("-is_favorite", "name")
|
||||||
|
.distinct()
|
||||||
|
)
|
||||||
|
|
||||||
|
def list(self, request, slug, project_id):
|
||||||
|
queryset = (
|
||||||
|
self.get_queryset()
|
||||||
|
.annotate(
|
||||||
|
total_issues=Count(
|
||||||
|
"issue_cycle",
|
||||||
|
filter=Q(
|
||||||
|
issue_cycle__issue__archived_at__isnull=True,
|
||||||
|
issue_cycle__issue__is_draft=False,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.values(
|
||||||
|
# necessary fields
|
||||||
|
"id",
|
||||||
|
"workspace_id",
|
||||||
|
"project_id",
|
||||||
|
# model fields
|
||||||
|
"name",
|
||||||
|
"description",
|
||||||
|
"start_date",
|
||||||
|
"end_date",
|
||||||
|
"owned_by_id",
|
||||||
|
"view_props",
|
||||||
|
"sort_order",
|
||||||
|
"external_source",
|
||||||
|
"external_id",
|
||||||
|
"progress_snapshot",
|
||||||
|
# meta fields
|
||||||
|
"total_issues",
|
||||||
|
"is_favorite",
|
||||||
|
"cancelled_issues",
|
||||||
|
"completed_issues",
|
||||||
|
"started_issues",
|
||||||
|
"unstarted_issues",
|
||||||
|
"backlog_issues",
|
||||||
|
"assignee_ids",
|
||||||
|
"status",
|
||||||
|
)
|
||||||
|
).order_by("-is_favorite", "-created_at")
|
||||||
|
return Response(queryset, status=status.HTTP_200_OK)
|
||||||
|
|
||||||
|
def post(self, request, slug, project_id, cycle_id):
|
||||||
|
cycle = Cycle.objects.get(
|
||||||
|
pk=cycle_id, project_id=project_id, workspace__slug=slug
|
||||||
|
)
|
||||||
|
cycle.archived_at = timezone.now()
|
||||||
|
cycle.save()
|
||||||
|
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
def delete(self, request, slug, project_id, cycle_id):
|
||||||
|
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)
|
||||||
|
|
||||||
|
|
||||||
class CycleDateCheckEndpoint(BaseAPIView):
|
class CycleDateCheckEndpoint(BaseAPIView):
|
||||||
permission_classes = [
|
permission_classes = [
|
||||||
ProjectEntityPermission,
|
ProjectEntityPermission,
|
||||||
|
@ -74,6 +74,7 @@ class CycleIssueViewSet(WebhookMixin, BaseViewSet):
|
|||||||
project__project_projectmember__member=self.request.user,
|
project__project_projectmember__member=self.request.user,
|
||||||
project__project_projectmember__is_active=True,
|
project__project_projectmember__is_active=True,
|
||||||
)
|
)
|
||||||
|
.filter(project__archived_at__isnull=True)
|
||||||
.filter(cycle_id=self.kwargs.get("cycle_id"))
|
.filter(cycle_id=self.kwargs.get("cycle_id"))
|
||||||
.select_related("project")
|
.select_related("project")
|
||||||
.select_related("workspace")
|
.select_related("workspace")
|
||||||
|
@ -471,6 +471,7 @@ def dashboard_recent_activity(self, request, slug):
|
|||||||
workspace__slug=slug,
|
workspace__slug=slug,
|
||||||
project__project_projectmember__member=request.user,
|
project__project_projectmember__member=request.user,
|
||||||
project__project_projectmember__is_active=True,
|
project__project_projectmember__is_active=True,
|
||||||
|
project__archived_at__isnull=True,
|
||||||
actor=request.user,
|
actor=request.user,
|
||||||
).select_related("actor", "workspace", "issue", "project")[:8]
|
).select_related("actor", "workspace", "issue", "project")[:8]
|
||||||
|
|
||||||
@ -486,6 +487,7 @@ def dashboard_recent_projects(self, request, slug):
|
|||||||
workspace__slug=slug,
|
workspace__slug=slug,
|
||||||
project__project_projectmember__member=request.user,
|
project__project_projectmember__member=request.user,
|
||||||
project__project_projectmember__is_active=True,
|
project__project_projectmember__is_active=True,
|
||||||
|
project__archived_at__isnull=True,
|
||||||
actor=request.user,
|
actor=request.user,
|
||||||
)
|
)
|
||||||
.values_list("project_id", flat=True)
|
.values_list("project_id", flat=True)
|
||||||
@ -500,6 +502,7 @@ def dashboard_recent_projects(self, request, slug):
|
|||||||
additional_projects = Project.objects.filter(
|
additional_projects = Project.objects.filter(
|
||||||
project_projectmember__member=request.user,
|
project_projectmember__member=request.user,
|
||||||
project_projectmember__is_active=True,
|
project_projectmember__is_active=True,
|
||||||
|
archived_at__isnull=True,
|
||||||
workspace__slug=slug,
|
workspace__slug=slug,
|
||||||
).exclude(id__in=unique_project_ids)
|
).exclude(id__in=unique_project_ids)
|
||||||
|
|
||||||
@ -522,6 +525,7 @@ def dashboard_recent_collaborators(self, request, slug):
|
|||||||
actor=OuterRef("member"),
|
actor=OuterRef("member"),
|
||||||
project__project_projectmember__member=request.user,
|
project__project_projectmember__member=request.user,
|
||||||
project__project_projectmember__is_active=True,
|
project__project_projectmember__is_active=True,
|
||||||
|
project__archived_at__isnull=True,
|
||||||
)
|
)
|
||||||
.values("actor")
|
.values("actor")
|
||||||
.annotate(num_activities=Count("pk"))
|
.annotate(num_activities=Count("pk"))
|
||||||
@ -534,6 +538,7 @@ def dashboard_recent_collaborators(self, request, slug):
|
|||||||
workspace__slug=slug,
|
workspace__slug=slug,
|
||||||
project__project_projectmember__member=request.user,
|
project__project_projectmember__member=request.user,
|
||||||
project__project_projectmember__is_active=True,
|
project__project_projectmember__is_active=True,
|
||||||
|
project__archived_at__isnull=True,
|
||||||
)
|
)
|
||||||
.annotate(
|
.annotate(
|
||||||
num_activities=Coalesce(
|
num_activities=Coalesce(
|
||||||
|
@ -29,7 +29,10 @@ class ExportIssuesEndpoint(BaseAPIView):
|
|||||||
if provider in ["csv", "xlsx", "json"]:
|
if provider in ["csv", "xlsx", "json"]:
|
||||||
if not project_ids:
|
if not project_ids:
|
||||||
project_ids = Project.objects.filter(
|
project_ids = Project.objects.filter(
|
||||||
workspace__slug=slug
|
workspace__slug=slug,
|
||||||
|
project_projectmember__member=request.user,
|
||||||
|
project_projectmember__is_active=True,
|
||||||
|
archived_at__isnull=True,
|
||||||
).values_list("id", flat=True)
|
).values_list("id", flat=True)
|
||||||
project_ids = [str(project_id) for project_id in project_ids]
|
project_ids = [str(project_id) for project_id in project_ids]
|
||||||
|
|
||||||
|
@ -44,6 +44,7 @@ class IssueActivityEndpoint(BaseAPIView):
|
|||||||
~Q(field__in=["comment", "vote", "reaction", "draft"]),
|
~Q(field__in=["comment", "vote", "reaction", "draft"]),
|
||||||
project__project_projectmember__member=self.request.user,
|
project__project_projectmember__member=self.request.user,
|
||||||
project__project_projectmember__is_active=True,
|
project__project_projectmember__is_active=True,
|
||||||
|
project__archived_at__isnull=True,
|
||||||
workspace__slug=slug,
|
workspace__slug=slug,
|
||||||
)
|
)
|
||||||
.filter(**filters)
|
.filter(**filters)
|
||||||
@ -54,6 +55,7 @@ class IssueActivityEndpoint(BaseAPIView):
|
|||||||
.filter(
|
.filter(
|
||||||
project__project_projectmember__member=self.request.user,
|
project__project_projectmember__member=self.request.user,
|
||||||
project__project_projectmember__is_active=True,
|
project__project_projectmember__is_active=True,
|
||||||
|
project__archived_at__isnull=True,
|
||||||
workspace__slug=slug,
|
workspace__slug=slug,
|
||||||
)
|
)
|
||||||
.filter(**filters)
|
.filter(**filters)
|
||||||
|
@ -48,6 +48,7 @@ class IssueCommentViewSet(WebhookMixin, BaseViewSet):
|
|||||||
.filter(
|
.filter(
|
||||||
project__project_projectmember__member=self.request.user,
|
project__project_projectmember__member=self.request.user,
|
||||||
project__project_projectmember__is_active=True,
|
project__project_projectmember__is_active=True,
|
||||||
|
project__archived_at__isnull=True,
|
||||||
)
|
)
|
||||||
.select_related("project")
|
.select_related("project")
|
||||||
.select_related("workspace")
|
.select_related("workspace")
|
||||||
@ -163,6 +164,7 @@ class CommentReactionViewSet(BaseViewSet):
|
|||||||
.filter(
|
.filter(
|
||||||
project__project_projectmember__member=self.request.user,
|
project__project_projectmember__member=self.request.user,
|
||||||
project__project_projectmember__is_active=True,
|
project__project_projectmember__is_active=True,
|
||||||
|
project__archived_at__isnull=True,
|
||||||
)
|
)
|
||||||
.order_by("-created_at")
|
.order_by("-created_at")
|
||||||
.distinct()
|
.distinct()
|
||||||
|
@ -35,6 +35,7 @@ class IssueLinkViewSet(BaseViewSet):
|
|||||||
.filter(
|
.filter(
|
||||||
project__project_projectmember__member=self.request.user,
|
project__project_projectmember__member=self.request.user,
|
||||||
project__project_projectmember__is_active=True,
|
project__project_projectmember__is_active=True,
|
||||||
|
project__archived_at__isnull=True,
|
||||||
)
|
)
|
||||||
.order_by("-created_at")
|
.order_by("-created_at")
|
||||||
.distinct()
|
.distinct()
|
||||||
|
@ -34,6 +34,7 @@ class IssueReactionViewSet(BaseViewSet):
|
|||||||
.filter(
|
.filter(
|
||||||
project__project_projectmember__member=self.request.user,
|
project__project_projectmember__member=self.request.user,
|
||||||
project__project_projectmember__is_active=True,
|
project__project_projectmember__is_active=True,
|
||||||
|
project__archived_at__isnull=True,
|
||||||
)
|
)
|
||||||
.order_by("-created_at")
|
.order_by("-created_at")
|
||||||
.distinct()
|
.distinct()
|
||||||
|
@ -41,6 +41,7 @@ class IssueRelationViewSet(BaseViewSet):
|
|||||||
.filter(
|
.filter(
|
||||||
project__project_projectmember__member=self.request.user,
|
project__project_projectmember__member=self.request.user,
|
||||||
project__project_projectmember__is_active=True,
|
project__project_projectmember__is_active=True,
|
||||||
|
project__archived_at__isnull=True,
|
||||||
)
|
)
|
||||||
.select_related("project")
|
.select_related("project")
|
||||||
.select_related("workspace")
|
.select_related("workspace")
|
||||||
|
@ -54,6 +54,7 @@ class IssueSubscriberViewSet(BaseViewSet):
|
|||||||
.filter(
|
.filter(
|
||||||
project__project_projectmember__member=self.request.user,
|
project__project_projectmember__member=self.request.user,
|
||||||
project__project_projectmember__is_active=True,
|
project__project_projectmember__is_active=True,
|
||||||
|
project__archived_at__isnull=True,
|
||||||
)
|
)
|
||||||
.order_by("-created_at")
|
.order_by("-created_at")
|
||||||
.distinct()
|
.distinct()
|
||||||
|
@ -196,7 +196,7 @@ class ModuleViewSet(WebhookMixin, BaseViewSet):
|
|||||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
def list(self, request, slug, project_id):
|
def list(self, request, slug, project_id):
|
||||||
queryset = self.get_queryset()
|
queryset = self.get_queryset().filter(archived_at__isnull=True)
|
||||||
if self.fields:
|
if self.fields:
|
||||||
modules = ModuleSerializer(
|
modules = ModuleSerializer(
|
||||||
queryset,
|
queryset,
|
||||||
@ -238,6 +238,7 @@ class ModuleViewSet(WebhookMixin, BaseViewSet):
|
|||||||
def retrieve(self, request, slug, project_id, pk):
|
def retrieve(self, request, slug, project_id, pk):
|
||||||
queryset = (
|
queryset = (
|
||||||
self.get_queryset()
|
self.get_queryset()
|
||||||
|
.filter(archived_at__isnull=True)
|
||||||
.filter(pk=pk)
|
.filter(pk=pk)
|
||||||
.annotate(
|
.annotate(
|
||||||
total_issues=Issue.issue_objects.filter(
|
total_issues=Issue.issue_objects.filter(
|
||||||
@ -374,14 +375,20 @@ class ModuleViewSet(WebhookMixin, BaseViewSet):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def partial_update(self, request, slug, project_id, pk):
|
def partial_update(self, request, slug, project_id, pk):
|
||||||
queryset = self.get_queryset().filter(pk=pk)
|
module = self.get_queryset().filter(pk=pk)
|
||||||
|
|
||||||
|
if module.first().archived_at:
|
||||||
|
return Response(
|
||||||
|
{"error": "Archived module cannot be updated"},
|
||||||
|
status=status.HTTP_400_BAD_REQUEST,
|
||||||
|
)
|
||||||
serializer = ModuleWriteSerializer(
|
serializer = ModuleWriteSerializer(
|
||||||
queryset.first(), data=request.data, partial=True
|
module.first(), data=request.data, partial=True
|
||||||
)
|
)
|
||||||
|
|
||||||
if serializer.is_valid():
|
if serializer.is_valid():
|
||||||
serializer.save()
|
serializer.save()
|
||||||
module = queryset.values(
|
module = module.values(
|
||||||
# Required fields
|
# Required fields
|
||||||
"id",
|
"id",
|
||||||
"workspace_id",
|
"workspace_id",
|
||||||
@ -464,12 +471,167 @@ class ModuleLinkViewSet(BaseViewSet):
|
|||||||
.filter(
|
.filter(
|
||||||
project__project_projectmember__member=self.request.user,
|
project__project_projectmember__member=self.request.user,
|
||||||
project__project_projectmember__is_active=True,
|
project__project_projectmember__is_active=True,
|
||||||
|
project__archived_at__isnull=True,
|
||||||
)
|
)
|
||||||
.order_by("-created_at")
|
.order_by("-created_at")
|
||||||
.distinct()
|
.distinct()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class ModuleArchiveUnarchiveEndpoint(BaseAPIView):
|
||||||
|
|
||||||
|
permission_classes = [
|
||||||
|
ProjectEntityPermission,
|
||||||
|
]
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
favorite_subquery = ModuleFavorite.objects.filter(
|
||||||
|
user=self.request.user,
|
||||||
|
module_id=OuterRef("pk"),
|
||||||
|
project_id=self.kwargs.get("project_id"),
|
||||||
|
workspace__slug=self.kwargs.get("slug"),
|
||||||
|
)
|
||||||
|
return (
|
||||||
|
super()
|
||||||
|
.get_queryset()
|
||||||
|
.filter(project_id=self.kwargs.get("project_id"))
|
||||||
|
.filter(workspace__slug=self.kwargs.get("slug"))
|
||||||
|
.filter(archived_at__isnull=False)
|
||||||
|
.annotate(is_favorite=Exists(favorite_subquery))
|
||||||
|
.select_related("project")
|
||||||
|
.select_related("workspace")
|
||||||
|
.select_related("lead")
|
||||||
|
.prefetch_related("members")
|
||||||
|
.prefetch_related(
|
||||||
|
Prefetch(
|
||||||
|
"link_module",
|
||||||
|
queryset=ModuleLink.objects.select_related(
|
||||||
|
"module", "created_by"
|
||||||
|
),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.annotate(
|
||||||
|
total_issues=Count(
|
||||||
|
"issue_module",
|
||||||
|
filter=Q(
|
||||||
|
issue_module__issue__archived_at__isnull=True,
|
||||||
|
issue_module__issue__is_draft=False,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.annotate(
|
||||||
|
completed_issues=Count(
|
||||||
|
"issue_module__issue__state__group",
|
||||||
|
filter=Q(
|
||||||
|
issue_module__issue__state__group="completed",
|
||||||
|
issue_module__issue__archived_at__isnull=True,
|
||||||
|
issue_module__issue__is_draft=False,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.annotate(
|
||||||
|
cancelled_issues=Count(
|
||||||
|
"issue_module__issue__state__group",
|
||||||
|
filter=Q(
|
||||||
|
issue_module__issue__state__group="cancelled",
|
||||||
|
issue_module__issue__archived_at__isnull=True,
|
||||||
|
issue_module__issue__is_draft=False,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.annotate(
|
||||||
|
started_issues=Count(
|
||||||
|
"issue_module__issue__state__group",
|
||||||
|
filter=Q(
|
||||||
|
issue_module__issue__state__group="started",
|
||||||
|
issue_module__issue__archived_at__isnull=True,
|
||||||
|
issue_module__issue__is_draft=False,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.annotate(
|
||||||
|
unstarted_issues=Count(
|
||||||
|
"issue_module__issue__state__group",
|
||||||
|
filter=Q(
|
||||||
|
issue_module__issue__state__group="unstarted",
|
||||||
|
issue_module__issue__archived_at__isnull=True,
|
||||||
|
issue_module__issue__is_draft=False,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.annotate(
|
||||||
|
backlog_issues=Count(
|
||||||
|
"issue_module__issue__state__group",
|
||||||
|
filter=Q(
|
||||||
|
issue_module__issue__state__group="backlog",
|
||||||
|
issue_module__issue__archived_at__isnull=True,
|
||||||
|
issue_module__issue__is_draft=False,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.annotate(
|
||||||
|
member_ids=Coalesce(
|
||||||
|
ArrayAgg(
|
||||||
|
"members__id",
|
||||||
|
distinct=True,
|
||||||
|
filter=~Q(members__id__isnull=True),
|
||||||
|
),
|
||||||
|
Value([], output_field=ArrayField(UUIDField())),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.order_by("-is_favorite", "-created_at")
|
||||||
|
)
|
||||||
|
|
||||||
|
def list(self, request, slug, project_id):
|
||||||
|
queryset = self.get_queryset()
|
||||||
|
modules = queryset.values( # Required fields
|
||||||
|
"id",
|
||||||
|
"workspace_id",
|
||||||
|
"project_id",
|
||||||
|
# Model fields
|
||||||
|
"name",
|
||||||
|
"description",
|
||||||
|
"description_text",
|
||||||
|
"description_html",
|
||||||
|
"start_date",
|
||||||
|
"target_date",
|
||||||
|
"status",
|
||||||
|
"lead_id",
|
||||||
|
"member_ids",
|
||||||
|
"view_props",
|
||||||
|
"sort_order",
|
||||||
|
"external_source",
|
||||||
|
"external_id",
|
||||||
|
# computed fields
|
||||||
|
"total_issues",
|
||||||
|
"is_favorite",
|
||||||
|
"cancelled_issues",
|
||||||
|
"completed_issues",
|
||||||
|
"started_issues",
|
||||||
|
"unstarted_issues",
|
||||||
|
"backlog_issues",
|
||||||
|
"created_at",
|
||||||
|
"updated_at",
|
||||||
|
)
|
||||||
|
return Response(modules, 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.archived_at = timezone.now()
|
||||||
|
module.save()
|
||||||
|
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
def delete(self, request, slug, project_id, module_id):
|
||||||
|
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)
|
||||||
|
|
||||||
|
|
||||||
class ModuleFavoriteViewSet(BaseViewSet):
|
class ModuleFavoriteViewSet(BaseViewSet):
|
||||||
serializer_class = ModuleFavoriteSerializer
|
serializer_class = ModuleFavoriteSerializer
|
||||||
model = ModuleFavorite
|
model = ModuleFavorite
|
||||||
|
@ -70,6 +70,7 @@ class PageViewSet(BaseViewSet):
|
|||||||
.filter(
|
.filter(
|
||||||
project__project_projectmember__member=self.request.user,
|
project__project_projectmember__member=self.request.user,
|
||||||
project__project_projectmember__is_active=True,
|
project__project_projectmember__is_active=True,
|
||||||
|
project__archived_at__isnull=True,
|
||||||
)
|
)
|
||||||
.filter(parent__isnull=True)
|
.filter(parent__isnull=True)
|
||||||
.filter(Q(owned_by=self.request.user) | Q(access=0))
|
.filter(Q(owned_by=self.request.user) | Q(access=0))
|
||||||
|
@ -13,6 +13,7 @@ from django.db.models import (
|
|||||||
Subquery,
|
Subquery,
|
||||||
)
|
)
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
from django.utils import timezone
|
||||||
|
|
||||||
# Third Party imports
|
# Third Party imports
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
@ -179,6 +180,7 @@ class ProjectViewSet(WebhookMixin, BaseViewSet):
|
|||||||
def retrieve(self, request, slug, pk):
|
def retrieve(self, request, slug, pk):
|
||||||
project = (
|
project = (
|
||||||
self.get_queryset()
|
self.get_queryset()
|
||||||
|
.filter(archived_at__isnull=True)
|
||||||
.filter(pk=pk)
|
.filter(pk=pk)
|
||||||
.annotate(
|
.annotate(
|
||||||
total_issues=Issue.issue_objects.filter(
|
total_issues=Issue.issue_objects.filter(
|
||||||
@ -366,6 +368,12 @@ class ProjectViewSet(WebhookMixin, BaseViewSet):
|
|||||||
|
|
||||||
project = Project.objects.get(pk=pk)
|
project = Project.objects.get(pk=pk)
|
||||||
|
|
||||||
|
if project.archived_at:
|
||||||
|
return Response(
|
||||||
|
{"error": "Archived projects cannot be updated"},
|
||||||
|
status=status.HTTP_400_BAD_REQUEST,
|
||||||
|
)
|
||||||
|
|
||||||
serializer = ProjectSerializer(
|
serializer = ProjectSerializer(
|
||||||
project,
|
project,
|
||||||
data={**request.data},
|
data={**request.data},
|
||||||
@ -420,6 +428,24 @@ class ProjectViewSet(WebhookMixin, BaseViewSet):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class ProjectArchiveUnarchiveEndpoint(BaseAPIView):
|
||||||
|
|
||||||
|
permission_classes = [
|
||||||
|
ProjectBasePermission,
|
||||||
|
]
|
||||||
|
def post(self, request, slug, project_id):
|
||||||
|
project = Project.objects.get(pk=project_id, workspace__slug=slug)
|
||||||
|
project.archived_at = timezone.now()
|
||||||
|
project.save()
|
||||||
|
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
def delete(self, request, slug, project_id):
|
||||||
|
project = Project.objects.get(pk=project_id, workspace__slug=slug)
|
||||||
|
project.archived_at = None
|
||||||
|
project.save()
|
||||||
|
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
|
||||||
class ProjectIdentifierEndpoint(BaseAPIView):
|
class ProjectIdentifierEndpoint(BaseAPIView):
|
||||||
permission_classes = [
|
permission_classes = [
|
||||||
ProjectBasePermission,
|
ProjectBasePermission,
|
||||||
|
@ -50,6 +50,7 @@ class GlobalSearchEndpoint(BaseAPIView):
|
|||||||
q,
|
q,
|
||||||
project_projectmember__member=self.request.user,
|
project_projectmember__member=self.request.user,
|
||||||
project_projectmember__is_active=True,
|
project_projectmember__is_active=True,
|
||||||
|
archived_at__isnull=True,
|
||||||
workspace__slug=slug,
|
workspace__slug=slug,
|
||||||
)
|
)
|
||||||
.distinct()
|
.distinct()
|
||||||
@ -72,6 +73,7 @@ class GlobalSearchEndpoint(BaseAPIView):
|
|||||||
q,
|
q,
|
||||||
project__project_projectmember__member=self.request.user,
|
project__project_projectmember__member=self.request.user,
|
||||||
project__project_projectmember__is_active=True,
|
project__project_projectmember__is_active=True,
|
||||||
|
project__archived_at__isnull=True,
|
||||||
workspace__slug=slug,
|
workspace__slug=slug,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -97,6 +99,7 @@ class GlobalSearchEndpoint(BaseAPIView):
|
|||||||
q,
|
q,
|
||||||
project__project_projectmember__member=self.request.user,
|
project__project_projectmember__member=self.request.user,
|
||||||
project__project_projectmember__is_active=True,
|
project__project_projectmember__is_active=True,
|
||||||
|
project__archived_at__isnull=True,
|
||||||
workspace__slug=slug,
|
workspace__slug=slug,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -121,6 +124,7 @@ class GlobalSearchEndpoint(BaseAPIView):
|
|||||||
q,
|
q,
|
||||||
project__project_projectmember__member=self.request.user,
|
project__project_projectmember__member=self.request.user,
|
||||||
project__project_projectmember__is_active=True,
|
project__project_projectmember__is_active=True,
|
||||||
|
project__archived_at__isnull=True,
|
||||||
workspace__slug=slug,
|
workspace__slug=slug,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -145,6 +149,7 @@ class GlobalSearchEndpoint(BaseAPIView):
|
|||||||
q,
|
q,
|
||||||
project__project_projectmember__member=self.request.user,
|
project__project_projectmember__member=self.request.user,
|
||||||
project__project_projectmember__is_active=True,
|
project__project_projectmember__is_active=True,
|
||||||
|
project__archived_at__isnull=True,
|
||||||
workspace__slug=slug,
|
workspace__slug=slug,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -169,6 +174,7 @@ class GlobalSearchEndpoint(BaseAPIView):
|
|||||||
q,
|
q,
|
||||||
project__project_projectmember__member=self.request.user,
|
project__project_projectmember__member=self.request.user,
|
||||||
project__project_projectmember__is_active=True,
|
project__project_projectmember__is_active=True,
|
||||||
|
project__archived_at__isnull=True,
|
||||||
workspace__slug=slug,
|
workspace__slug=slug,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -243,6 +249,7 @@ class IssueSearchEndpoint(BaseAPIView):
|
|||||||
workspace__slug=slug,
|
workspace__slug=slug,
|
||||||
project__project_projectmember__member=self.request.user,
|
project__project_projectmember__member=self.request.user,
|
||||||
project__project_projectmember__is_active=True,
|
project__project_projectmember__is_active=True,
|
||||||
|
project__archived_at__isnull=True
|
||||||
)
|
)
|
||||||
|
|
||||||
if workspace_search == "false":
|
if workspace_search == "false":
|
||||||
|
@ -33,6 +33,7 @@ class StateViewSet(BaseViewSet):
|
|||||||
.filter(
|
.filter(
|
||||||
project__project_projectmember__member=self.request.user,
|
project__project_projectmember__member=self.request.user,
|
||||||
project__project_projectmember__is_active=True,
|
project__project_projectmember__is_active=True,
|
||||||
|
project__archived_at__isnull=True,
|
||||||
)
|
)
|
||||||
.filter(~Q(name="Triage"))
|
.filter(~Q(name="Triage"))
|
||||||
.select_related("project")
|
.select_related("project")
|
||||||
|
@ -282,6 +282,7 @@ class IssueViewViewSet(BaseViewSet):
|
|||||||
.filter(
|
.filter(
|
||||||
project__project_projectmember__member=self.request.user,
|
project__project_projectmember__member=self.request.user,
|
||||||
project__project_projectmember__is_active=True,
|
project__project_projectmember__is_active=True,
|
||||||
|
project__archived_at__isnull=True,
|
||||||
)
|
)
|
||||||
.select_related("project")
|
.select_related("project")
|
||||||
.select_related("workspace")
|
.select_related("workspace")
|
||||||
|
@ -20,6 +20,7 @@ class WorkspaceLabelsEndpoint(BaseAPIView):
|
|||||||
workspace__slug=slug,
|
workspace__slug=slug,
|
||||||
project__project_projectmember__member=request.user,
|
project__project_projectmember__member=request.user,
|
||||||
project__project_projectmember__is_active=True,
|
project__project_projectmember__is_active=True,
|
||||||
|
project__archived_at__isnull=True,
|
||||||
)
|
)
|
||||||
serializer = LabelSerializer(labels, many=True).data
|
serializer = LabelSerializer(labels, many=True).data
|
||||||
return Response(serializer, status=status.HTTP_200_OK)
|
return Response(serializer, status=status.HTTP_200_OK)
|
||||||
|
@ -20,6 +20,7 @@ class WorkspaceStatesEndpoint(BaseAPIView):
|
|||||||
workspace__slug=slug,
|
workspace__slug=slug,
|
||||||
project__project_projectmember__member=request.user,
|
project__project_projectmember__member=request.user,
|
||||||
project__project_projectmember__is_active=True,
|
project__project_projectmember__is_active=True,
|
||||||
|
project__archived_at__isnull=True,
|
||||||
)
|
)
|
||||||
serializer = StateSerializer(states, many=True).data
|
serializer = StateSerializer(states, many=True).data
|
||||||
return Response(serializer, status=status.HTTP_200_OK)
|
return Response(serializer, status=status.HTTP_200_OK)
|
||||||
|
@ -124,7 +124,7 @@ class WorkspaceUserProfileIssuesEndpoint(BaseAPIView):
|
|||||||
| Q(issue_subscribers__subscriber_id=user_id),
|
| Q(issue_subscribers__subscriber_id=user_id),
|
||||||
workspace__slug=slug,
|
workspace__slug=slug,
|
||||||
project__project_projectmember__member=request.user,
|
project__project_projectmember__member=request.user,
|
||||||
project__project_projectmember__is_active=True
|
project__project_projectmember__is_active=True,
|
||||||
)
|
)
|
||||||
.filter(**filters)
|
.filter(**filters)
|
||||||
.select_related("workspace", "project", "state", "parent")
|
.select_related("workspace", "project", "state", "parent")
|
||||||
@ -299,6 +299,7 @@ class WorkspaceUserProfileEndpoint(BaseAPIView):
|
|||||||
workspace__slug=slug,
|
workspace__slug=slug,
|
||||||
project_projectmember__member=request.user,
|
project_projectmember__member=request.user,
|
||||||
project_projectmember__is_active=True,
|
project_projectmember__is_active=True,
|
||||||
|
archived_at__isnull=True,
|
||||||
)
|
)
|
||||||
.annotate(
|
.annotate(
|
||||||
created_issues=Count(
|
created_issues=Count(
|
||||||
@ -387,6 +388,7 @@ class WorkspaceUserActivityEndpoint(BaseAPIView):
|
|||||||
workspace__slug=slug,
|
workspace__slug=slug,
|
||||||
project__project_projectmember__member=request.user,
|
project__project_projectmember__member=request.user,
|
||||||
project__project_projectmember__is_active=True,
|
project__project_projectmember__is_active=True,
|
||||||
|
project__archived_at__isnull=True,
|
||||||
actor=user_id,
|
actor=user_id,
|
||||||
).select_related("actor", "workspace", "issue", "project")
|
).select_related("actor", "workspace", "issue", "project")
|
||||||
|
|
||||||
@ -498,6 +500,7 @@ class WorkspaceUserProfileStatsEndpoint(BaseAPIView):
|
|||||||
subscriber_id=user_id,
|
subscriber_id=user_id,
|
||||||
project__project_projectmember__member=request.user,
|
project__project_projectmember__member=request.user,
|
||||||
project__project_projectmember__is_active=True,
|
project__project_projectmember__is_active=True,
|
||||||
|
project__archived_at__isnull=True,
|
||||||
)
|
)
|
||||||
.filter(**filters)
|
.filter(**filters)
|
||||||
.count()
|
.count()
|
||||||
|
@ -304,6 +304,7 @@ def issue_export_task(
|
|||||||
project_id__in=project_ids,
|
project_id__in=project_ids,
|
||||||
project__project_projectmember__member=exporter_instance.initiated_by_id,
|
project__project_projectmember__member=exporter_instance.initiated_by_id,
|
||||||
project__project_projectmember__is_active=True,
|
project__project_projectmember__is_active=True,
|
||||||
|
project__archived_at__isnull=True,
|
||||||
)
|
)
|
||||||
.select_related(
|
.select_related(
|
||||||
"project", "workspace", "state", "parent", "created_by"
|
"project", "workspace", "state", "parent", "created_by"
|
||||||
|
@ -0,0 +1,41 @@
|
|||||||
|
# Generated by Django 4.2.7 on 2024-03-19 08:28
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('db', '0061_project_logo_props'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="cycle",
|
||||||
|
name="archived_at",
|
||||||
|
field=models.DateTimeField(null=True),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="module",
|
||||||
|
name="archived_at",
|
||||||
|
field=models.DateTimeField(null=True),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="project",
|
||||||
|
name="archived_at",
|
||||||
|
field=models.DateTimeField(null=True),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="socialloginconnection",
|
||||||
|
name="medium",
|
||||||
|
field=models.CharField(
|
||||||
|
choices=[
|
||||||
|
("Google", "google"),
|
||||||
|
("Github", "github"),
|
||||||
|
("Jira", "jira"),
|
||||||
|
],
|
||||||
|
default=None,
|
||||||
|
max_length=20,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
@ -69,6 +69,7 @@ class Cycle(ProjectBaseModel):
|
|||||||
external_source = models.CharField(max_length=255, null=True, blank=True)
|
external_source = models.CharField(max_length=255, null=True, blank=True)
|
||||||
external_id = models.CharField(max_length=255, blank=True, null=True)
|
external_id = models.CharField(max_length=255, blank=True, null=True)
|
||||||
progress_snapshot = models.JSONField(default=dict)
|
progress_snapshot = models.JSONField(default=dict)
|
||||||
|
archived_at = models.DateTimeField(null=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = "Cycle"
|
verbose_name = "Cycle"
|
||||||
|
@ -91,6 +91,7 @@ class IssueManager(models.Manager):
|
|||||||
| models.Q(issue_inbox__isnull=True)
|
| models.Q(issue_inbox__isnull=True)
|
||||||
)
|
)
|
||||||
.exclude(archived_at__isnull=False)
|
.exclude(archived_at__isnull=False)
|
||||||
|
.exclude(project__archived_at__isnull=False)
|
||||||
.exclude(is_draft=True)
|
.exclude(is_draft=True)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -92,6 +92,7 @@ class Module(ProjectBaseModel):
|
|||||||
sort_order = models.FloatField(default=65535)
|
sort_order = models.FloatField(default=65535)
|
||||||
external_source = models.CharField(max_length=255, null=True, blank=True)
|
external_source = models.CharField(max_length=255, null=True, blank=True)
|
||||||
external_id = models.CharField(max_length=255, blank=True, null=True)
|
external_id = models.CharField(max_length=255, blank=True, null=True)
|
||||||
|
archived_at = models.DateTimeField(null=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
unique_together = ["name", "project"]
|
unique_together = ["name", "project"]
|
||||||
|
@ -114,6 +114,7 @@ class Project(BaseModel):
|
|||||||
null=True,
|
null=True,
|
||||||
related_name="default_state",
|
related_name="default_state",
|
||||||
)
|
)
|
||||||
|
archived_at = models.DateTimeField(null=True)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
"""Return name of the project"""
|
"""Return name of the project"""
|
||||||
|
Loading…
Reference in New Issue
Block a user