chore: formatting changes
This commit is contained in:
parent
a446bc043e
commit
0dbd4cfe97
267 changed files with 2478 additions and 7981 deletions
|
|
@ -26,9 +26,7 @@ def update_description():
|
|||
updated_issues.append(issue)
|
||||
|
||||
Issue.objects.bulk_update(
|
||||
updated_issues,
|
||||
["description_html", "description_stripped"],
|
||||
batch_size=100,
|
||||
updated_issues, ["description_html", "description_stripped"], batch_size=100
|
||||
)
|
||||
print("Success")
|
||||
except Exception as e:
|
||||
|
|
@ -42,9 +40,7 @@ def update_comments():
|
|||
updated_issue_comments = []
|
||||
|
||||
for issue_comment in issue_comments:
|
||||
issue_comment.comment_html = (
|
||||
f"<p>{issue_comment.comment_stripped}</p>"
|
||||
)
|
||||
issue_comment.comment_html = f"<p>{issue_comment.comment_stripped}</p>"
|
||||
updated_issue_comments.append(issue_comment)
|
||||
|
||||
IssueComment.objects.bulk_update(
|
||||
|
|
@ -103,9 +99,7 @@ def updated_issue_sort_order():
|
|||
issue.sort_order = issue.sequence_id * random.randint(100, 500)
|
||||
updated_issues.append(issue)
|
||||
|
||||
Issue.objects.bulk_update(
|
||||
updated_issues, ["sort_order"], batch_size=100
|
||||
)
|
||||
Issue.objects.bulk_update(updated_issues, ["sort_order"], batch_size=100)
|
||||
print("Success")
|
||||
except Exception as e:
|
||||
print(e)
|
||||
|
|
@ -143,9 +137,7 @@ def update_project_cover_images():
|
|||
project.cover_image = project_cover_images[random.randint(0, 19)]
|
||||
updated_projects.append(project)
|
||||
|
||||
Project.objects.bulk_update(
|
||||
updated_projects, ["cover_image"], batch_size=100
|
||||
)
|
||||
Project.objects.bulk_update(updated_projects, ["cover_image"], batch_size=100)
|
||||
print("Success")
|
||||
except Exception as e:
|
||||
print(e)
|
||||
|
|
@ -194,9 +186,7 @@ def update_label_color():
|
|||
|
||||
def create_slack_integration():
|
||||
try:
|
||||
_ = Integration.objects.create(
|
||||
provider="slack", network=2, title="Slack"
|
||||
)
|
||||
_ = Integration.objects.create(provider="slack", network=2, title="Slack")
|
||||
print("Success")
|
||||
except Exception as e:
|
||||
print(e)
|
||||
|
|
@ -222,16 +212,12 @@ def update_integration_verified():
|
|||
|
||||
def update_start_date():
|
||||
try:
|
||||
issues = Issue.objects.filter(
|
||||
state__group__in=["started", "completed"]
|
||||
)
|
||||
issues = Issue.objects.filter(state__group__in=["started", "completed"])
|
||||
updated_issues = []
|
||||
for issue in issues:
|
||||
issue.start_date = issue.created_at.date()
|
||||
updated_issues.append(issue)
|
||||
Issue.objects.bulk_update(
|
||||
updated_issues, ["start_date"], batch_size=500
|
||||
)
|
||||
Issue.objects.bulk_update(updated_issues, ["start_date"], batch_size=500)
|
||||
print("Success")
|
||||
except Exception as e:
|
||||
print(e)
|
||||
|
|
|
|||
|
|
@ -3,9 +3,7 @@ import os
|
|||
import sys
|
||||
|
||||
if __name__ == "__main__":
|
||||
os.environ.setdefault(
|
||||
"DJANGO_SETTINGS_MODULE", "plane.settings.production"
|
||||
)
|
||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "plane.settings.production")
|
||||
try:
|
||||
from django.core.management import execute_from_command_line
|
||||
except ImportError as exc:
|
||||
|
|
|
|||
|
|
@ -25,10 +25,7 @@ class APIKeyAuthentication(authentication.BaseAuthentication):
|
|||
def validate_api_token(self, token):
|
||||
try:
|
||||
api_token = APIToken.objects.get(
|
||||
Q(
|
||||
Q(expired_at__gt=timezone.now())
|
||||
| Q(expired_at__isnull=True)
|
||||
),
|
||||
Q(Q(expired_at__gt=timezone.now()) | Q(expired_at__isnull=True)),
|
||||
token=token,
|
||||
is_active=True,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -80,4 +80,4 @@ class ServiceTokenRateThrottle(SimpleRateThrottle):
|
|||
request.META["X-RateLimit-Remaining"] = max(0, available)
|
||||
request.META["X-RateLimit-Reset"] = reset_time
|
||||
|
||||
return allowed
|
||||
return allowed
|
||||
|
|
|
|||
|
|
@ -13,9 +13,5 @@ from .issue import (
|
|||
)
|
||||
from .state import StateLiteSerializer, StateSerializer
|
||||
from .cycle import CycleSerializer, CycleIssueSerializer, CycleLiteSerializer
|
||||
from .module import (
|
||||
ModuleSerializer,
|
||||
ModuleIssueSerializer,
|
||||
ModuleLiteSerializer,
|
||||
)
|
||||
from .module import ModuleSerializer, ModuleIssueSerializer, ModuleLiteSerializer
|
||||
from .intake import IntakeIssueSerializer
|
||||
|
|
|
|||
|
|
@ -102,8 +102,6 @@ class BaseSerializer(serializers.ModelSerializer):
|
|||
response[expand] = exp_serializer.data
|
||||
else:
|
||||
# You might need to handle this case differently
|
||||
response[expand] = getattr(
|
||||
instance, f"{expand}_id", None
|
||||
)
|
||||
response[expand] = getattr(instance, f"{expand}_id", None)
|
||||
|
||||
return response
|
||||
|
|
|
|||
|
|
@ -23,9 +23,7 @@ class CycleSerializer(BaseSerializer):
|
|||
and data.get("end_date", None) is not None
|
||||
and data.get("start_date", None) > data.get("end_date", None)
|
||||
):
|
||||
raise serializers.ValidationError(
|
||||
"Start date cannot exceed end date"
|
||||
)
|
||||
raise serializers.ValidationError("Start date cannot exceed end date")
|
||||
return data
|
||||
|
||||
class Meta:
|
||||
|
|
@ -50,11 +48,7 @@ class CycleIssueSerializer(BaseSerializer):
|
|||
class Meta:
|
||||
model = CycleIssue
|
||||
fields = "__all__"
|
||||
read_only_fields = [
|
||||
"workspace",
|
||||
"project",
|
||||
"cycle",
|
||||
]
|
||||
read_only_fields = ["workspace", "project", "cycle"]
|
||||
|
||||
|
||||
class CycleLiteSerializer(BaseSerializer):
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ from rest_framework import serializers
|
|||
|
||||
|
||||
class IntakeIssueSerializer(BaseSerializer):
|
||||
|
||||
issue_detail = IssueExpandSerializer(read_only=True, source="issue")
|
||||
inbox = serializers.UUIDField(source="intake.id", read_only=True)
|
||||
|
||||
|
|
|
|||
|
|
@ -49,25 +49,13 @@ class IssueSerializer(BaseSerializer):
|
|||
required=False,
|
||||
)
|
||||
type_id = serializers.PrimaryKeyRelatedField(
|
||||
source="type",
|
||||
queryset=IssueType.objects.all(),
|
||||
required=False,
|
||||
allow_null=True,
|
||||
source="type", queryset=IssueType.objects.all(), required=False, allow_null=True
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = Issue
|
||||
read_only_fields = [
|
||||
"id",
|
||||
"workspace",
|
||||
"project",
|
||||
"updated_by",
|
||||
"updated_at",
|
||||
]
|
||||
exclude = [
|
||||
"description",
|
||||
"description_stripped",
|
||||
]
|
||||
read_only_fields = ["id", "workspace", "project", "updated_by", "updated_at"]
|
||||
exclude = ["description", "description_stripped"]
|
||||
|
||||
def validate(self, data):
|
||||
if (
|
||||
|
|
@ -75,9 +63,7 @@ class IssueSerializer(BaseSerializer):
|
|||
and data.get("target_date", None) is not None
|
||||
and data.get("start_date", None) > data.get("target_date", None)
|
||||
):
|
||||
raise serializers.ValidationError(
|
||||
"Start date cannot exceed target date"
|
||||
)
|
||||
raise serializers.ValidationError("Start date cannot exceed target date")
|
||||
|
||||
try:
|
||||
if data.get("description_html", None) is not None:
|
||||
|
|
@ -99,16 +85,14 @@ class IssueSerializer(BaseSerializer):
|
|||
# Validate labels are from project
|
||||
if data.get("labels", []):
|
||||
data["labels"] = Label.objects.filter(
|
||||
project_id=self.context.get("project_id"),
|
||||
id__in=data["labels"],
|
||||
project_id=self.context.get("project_id"), id__in=data["labels"]
|
||||
).values_list("id", flat=True)
|
||||
|
||||
# Check state is from the project only else raise validation error
|
||||
if (
|
||||
data.get("state")
|
||||
and not State.objects.filter(
|
||||
project_id=self.context.get("project_id"),
|
||||
pk=data.get("state").id,
|
||||
project_id=self.context.get("project_id"), pk=data.get("state").id
|
||||
).exists()
|
||||
):
|
||||
raise serializers.ValidationError(
|
||||
|
|
@ -119,8 +103,7 @@ class IssueSerializer(BaseSerializer):
|
|||
if (
|
||||
data.get("parent")
|
||||
and not Issue.objects.filter(
|
||||
workspace_id=self.context.get("workspace_id"),
|
||||
pk=data.get("parent").id,
|
||||
workspace_id=self.context.get("workspace_id"), pk=data.get("parent").id
|
||||
).exists()
|
||||
):
|
||||
raise serializers.ValidationError(
|
||||
|
|
@ -147,9 +130,7 @@ class IssueSerializer(BaseSerializer):
|
|||
issue_type = issue_type
|
||||
|
||||
issue = Issue.objects.create(
|
||||
**validated_data,
|
||||
project_id=project_id,
|
||||
type=issue_type,
|
||||
**validated_data, project_id=project_id, type=issue_type
|
||||
)
|
||||
|
||||
# Issue Audit Users
|
||||
|
|
@ -264,13 +245,9 @@ class IssueSerializer(BaseSerializer):
|
|||
]
|
||||
if "labels" in self.fields:
|
||||
if "labels" in self.expand:
|
||||
data["labels"] = LabelSerializer(
|
||||
instance.labels.all(), many=True
|
||||
).data
|
||||
data["labels"] = LabelSerializer(instance.labels.all(), many=True).data
|
||||
else:
|
||||
data["labels"] = [
|
||||
str(label.id) for label in instance.labels.all()
|
||||
]
|
||||
data["labels"] = [str(label.id) for label in instance.labels.all()]
|
||||
|
||||
return data
|
||||
|
||||
|
|
@ -278,11 +255,7 @@ class IssueSerializer(BaseSerializer):
|
|||
class IssueLiteSerializer(BaseSerializer):
|
||||
class Meta:
|
||||
model = Issue
|
||||
fields = [
|
||||
"id",
|
||||
"sequence_id",
|
||||
"project_id",
|
||||
]
|
||||
fields = ["id", "sequence_id", "project_id"]
|
||||
read_only_fields = fields
|
||||
|
||||
|
||||
|
|
@ -334,8 +307,7 @@ class IssueLinkSerializer(BaseSerializer):
|
|||
# Validation if url already exists
|
||||
def create(self, validated_data):
|
||||
if IssueLink.objects.filter(
|
||||
url=validated_data.get("url"),
|
||||
issue_id=validated_data.get("issue_id"),
|
||||
url=validated_data.get("url"), issue_id=validated_data.get("issue_id")
|
||||
).exists():
|
||||
raise serializers.ValidationError(
|
||||
{"error": "URL already exists for this Issue"}
|
||||
|
|
@ -345,8 +317,7 @@ class IssueLinkSerializer(BaseSerializer):
|
|||
def update(self, instance, validated_data):
|
||||
if (
|
||||
IssueLink.objects.filter(
|
||||
url=validated_data.get("url"),
|
||||
issue_id=instance.issue_id,
|
||||
url=validated_data.get("url"), issue_id=instance.issue_id
|
||||
)
|
||||
.exclude(pk=instance.id)
|
||||
.exists()
|
||||
|
|
@ -387,10 +358,7 @@ class IssueCommentSerializer(BaseSerializer):
|
|||
"created_at",
|
||||
"updated_at",
|
||||
]
|
||||
exclude = [
|
||||
"comment_stripped",
|
||||
"comment_json",
|
||||
]
|
||||
exclude = ["comment_stripped", "comment_json"]
|
||||
|
||||
def validate(self, data):
|
||||
try:
|
||||
|
|
@ -407,38 +375,27 @@ class IssueCommentSerializer(BaseSerializer):
|
|||
class IssueActivitySerializer(BaseSerializer):
|
||||
class Meta:
|
||||
model = IssueActivity
|
||||
exclude = [
|
||||
"created_by",
|
||||
"updated_by",
|
||||
]
|
||||
exclude = ["created_by", "updated_by"]
|
||||
|
||||
|
||||
class CycleIssueSerializer(BaseSerializer):
|
||||
cycle = CycleSerializer(read_only=True)
|
||||
|
||||
class Meta:
|
||||
fields = [
|
||||
"cycle",
|
||||
]
|
||||
fields = ["cycle"]
|
||||
|
||||
|
||||
class ModuleIssueSerializer(BaseSerializer):
|
||||
module = ModuleSerializer(read_only=True)
|
||||
|
||||
class Meta:
|
||||
fields = [
|
||||
"module",
|
||||
]
|
||||
fields = ["module"]
|
||||
|
||||
|
||||
class LabelLiteSerializer(BaseSerializer):
|
||||
class Meta:
|
||||
model = Label
|
||||
fields = [
|
||||
"id",
|
||||
"name",
|
||||
"color",
|
||||
]
|
||||
fields = ["id", "name", "color"]
|
||||
|
||||
|
||||
class IssueExpandSerializer(BaseSerializer):
|
||||
|
|
|
|||
|
|
@ -53,14 +53,11 @@ class ModuleSerializer(BaseSerializer):
|
|||
and data.get("target_date", None) is not None
|
||||
and data.get("start_date", None) > data.get("target_date", None)
|
||||
):
|
||||
raise serializers.ValidationError(
|
||||
"Start date cannot exceed target date"
|
||||
)
|
||||
raise serializers.ValidationError("Start date cannot exceed target date")
|
||||
|
||||
if data.get("members", []):
|
||||
data["members"] = ProjectMember.objects.filter(
|
||||
project_id=self.context.get("project_id"),
|
||||
member_id__in=data["members"],
|
||||
project_id=self.context.get("project_id"), member_id__in=data["members"]
|
||||
).values_list("member_id", flat=True)
|
||||
|
||||
return data
|
||||
|
|
@ -74,9 +71,7 @@ class ModuleSerializer(BaseSerializer):
|
|||
module_name = validated_data.get("name")
|
||||
if module_name:
|
||||
# Lookup for the module name in the module table for that project
|
||||
if Module.objects.filter(
|
||||
name=module_name, project_id=project_id
|
||||
).exists():
|
||||
if Module.objects.filter(name=module_name, project_id=project_id).exists():
|
||||
raise serializers.ValidationError(
|
||||
{"error": "Module with this name already exists"}
|
||||
)
|
||||
|
|
@ -107,9 +102,7 @@ class ModuleSerializer(BaseSerializer):
|
|||
if module_name:
|
||||
# Lookup for the module name in the module table for that project
|
||||
if (
|
||||
Module.objects.filter(
|
||||
name=module_name, project=instance.project
|
||||
)
|
||||
Module.objects.filter(name=module_name, project=instance.project)
|
||||
.exclude(id=instance.id)
|
||||
.exists()
|
||||
):
|
||||
|
|
@ -172,8 +165,7 @@ class ModuleLinkSerializer(BaseSerializer):
|
|||
# Validation if url already exists
|
||||
def create(self, validated_data):
|
||||
if ModuleLink.objects.filter(
|
||||
url=validated_data.get("url"),
|
||||
module_id=validated_data.get("module_id"),
|
||||
url=validated_data.get("url"), module_id=validated_data.get("module_id")
|
||||
).exists():
|
||||
raise serializers.ValidationError(
|
||||
{"error": "URL already exists for this Issue"}
|
||||
|
|
|
|||
|
|
@ -2,11 +2,7 @@
|
|||
from rest_framework import serializers
|
||||
|
||||
# Module imports
|
||||
from plane.db.models import (
|
||||
Project,
|
||||
ProjectIdentifier,
|
||||
WorkspaceMember,
|
||||
)
|
||||
from plane.db.models import Project, ProjectIdentifier, WorkspaceMember
|
||||
|
||||
from .base import BaseSerializer
|
||||
|
||||
|
|
@ -67,16 +63,12 @@ class ProjectSerializer(BaseSerializer):
|
|||
def create(self, validated_data):
|
||||
identifier = validated_data.get("identifier", "").strip().upper()
|
||||
if identifier == "":
|
||||
raise serializers.ValidationError(
|
||||
detail="Project Identifier is required"
|
||||
)
|
||||
raise serializers.ValidationError(detail="Project Identifier is required")
|
||||
|
||||
if ProjectIdentifier.objects.filter(
|
||||
name=identifier, workspace_id=self.context["workspace_id"]
|
||||
).exists():
|
||||
raise serializers.ValidationError(
|
||||
detail="Project Identifier is taken"
|
||||
)
|
||||
raise serializers.ValidationError(detail="Project Identifier is taken")
|
||||
|
||||
project = Project.objects.create(
|
||||
**validated_data, workspace_id=self.context["workspace_id"]
|
||||
|
|
|
|||
|
|
@ -7,9 +7,9 @@ class StateSerializer(BaseSerializer):
|
|||
def validate(self, data):
|
||||
# If the default is being provided then make all other states default False
|
||||
if data.get("default", False):
|
||||
State.objects.filter(
|
||||
project_id=self.context.get("project_id")
|
||||
).update(default=False)
|
||||
State.objects.filter(project_id=self.context.get("project_id")).update(
|
||||
default=False
|
||||
)
|
||||
return data
|
||||
|
||||
class Meta:
|
||||
|
|
@ -30,10 +30,5 @@ class StateSerializer(BaseSerializer):
|
|||
class StateLiteSerializer(BaseSerializer):
|
||||
class Meta:
|
||||
model = State
|
||||
fields = [
|
||||
"id",
|
||||
"name",
|
||||
"color",
|
||||
"group",
|
||||
]
|
||||
fields = ["id", "name", "color", "group"]
|
||||
read_only_fields = fields
|
||||
|
|
|
|||
|
|
@ -8,9 +8,5 @@ class WorkspaceLiteSerializer(BaseSerializer):
|
|||
|
||||
class Meta:
|
||||
model = Workspace
|
||||
fields = [
|
||||
"name",
|
||||
"slug",
|
||||
"id",
|
||||
]
|
||||
fields = ["name", "slug", "id"]
|
||||
read_only_fields = fields
|
||||
|
|
|
|||
|
|
@ -1,13 +1,11 @@
|
|||
from django.urls import path
|
||||
|
||||
from plane.api.views import (
|
||||
ProjectMemberAPIEndpoint,
|
||||
)
|
||||
from plane.api.views import ProjectMemberAPIEndpoint
|
||||
|
||||
urlpatterns = [
|
||||
path(
|
||||
"workspaces/<str:slug>/projects/<str:project_id>/members/",
|
||||
ProjectMemberAPIEndpoint.as_view(),
|
||||
name="users",
|
||||
),
|
||||
)
|
||||
]
|
||||
|
|
|
|||
|
|
@ -1,15 +1,10 @@
|
|||
from django.urls import path
|
||||
|
||||
from plane.api.views import (
|
||||
ProjectAPIEndpoint,
|
||||
ProjectArchiveUnarchiveAPIEndpoint,
|
||||
)
|
||||
from plane.api.views import ProjectAPIEndpoint, ProjectArchiveUnarchiveAPIEndpoint
|
||||
|
||||
urlpatterns = [
|
||||
path(
|
||||
"workspaces/<str:slug>/projects/",
|
||||
ProjectAPIEndpoint.as_view(),
|
||||
name="project",
|
||||
"workspaces/<str:slug>/projects/", ProjectAPIEndpoint.as_view(), name="project"
|
||||
),
|
||||
path(
|
||||
"workspaces/<str:slug>/projects/<uuid:pk>/",
|
||||
|
|
|
|||
|
|
@ -37,13 +37,9 @@ class TimezoneMixin:
|
|||
|
||||
|
||||
class BaseAPIView(TimezoneMixin, APIView, BasePaginator):
|
||||
authentication_classes = [
|
||||
APIKeyAuthentication,
|
||||
]
|
||||
authentication_classes = [APIKeyAuthentication]
|
||||
|
||||
permission_classes = [
|
||||
IsAuthenticated,
|
||||
]
|
||||
permission_classes = [IsAuthenticated]
|
||||
|
||||
def filter_queryset(self, queryset):
|
||||
for backend in list(self.filter_backends):
|
||||
|
|
@ -56,8 +52,7 @@ class BaseAPIView(TimezoneMixin, APIView, BasePaginator):
|
|||
|
||||
if api_key:
|
||||
service_token = APIToken.objects.filter(
|
||||
token=api_key,
|
||||
is_service=True,
|
||||
token=api_key, is_service=True
|
||||
).first()
|
||||
|
||||
if service_token:
|
||||
|
|
@ -123,9 +118,7 @@ class BaseAPIView(TimezoneMixin, APIView, BasePaginator):
|
|||
|
||||
def finalize_response(self, request, response, *args, **kwargs):
|
||||
# Call super to get the default response
|
||||
response = super().finalize_response(
|
||||
request, response, *args, **kwargs
|
||||
)
|
||||
response = super().finalize_response(request, response, *args, **kwargs)
|
||||
|
||||
# Add custom headers if they exist in the request META
|
||||
ratelimit_remaining = request.META.get("X-RateLimit-Remaining")
|
||||
|
|
@ -154,17 +147,13 @@ class BaseAPIView(TimezoneMixin, APIView, BasePaginator):
|
|||
@property
|
||||
def fields(self):
|
||||
fields = [
|
||||
field
|
||||
for field in self.request.GET.get("fields", "").split(",")
|
||||
if field
|
||||
field for field in self.request.GET.get("fields", "").split(",") if field
|
||||
]
|
||||
return fields if fields else None
|
||||
|
||||
@property
|
||||
def expand(self):
|
||||
expand = [
|
||||
expand
|
||||
for expand in self.request.GET.get("expand", "").split(",")
|
||||
if expand
|
||||
expand for expand in self.request.GET.get("expand", "").split(",") if expand
|
||||
]
|
||||
return expand if expand else None
|
||||
return expand if expand else None
|
||||
|
|
|
|||
|
|
@ -25,10 +25,7 @@ from rest_framework import status
|
|||
from rest_framework.response import Response
|
||||
|
||||
# Module imports
|
||||
from plane.api.serializers import (
|
||||
CycleIssueSerializer,
|
||||
CycleSerializer,
|
||||
)
|
||||
from plane.api.serializers import CycleIssueSerializer, CycleSerializer
|
||||
from plane.app.permissions import ProjectEntityPermission
|
||||
from plane.bgtasks.issue_activities_task import issue_activity
|
||||
from plane.db.models import (
|
||||
|
|
@ -57,9 +54,7 @@ class CycleAPIEndpoint(BaseAPIView):
|
|||
serializer_class = CycleSerializer
|
||||
model = Cycle
|
||||
webhook_event = "cycle"
|
||||
permission_classes = [
|
||||
ProjectEntityPermission,
|
||||
]
|
||||
permission_classes = [ProjectEntityPermission]
|
||||
|
||||
def get_queryset(self):
|
||||
return (
|
||||
|
|
@ -143,26 +138,18 @@ class CycleAPIEndpoint(BaseAPIView):
|
|||
|
||||
def get(self, request, slug, project_id, pk=None):
|
||||
if pk:
|
||||
queryset = (
|
||||
self.get_queryset().filter(archived_at__isnull=True).get(pk=pk)
|
||||
)
|
||||
queryset = self.get_queryset().filter(archived_at__isnull=True).get(pk=pk)
|
||||
data = CycleSerializer(
|
||||
queryset,
|
||||
fields=self.fields,
|
||||
expand=self.expand,
|
||||
queryset, fields=self.fields, expand=self.expand
|
||||
).data
|
||||
return Response(
|
||||
data,
|
||||
status=status.HTTP_200_OK,
|
||||
)
|
||||
return Response(data, status=status.HTTP_200_OK)
|
||||
queryset = self.get_queryset().filter(archived_at__isnull=True)
|
||||
cycle_view = request.GET.get("cycle_view", "all")
|
||||
|
||||
# Current Cycle
|
||||
if cycle_view == "current":
|
||||
queryset = queryset.filter(
|
||||
start_date__lte=timezone.now(),
|
||||
end_date__gte=timezone.now(),
|
||||
start_date__lte=timezone.now(), end_date__gte=timezone.now()
|
||||
)
|
||||
data = CycleSerializer(
|
||||
queryset, many=True, fields=self.fields, expand=self.expand
|
||||
|
|
@ -176,10 +163,7 @@ class CycleAPIEndpoint(BaseAPIView):
|
|||
request=request,
|
||||
queryset=(queryset),
|
||||
on_results=lambda cycles: CycleSerializer(
|
||||
cycles,
|
||||
many=True,
|
||||
fields=self.fields,
|
||||
expand=self.expand,
|
||||
cycles, many=True, fields=self.fields, expand=self.expand
|
||||
).data,
|
||||
)
|
||||
|
||||
|
|
@ -190,53 +174,38 @@ class CycleAPIEndpoint(BaseAPIView):
|
|||
request=request,
|
||||
queryset=(queryset),
|
||||
on_results=lambda cycles: CycleSerializer(
|
||||
cycles,
|
||||
many=True,
|
||||
fields=self.fields,
|
||||
expand=self.expand,
|
||||
cycles, many=True, fields=self.fields, expand=self.expand
|
||||
).data,
|
||||
)
|
||||
|
||||
# Draft Cycles
|
||||
if cycle_view == "draft":
|
||||
queryset = queryset.filter(
|
||||
end_date=None,
|
||||
start_date=None,
|
||||
)
|
||||
queryset = queryset.filter(end_date=None, start_date=None)
|
||||
return self.paginate(
|
||||
request=request,
|
||||
queryset=(queryset),
|
||||
on_results=lambda cycles: CycleSerializer(
|
||||
cycles,
|
||||
many=True,
|
||||
fields=self.fields,
|
||||
expand=self.expand,
|
||||
cycles, many=True, fields=self.fields, expand=self.expand
|
||||
).data,
|
||||
)
|
||||
|
||||
# Incomplete Cycles
|
||||
if cycle_view == "incomplete":
|
||||
queryset = queryset.filter(
|
||||
Q(end_date__gte=timezone.now()) | Q(end_date__isnull=True),
|
||||
Q(end_date__gte=timezone.now()) | Q(end_date__isnull=True)
|
||||
)
|
||||
return self.paginate(
|
||||
request=request,
|
||||
queryset=(queryset),
|
||||
on_results=lambda cycles: CycleSerializer(
|
||||
cycles,
|
||||
many=True,
|
||||
fields=self.fields,
|
||||
expand=self.expand,
|
||||
cycles, many=True, fields=self.fields, expand=self.expand
|
||||
).data,
|
||||
)
|
||||
return self.paginate(
|
||||
request=request,
|
||||
queryset=(queryset),
|
||||
on_results=lambda cycles: CycleSerializer(
|
||||
cycles,
|
||||
many=True,
|
||||
fields=self.fields,
|
||||
expand=self.expand,
|
||||
cycles, many=True, fields=self.fields, expand=self.expand
|
||||
).data,
|
||||
)
|
||||
|
||||
|
|
@ -273,10 +242,7 @@ class CycleAPIEndpoint(BaseAPIView):
|
|||
},
|
||||
status=status.HTTP_409_CONFLICT,
|
||||
)
|
||||
serializer.save(
|
||||
project_id=project_id,
|
||||
owned_by=request.user,
|
||||
)
|
||||
serializer.save(project_id=project_id, owned_by=request.user)
|
||||
# Send the model activity
|
||||
model_activity.delay(
|
||||
model_name="cycle",
|
||||
|
|
@ -287,12 +253,8 @@ class CycleAPIEndpoint(BaseAPIView):
|
|||
slug=slug,
|
||||
origin=request.META.get("HTTP_ORIGIN"),
|
||||
)
|
||||
return Response(
|
||||
serializer.data, status=status.HTTP_201_CREATED
|
||||
)
|
||||
return Response(
|
||||
serializer.errors, status=status.HTTP_400_BAD_REQUEST
|
||||
)
|
||||
return Response(serializer.data, status=status.HTTP_201_CREATED)
|
||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
else:
|
||||
return Response(
|
||||
{
|
||||
|
|
@ -302,9 +264,7 @@ class CycleAPIEndpoint(BaseAPIView):
|
|||
)
|
||||
|
||||
def patch(self, request, slug, project_id, pk):
|
||||
cycle = Cycle.objects.get(
|
||||
workspace__slug=slug, project_id=project_id, pk=pk
|
||||
)
|
||||
cycle = Cycle.objects.get(workspace__slug=slug, project_id=project_id, pk=pk)
|
||||
|
||||
current_instance = json.dumps(
|
||||
CycleSerializer(cycle).data, cls=DjangoJSONEncoder
|
||||
|
|
@ -322,9 +282,7 @@ class CycleAPIEndpoint(BaseAPIView):
|
|||
if "sort_order" in request_data:
|
||||
# Can only change sort order
|
||||
request_data = {
|
||||
"sort_order": request_data.get(
|
||||
"sort_order", cycle.sort_order
|
||||
)
|
||||
"sort_order": request_data.get("sort_order", cycle.sort_order)
|
||||
}
|
||||
else:
|
||||
return Response(
|
||||
|
|
@ -371,9 +329,7 @@ class CycleAPIEndpoint(BaseAPIView):
|
|||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
def delete(self, request, slug, project_id, pk):
|
||||
cycle = Cycle.objects.get(
|
||||
workspace__slug=slug, project_id=project_id, pk=pk
|
||||
)
|
||||
cycle = Cycle.objects.get(workspace__slug=slug, project_id=project_id, pk=pk)
|
||||
if cycle.owned_by_id != request.user.id and (
|
||||
not ProjectMember.objects.filter(
|
||||
workspace__slug=slug,
|
||||
|
|
@ -389,9 +345,9 @@ class CycleAPIEndpoint(BaseAPIView):
|
|||
)
|
||||
|
||||
cycle_issues = list(
|
||||
CycleIssue.objects.filter(
|
||||
cycle_id=self.kwargs.get("pk")
|
||||
).values_list("issue", flat=True)
|
||||
CycleIssue.objects.filter(cycle_id=self.kwargs.get("pk")).values_list(
|
||||
"issue", flat=True
|
||||
)
|
||||
)
|
||||
|
||||
issue_activity.delay(
|
||||
|
|
@ -413,17 +369,13 @@ class CycleAPIEndpoint(BaseAPIView):
|
|||
cycle.delete()
|
||||
# Delete the user favorite cycle
|
||||
UserFavorite.objects.filter(
|
||||
entity_type="cycle",
|
||||
entity_identifier=pk,
|
||||
project_id=project_id,
|
||||
entity_type="cycle", entity_identifier=pk, project_id=project_id
|
||||
).delete()
|
||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
|
||||
class CycleArchiveUnarchiveAPIEndpoint(BaseAPIView):
|
||||
permission_classes = [
|
||||
ProjectEntityPermission,
|
||||
]
|
||||
permission_classes = [ProjectEntityPermission]
|
||||
|
||||
def get_queryset(self):
|
||||
return (
|
||||
|
|
@ -502,9 +454,7 @@ class CycleArchiveUnarchiveAPIEndpoint(BaseAPIView):
|
|||
),
|
||||
)
|
||||
)
|
||||
.annotate(
|
||||
total_estimates=Sum("issue_cycle__issue__estimate_point")
|
||||
)
|
||||
.annotate(total_estimates=Sum("issue_cycle__issue__estimate_point"))
|
||||
.annotate(
|
||||
completed_estimates=Sum(
|
||||
"issue_cycle__issue__estimate_point",
|
||||
|
|
@ -536,10 +486,7 @@ class CycleArchiveUnarchiveAPIEndpoint(BaseAPIView):
|
|||
request=request,
|
||||
queryset=(self.get_queryset()),
|
||||
on_results=lambda cycles: CycleSerializer(
|
||||
cycles,
|
||||
many=True,
|
||||
fields=self.fields,
|
||||
expand=self.expand,
|
||||
cycles, many=True, fields=self.fields, expand=self.expand
|
||||
).data,
|
||||
)
|
||||
|
||||
|
|
@ -582,16 +529,12 @@ class CycleIssueAPIEndpoint(BaseAPIView):
|
|||
model = CycleIssue
|
||||
webhook_event = "cycle_issue"
|
||||
bulk = True
|
||||
permission_classes = [
|
||||
ProjectEntityPermission,
|
||||
]
|
||||
permission_classes = [ProjectEntityPermission]
|
||||
|
||||
def get_queryset(self):
|
||||
return (
|
||||
CycleIssue.objects.annotate(
|
||||
sub_issues_count=Issue.issue_objects.filter(
|
||||
parent=OuterRef("issue_id")
|
||||
)
|
||||
sub_issues_count=Issue.issue_objects.filter(parent=OuterRef("issue_id"))
|
||||
.order_by()
|
||||
.annotate(count=Func(F("id"), function="Count"))
|
||||
.values("count")
|
||||
|
|
@ -630,13 +573,10 @@ class CycleIssueAPIEndpoint(BaseAPIView):
|
|||
order_by = request.GET.get("order_by", "created_at")
|
||||
issues = (
|
||||
Issue.issue_objects.filter(
|
||||
issue_cycle__cycle_id=cycle_id,
|
||||
issue_cycle__deleted_at__isnull=True,
|
||||
issue_cycle__cycle_id=cycle_id, issue_cycle__deleted_at__isnull=True
|
||||
)
|
||||
.annotate(
|
||||
sub_issues_count=Issue.issue_objects.filter(
|
||||
parent=OuterRef("id")
|
||||
)
|
||||
sub_issues_count=Issue.issue_objects.filter(parent=OuterRef("id"))
|
||||
.order_by()
|
||||
.annotate(count=Func(F("id"), function="Count"))
|
||||
.values("count")
|
||||
|
|
@ -672,10 +612,7 @@ class CycleIssueAPIEndpoint(BaseAPIView):
|
|||
request=request,
|
||||
queryset=(issues),
|
||||
on_results=lambda issues: CycleSerializer(
|
||||
issues,
|
||||
many=True,
|
||||
fields=self.fields,
|
||||
expand=self.expand,
|
||||
issues, many=True, fields=self.fields, expand=self.expand
|
||||
).data,
|
||||
)
|
||||
|
||||
|
|
@ -684,8 +621,7 @@ class CycleIssueAPIEndpoint(BaseAPIView):
|
|||
|
||||
if not issues:
|
||||
return Response(
|
||||
{"error": "Issues are required"},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
{"error": "Issues are required"}, status=status.HTTP_400_BAD_REQUEST
|
||||
)
|
||||
|
||||
cycle = Cycle.objects.get(
|
||||
|
|
@ -694,9 +630,7 @@ class CycleIssueAPIEndpoint(BaseAPIView):
|
|||
|
||||
# Get all CycleIssues already created
|
||||
cycle_issues = list(
|
||||
CycleIssue.objects.filter(
|
||||
~Q(cycle_id=cycle_id), issue_id__in=issues
|
||||
)
|
||||
CycleIssue.objects.filter(~Q(cycle_id=cycle_id), issue_id__in=issues)
|
||||
)
|
||||
|
||||
existing_issues = [
|
||||
|
|
@ -741,9 +675,7 @@ class CycleIssueAPIEndpoint(BaseAPIView):
|
|||
)
|
||||
|
||||
# Update the cycle issues
|
||||
CycleIssue.objects.bulk_update(
|
||||
updated_records, ["cycle_id"], batch_size=100
|
||||
)
|
||||
CycleIssue.objects.bulk_update(updated_records, ["cycle_id"], batch_size=100)
|
||||
|
||||
# Capture Issue Activity
|
||||
issue_activity.delay(
|
||||
|
|
@ -802,9 +734,7 @@ class TransferCycleIssueAPIEndpoint(BaseAPIView):
|
|||
|
||||
"""
|
||||
|
||||
permission_classes = [
|
||||
ProjectEntityPermission,
|
||||
]
|
||||
permission_classes = [ProjectEntityPermission]
|
||||
|
||||
def post(self, request, slug, project_id, cycle_id):
|
||||
new_cycle_id = request.data.get("new_cycle_id", False)
|
||||
|
|
@ -930,9 +860,7 @@ class TransferCycleIssueAPIEndpoint(BaseAPIView):
|
|||
)
|
||||
.values("display_name", "assignee_id", "avatar", "avatar_url")
|
||||
.annotate(
|
||||
total_estimates=Sum(
|
||||
Cast("estimate_point__value", FloatField())
|
||||
)
|
||||
total_estimates=Sum(Cast("estimate_point__value", FloatField()))
|
||||
)
|
||||
.annotate(
|
||||
completed_estimates=Sum(
|
||||
|
|
@ -961,9 +889,7 @@ class TransferCycleIssueAPIEndpoint(BaseAPIView):
|
|||
{
|
||||
"display_name": item["display_name"],
|
||||
"assignee_id": (
|
||||
str(item["assignee_id"])
|
||||
if item["assignee_id"]
|
||||
else None
|
||||
str(item["assignee_id"]) if item["assignee_id"] else None
|
||||
),
|
||||
"avatar": item.get("avatar", None),
|
||||
"avatar_url": item.get("avatar_url", None),
|
||||
|
|
@ -986,9 +912,7 @@ class TransferCycleIssueAPIEndpoint(BaseAPIView):
|
|||
.annotate(label_id=F("labels__id"))
|
||||
.values("label_name", "color", "label_id")
|
||||
.annotate(
|
||||
total_estimates=Sum(
|
||||
Cast("estimate_point__value", FloatField())
|
||||
)
|
||||
total_estimates=Sum(Cast("estimate_point__value", FloatField()))
|
||||
)
|
||||
.annotate(
|
||||
completed_estimates=Sum(
|
||||
|
|
@ -1025,9 +949,7 @@ class TransferCycleIssueAPIEndpoint(BaseAPIView):
|
|||
{
|
||||
"label_name": item["label_name"],
|
||||
"color": item["color"],
|
||||
"label_id": (
|
||||
str(item["label_id"]) if item["label_id"] else None
|
||||
),
|
||||
"label_id": (str(item["label_id"]) if item["label_id"] else None),
|
||||
"total_estimates": item["total_estimates"],
|
||||
"completed_estimates": item["completed_estimates"],
|
||||
"pending_estimates": item["pending_estimates"],
|
||||
|
|
@ -1059,8 +981,7 @@ class TransferCycleIssueAPIEndpoint(BaseAPIView):
|
|||
),
|
||||
# If `avatar_asset` is None, fall back to using `avatar` field directly
|
||||
When(
|
||||
assignees__avatar_asset__isnull=True,
|
||||
then="assignees__avatar",
|
||||
assignees__avatar_asset__isnull=True, then="assignees__avatar"
|
||||
),
|
||||
default=Value(None),
|
||||
output_field=models.CharField(),
|
||||
|
|
@ -1069,12 +990,8 @@ class TransferCycleIssueAPIEndpoint(BaseAPIView):
|
|||
.values("display_name", "assignee_id", "avatar_url")
|
||||
.annotate(
|
||||
total_issues=Count(
|
||||
"id",
|
||||
filter=Q(
|
||||
archived_at__isnull=True,
|
||||
is_draft=False,
|
||||
),
|
||||
),
|
||||
"id", filter=Q(archived_at__isnull=True, is_draft=False)
|
||||
)
|
||||
)
|
||||
.annotate(
|
||||
completed_issues=Count(
|
||||
|
|
@ -1128,12 +1045,8 @@ class TransferCycleIssueAPIEndpoint(BaseAPIView):
|
|||
.values("label_name", "color", "label_id")
|
||||
.annotate(
|
||||
total_issues=Count(
|
||||
"id",
|
||||
filter=Q(
|
||||
archived_at__isnull=True,
|
||||
is_draft=False,
|
||||
),
|
||||
),
|
||||
"id", filter=Q(archived_at__isnull=True, is_draft=False)
|
||||
)
|
||||
)
|
||||
.annotate(
|
||||
completed_issues=Count(
|
||||
|
|
@ -1163,9 +1076,7 @@ class TransferCycleIssueAPIEndpoint(BaseAPIView):
|
|||
{
|
||||
"label_name": item["label_name"],
|
||||
"color": item["color"],
|
||||
"label_id": (
|
||||
str(item["label_id"]) if item["label_id"] else None
|
||||
),
|
||||
"label_id": (str(item["label_id"]) if item["label_id"] else None),
|
||||
"total_issues": item["total_issues"],
|
||||
"completed_issues": item["completed_issues"],
|
||||
"pending_issues": item["pending_issues"],
|
||||
|
|
@ -1210,10 +1121,7 @@ class TransferCycleIssueAPIEndpoint(BaseAPIView):
|
|||
}
|
||||
current_cycle.save(update_fields=["progress_snapshot"])
|
||||
|
||||
if (
|
||||
new_cycle.end_date is not None
|
||||
and new_cycle.end_date < timezone.now()
|
||||
):
|
||||
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"
|
||||
|
|
|
|||
|
|
@ -17,14 +17,7 @@ from rest_framework.response import Response
|
|||
from plane.api.serializers import IntakeIssueSerializer, IssueSerializer
|
||||
from plane.app.permissions import ProjectLitePermission
|
||||
from plane.bgtasks.issue_activities_task import issue_activity
|
||||
from plane.db.models import (
|
||||
Intake,
|
||||
IntakeIssue,
|
||||
Issue,
|
||||
Project,
|
||||
ProjectMember,
|
||||
State,
|
||||
)
|
||||
from plane.db.models import Intake, IntakeIssue, Issue, Project, ProjectMember, State
|
||||
|
||||
from .base import BaseAPIView
|
||||
|
||||
|
|
@ -36,16 +29,12 @@ class IntakeIssueAPIEndpoint(BaseAPIView):
|
|||
|
||||
"""
|
||||
|
||||
permission_classes = [
|
||||
ProjectLitePermission,
|
||||
]
|
||||
permission_classes = [ProjectLitePermission]
|
||||
|
||||
serializer_class = IntakeIssueSerializer
|
||||
model = IntakeIssue
|
||||
|
||||
filterset_fields = [
|
||||
"status",
|
||||
]
|
||||
filterset_fields = ["status"]
|
||||
|
||||
def get_queryset(self):
|
||||
intake = Intake.objects.filter(
|
||||
|
|
@ -54,8 +43,7 @@ class IntakeIssueAPIEndpoint(BaseAPIView):
|
|||
).first()
|
||||
|
||||
project = Project.objects.get(
|
||||
workspace__slug=self.kwargs.get("slug"),
|
||||
pk=self.kwargs.get("project_id"),
|
||||
workspace__slug=self.kwargs.get("slug"), pk=self.kwargs.get("project_id")
|
||||
)
|
||||
|
||||
if intake is None and not project.intake_view:
|
||||
|
|
@ -63,8 +51,7 @@ class IntakeIssueAPIEndpoint(BaseAPIView):
|
|||
|
||||
return (
|
||||
IntakeIssue.objects.filter(
|
||||
Q(snoozed_till__gte=timezone.now())
|
||||
| Q(snoozed_till__isnull=True),
|
||||
Q(snoozed_till__gte=timezone.now()) | Q(snoozed_till__isnull=True),
|
||||
workspace__slug=self.kwargs.get("slug"),
|
||||
project_id=self.kwargs.get("project_id"),
|
||||
intake_id=intake.id,
|
||||
|
|
@ -77,41 +64,29 @@ class IntakeIssueAPIEndpoint(BaseAPIView):
|
|||
if issue_id:
|
||||
intake_issue_queryset = self.get_queryset().get(issue_id=issue_id)
|
||||
intake_issue_data = IntakeIssueSerializer(
|
||||
intake_issue_queryset,
|
||||
fields=self.fields,
|
||||
expand=self.expand,
|
||||
intake_issue_queryset, fields=self.fields, expand=self.expand
|
||||
).data
|
||||
return Response(
|
||||
intake_issue_data,
|
||||
status=status.HTTP_200_OK,
|
||||
)
|
||||
return Response(intake_issue_data, status=status.HTTP_200_OK)
|
||||
issue_queryset = self.get_queryset()
|
||||
return self.paginate(
|
||||
request=request,
|
||||
queryset=(issue_queryset),
|
||||
on_results=lambda intake_issues: IntakeIssueSerializer(
|
||||
intake_issues,
|
||||
many=True,
|
||||
fields=self.fields,
|
||||
expand=self.expand,
|
||||
intake_issues, many=True, fields=self.fields, expand=self.expand
|
||||
).data,
|
||||
)
|
||||
|
||||
def post(self, request, slug, project_id):
|
||||
if not request.data.get("issue", {}).get("name", False):
|
||||
return Response(
|
||||
{"error": "Name is required"},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
{"error": "Name is required"}, status=status.HTTP_400_BAD_REQUEST
|
||||
)
|
||||
|
||||
intake = Intake.objects.filter(
|
||||
workspace__slug=slug, project_id=project_id
|
||||
).first()
|
||||
|
||||
project = Project.objects.get(
|
||||
workspace__slug=slug,
|
||||
pk=project_id,
|
||||
)
|
||||
project = Project.objects.get(workspace__slug=slug, pk=project_id)
|
||||
|
||||
# Intake view
|
||||
if intake is None and not project.intake_view:
|
||||
|
|
@ -131,8 +106,7 @@ class IntakeIssueAPIEndpoint(BaseAPIView):
|
|||
"none",
|
||||
]:
|
||||
return Response(
|
||||
{"error": "Invalid priority"},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
{"error": "Invalid priority"}, status=status.HTTP_400_BAD_REQUEST
|
||||
)
|
||||
|
||||
# Create or get state
|
||||
|
|
@ -184,10 +158,7 @@ class IntakeIssueAPIEndpoint(BaseAPIView):
|
|||
workspace__slug=slug, project_id=project_id
|
||||
).first()
|
||||
|
||||
project = Project.objects.get(
|
||||
workspace__slug=slug,
|
||||
pk=project_id,
|
||||
)
|
||||
project = Project.objects.get(workspace__slug=slug, pk=project_id)
|
||||
|
||||
# Intake view
|
||||
if intake is None and not project.intake_view:
|
||||
|
|
@ -234,7 +205,7 @@ class IntakeIssueAPIEndpoint(BaseAPIView):
|
|||
distinct=True,
|
||||
filter=Q(
|
||||
~Q(labels__id__isnull=True)
|
||||
& Q(label_issue__deleted_at__isnull=True),
|
||||
& Q(label_issue__deleted_at__isnull=True)
|
||||
),
|
||||
),
|
||||
Value([], output_field=ArrayField(UUIDField())),
|
||||
|
|
@ -251,11 +222,7 @@ class IntakeIssueAPIEndpoint(BaseAPIView):
|
|||
),
|
||||
Value([], output_field=ArrayField(UUIDField())),
|
||||
),
|
||||
).get(
|
||||
pk=issue_id,
|
||||
workspace__slug=slug,
|
||||
project_id=project_id,
|
||||
)
|
||||
).get(pk=issue_id, workspace__slug=slug, project_id=project_id)
|
||||
# Only allow guests to edit name and description
|
||||
if project_member.role <= 5:
|
||||
issue_data = {
|
||||
|
|
@ -263,14 +230,10 @@ class IntakeIssueAPIEndpoint(BaseAPIView):
|
|||
"description_html": issue_data.get(
|
||||
"description_html", issue.description_html
|
||||
),
|
||||
"description": issue_data.get(
|
||||
"description", issue.description
|
||||
),
|
||||
"description": issue_data.get("description", issue.description),
|
||||
}
|
||||
|
||||
issue_serializer = IssueSerializer(
|
||||
issue, data=issue_data, partial=True
|
||||
)
|
||||
issue_serializer = IssueSerializer(issue, data=issue_data, partial=True)
|
||||
|
||||
if issue_serializer.is_valid():
|
||||
current_instance = issue
|
||||
|
|
@ -310,14 +273,10 @@ class IntakeIssueAPIEndpoint(BaseAPIView):
|
|||
# Update the issue state if the issue is rejected or marked as duplicate
|
||||
if serializer.data["status"] in [-1, 2]:
|
||||
issue = Issue.objects.get(
|
||||
pk=issue_id,
|
||||
workspace__slug=slug,
|
||||
project_id=project_id,
|
||||
pk=issue_id, workspace__slug=slug, project_id=project_id
|
||||
)
|
||||
state = State.objects.filter(
|
||||
group="cancelled",
|
||||
workspace__slug=slug,
|
||||
project_id=project_id,
|
||||
group="cancelled", workspace__slug=slug, project_id=project_id
|
||||
).first()
|
||||
if state is not None:
|
||||
issue.state = state
|
||||
|
|
@ -326,18 +285,14 @@ class IntakeIssueAPIEndpoint(BaseAPIView):
|
|||
# Update the issue state if it is accepted
|
||||
if serializer.data["status"] in [1]:
|
||||
issue = Issue.objects.get(
|
||||
pk=issue_id,
|
||||
workspace__slug=slug,
|
||||
project_id=project_id,
|
||||
pk=issue_id, workspace__slug=slug, project_id=project_id
|
||||
)
|
||||
|
||||
# Update the issue state only if it is in triage state
|
||||
if issue.state.is_triage:
|
||||
# Move to default state
|
||||
state = State.objects.filter(
|
||||
workspace__slug=slug,
|
||||
project_id=project_id,
|
||||
default=True,
|
||||
workspace__slug=slug, project_id=project_id, default=True
|
||||
).first()
|
||||
if state is not None:
|
||||
issue.state = state
|
||||
|
|
@ -346,9 +301,7 @@ class IntakeIssueAPIEndpoint(BaseAPIView):
|
|||
# create a activity for status change
|
||||
issue_activity.delay(
|
||||
type="intake.activity.created",
|
||||
requested_data=json.dumps(
|
||||
request.data, cls=DjangoJSONEncoder
|
||||
),
|
||||
requested_data=json.dumps(request.data, cls=DjangoJSONEncoder),
|
||||
actor_id=str(request.user.id),
|
||||
issue_id=str(issue_id),
|
||||
project_id=str(project_id),
|
||||
|
|
@ -360,13 +313,10 @@ class IntakeIssueAPIEndpoint(BaseAPIView):
|
|||
)
|
||||
|
||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||
return Response(
|
||||
serializer.errors, status=status.HTTP_400_BAD_REQUEST
|
||||
)
|
||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
else:
|
||||
return Response(
|
||||
IntakeIssueSerializer(intake_issue).data,
|
||||
status=status.HTTP_200_OK,
|
||||
IntakeIssueSerializer(intake_issue).data, status=status.HTTP_200_OK
|
||||
)
|
||||
|
||||
def delete(self, request, slug, project_id, issue_id):
|
||||
|
|
@ -374,10 +324,7 @@ class IntakeIssueAPIEndpoint(BaseAPIView):
|
|||
workspace__slug=slug, project_id=project_id
|
||||
).first()
|
||||
|
||||
project = Project.objects.get(
|
||||
workspace__slug=slug,
|
||||
pk=project_id,
|
||||
)
|
||||
project = Project.objects.get(workspace__slug=slug, pk=project_id)
|
||||
|
||||
# Intake view
|
||||
if intake is None and not project.intake_view:
|
||||
|
|
|
|||
|
|
@ -73,9 +73,7 @@ class WorkspaceIssueAPIEndpoint(BaseAPIView):
|
|||
def get_queryset(self):
|
||||
return (
|
||||
Issue.issue_objects.annotate(
|
||||
sub_issues_count=Issue.issue_objects.filter(
|
||||
parent=OuterRef("id")
|
||||
)
|
||||
sub_issues_count=Issue.issue_objects.filter(parent=OuterRef("id"))
|
||||
.order_by()
|
||||
.annotate(count=Func(F("id"), function="Count"))
|
||||
.values("count")
|
||||
|
|
@ -91,14 +89,10 @@ class WorkspaceIssueAPIEndpoint(BaseAPIView):
|
|||
.order_by(self.kwargs.get("order_by", "-created_at"))
|
||||
).distinct()
|
||||
|
||||
def get(
|
||||
self, request, slug, project__identifier=None, issue__identifier=None
|
||||
):
|
||||
def get(self, request, slug, project__identifier=None, issue__identifier=None):
|
||||
if issue__identifier and project__identifier:
|
||||
issue = Issue.issue_objects.annotate(
|
||||
sub_issues_count=Issue.issue_objects.filter(
|
||||
parent=OuterRef("id")
|
||||
)
|
||||
sub_issues_count=Issue.issue_objects.filter(parent=OuterRef("id"))
|
||||
.order_by()
|
||||
.annotate(count=Func(F("id"), function="Count"))
|
||||
.values("count")
|
||||
|
|
@ -108,11 +102,7 @@ class WorkspaceIssueAPIEndpoint(BaseAPIView):
|
|||
sequence_id=issue__identifier,
|
||||
)
|
||||
return Response(
|
||||
IssueSerializer(
|
||||
issue,
|
||||
fields=self.fields,
|
||||
expand=self.expand,
|
||||
).data,
|
||||
IssueSerializer(issue, fields=self.fields, expand=self.expand).data,
|
||||
status=status.HTTP_200_OK,
|
||||
)
|
||||
|
||||
|
|
@ -126,17 +116,13 @@ class IssueAPIEndpoint(BaseAPIView):
|
|||
|
||||
model = Issue
|
||||
webhook_event = "issue"
|
||||
permission_classes = [
|
||||
ProjectEntityPermission,
|
||||
]
|
||||
permission_classes = [ProjectEntityPermission]
|
||||
serializer_class = IssueSerializer
|
||||
|
||||
def get_queryset(self):
|
||||
return (
|
||||
Issue.issue_objects.annotate(
|
||||
sub_issues_count=Issue.issue_objects.filter(
|
||||
parent=OuterRef("id")
|
||||
)
|
||||
sub_issues_count=Issue.issue_objects.filter(parent=OuterRef("id"))
|
||||
.order_by()
|
||||
.annotate(count=Func(F("id"), function="Count"))
|
||||
.values("count")
|
||||
|
|
@ -164,41 +150,25 @@ class IssueAPIEndpoint(BaseAPIView):
|
|||
project_id=project_id,
|
||||
)
|
||||
return Response(
|
||||
IssueSerializer(
|
||||
issue,
|
||||
fields=self.fields,
|
||||
expand=self.expand,
|
||||
).data,
|
||||
IssueSerializer(issue, fields=self.fields, expand=self.expand).data,
|
||||
status=status.HTTP_200_OK,
|
||||
)
|
||||
|
||||
if pk:
|
||||
issue = Issue.issue_objects.annotate(
|
||||
sub_issues_count=Issue.issue_objects.filter(
|
||||
parent=OuterRef("id")
|
||||
)
|
||||
sub_issues_count=Issue.issue_objects.filter(parent=OuterRef("id"))
|
||||
.order_by()
|
||||
.annotate(count=Func(F("id"), function="Count"))
|
||||
.values("count")
|
||||
).get(workspace__slug=slug, project_id=project_id, pk=pk)
|
||||
return Response(
|
||||
IssueSerializer(
|
||||
issue,
|
||||
fields=self.fields,
|
||||
expand=self.expand,
|
||||
).data,
|
||||
IssueSerializer(issue, fields=self.fields, expand=self.expand).data,
|
||||
status=status.HTTP_200_OK,
|
||||
)
|
||||
|
||||
# Custom ordering for priority and state
|
||||
priority_order = ["urgent", "high", "medium", "low", "none"]
|
||||
state_order = [
|
||||
"backlog",
|
||||
"unstarted",
|
||||
"started",
|
||||
"completed",
|
||||
"cancelled",
|
||||
]
|
||||
state_order = ["backlog", "unstarted", "started", "completed", "cancelled"]
|
||||
|
||||
order_by_param = request.GET.get("order_by", "-created_at")
|
||||
|
||||
|
|
@ -231,9 +201,7 @@ class IssueAPIEndpoint(BaseAPIView):
|
|||
# Priority Ordering
|
||||
if order_by_param == "priority" or order_by_param == "-priority":
|
||||
priority_order = (
|
||||
priority_order
|
||||
if order_by_param == "priority"
|
||||
else priority_order[::-1]
|
||||
priority_order if order_by_param == "priority" else priority_order[::-1]
|
||||
)
|
||||
issue_queryset = issue_queryset.annotate(
|
||||
priority_order=Case(
|
||||
|
|
@ -281,9 +249,7 @@ class IssueAPIEndpoint(BaseAPIView):
|
|||
else order_by_param
|
||||
)
|
||||
).order_by(
|
||||
"-max_values"
|
||||
if order_by_param.startswith("-")
|
||||
else "max_values"
|
||||
"-max_values" if order_by_param.startswith("-") else "max_values"
|
||||
)
|
||||
else:
|
||||
issue_queryset = issue_queryset.order_by(order_by_param)
|
||||
|
|
@ -292,10 +258,7 @@ class IssueAPIEndpoint(BaseAPIView):
|
|||
request=request,
|
||||
queryset=(issue_queryset),
|
||||
on_results=lambda issues: IssueSerializer(
|
||||
issues,
|
||||
many=True,
|
||||
fields=self.fields,
|
||||
expand=self.expand,
|
||||
issues, many=True, fields=self.fields, expand=self.expand
|
||||
).data,
|
||||
)
|
||||
|
||||
|
|
@ -339,22 +302,16 @@ class IssueAPIEndpoint(BaseAPIView):
|
|||
serializer.save()
|
||||
# Refetch the issue
|
||||
issue = Issue.objects.filter(
|
||||
workspace__slug=slug,
|
||||
project_id=project_id,
|
||||
pk=serializer.data["id"],
|
||||
workspace__slug=slug, project_id=project_id, pk=serializer.data["id"]
|
||||
).first()
|
||||
issue.created_at = request.data.get("created_at", timezone.now())
|
||||
issue.created_by_id = request.data.get(
|
||||
"created_by", request.user.id
|
||||
)
|
||||
issue.created_by_id = request.data.get("created_by", request.user.id)
|
||||
issue.save(update_fields=["created_at", "created_by"])
|
||||
|
||||
# Track the issue
|
||||
issue_activity.delay(
|
||||
type="issue.activity.created",
|
||||
requested_data=json.dumps(
|
||||
self.request.data, cls=DjangoJSONEncoder
|
||||
),
|
||||
requested_data=json.dumps(self.request.data, cls=DjangoJSONEncoder),
|
||||
actor_id=str(request.user.id),
|
||||
issue_id=str(serializer.data.get("id", None)),
|
||||
project_id=str(project_id),
|
||||
|
|
@ -391,9 +348,7 @@ class IssueAPIEndpoint(BaseAPIView):
|
|||
|
||||
# Get the requested data, encode it as django object and pass it
|
||||
# to serializer to validation
|
||||
requested_data = json.dumps(
|
||||
self.request.data, cls=DjangoJSONEncoder
|
||||
)
|
||||
requested_data = json.dumps(self.request.data, cls=DjangoJSONEncoder)
|
||||
serializer = IssueSerializer(
|
||||
issue,
|
||||
data=request.data,
|
||||
|
|
@ -451,9 +406,7 @@ class IssueAPIEndpoint(BaseAPIView):
|
|||
# If any of the created_at or created_by is present, update
|
||||
# the issue with the provided data, else return with the
|
||||
# default states given.
|
||||
issue.created_at = request.data.get(
|
||||
"created_at", timezone.now()
|
||||
)
|
||||
issue.created_at = request.data.get("created_at", timezone.now())
|
||||
issue.created_by_id = request.data.get(
|
||||
"created_by", request.user.id
|
||||
)
|
||||
|
|
@ -470,12 +423,8 @@ class IssueAPIEndpoint(BaseAPIView):
|
|||
current_instance=None,
|
||||
epoch=int(timezone.now().timestamp()),
|
||||
)
|
||||
return Response(
|
||||
serializer.data, status=status.HTTP_201_CREATED
|
||||
)
|
||||
return Response(
|
||||
serializer.errors, status=status.HTTP_400_BAD_REQUEST
|
||||
)
|
||||
return Response(serializer.data, status=status.HTTP_201_CREATED)
|
||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
else:
|
||||
return Response(
|
||||
{"error": "external_id and external_source are required"},
|
||||
|
|
@ -483,9 +432,7 @@ class IssueAPIEndpoint(BaseAPIView):
|
|||
)
|
||||
|
||||
def patch(self, request, slug, project_id, pk=None):
|
||||
issue = Issue.objects.get(
|
||||
workspace__slug=slug, project_id=project_id, pk=pk
|
||||
)
|
||||
issue = Issue.objects.get(workspace__slug=slug, project_id=project_id, pk=pk)
|
||||
project = Project.objects.get(pk=project_id)
|
||||
current_instance = json.dumps(
|
||||
IssueSerializer(issue).data, cls=DjangoJSONEncoder
|
||||
|
|
@ -494,10 +441,7 @@ class IssueAPIEndpoint(BaseAPIView):
|
|||
serializer = IssueSerializer(
|
||||
issue,
|
||||
data=request.data,
|
||||
context={
|
||||
"project_id": project_id,
|
||||
"workspace_id": project.workspace_id,
|
||||
},
|
||||
context={"project_id": project_id, "workspace_id": project.workspace_id},
|
||||
partial=True,
|
||||
)
|
||||
if serializer.is_valid():
|
||||
|
|
@ -535,9 +479,7 @@ class IssueAPIEndpoint(BaseAPIView):
|
|||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
def delete(self, request, slug, project_id, pk=None):
|
||||
issue = Issue.objects.get(
|
||||
workspace__slug=slug, project_id=project_id, pk=pk
|
||||
)
|
||||
issue = Issue.objects.get(workspace__slug=slug, project_id=project_id, pk=pk)
|
||||
if issue.created_by_id != request.user.id and (
|
||||
not ProjectMember.objects.filter(
|
||||
workspace__slug=slug,
|
||||
|
|
@ -576,9 +518,7 @@ class LabelAPIEndpoint(BaseAPIView):
|
|||
|
||||
serializer_class = LabelSerializer
|
||||
model = Label
|
||||
permission_classes = [
|
||||
ProjectMemberPermission,
|
||||
]
|
||||
permission_classes = [ProjectMemberPermission]
|
||||
|
||||
def get_queryset(self):
|
||||
return (
|
||||
|
|
@ -625,12 +565,8 @@ class LabelAPIEndpoint(BaseAPIView):
|
|||
)
|
||||
|
||||
serializer.save(project_id=project_id)
|
||||
return Response(
|
||||
serializer.data, status=status.HTTP_201_CREATED
|
||||
)
|
||||
return Response(
|
||||
serializer.errors, status=status.HTTP_400_BAD_REQUEST
|
||||
)
|
||||
return Response(serializer.data, status=status.HTTP_201_CREATED)
|
||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
except IntegrityError:
|
||||
label = Label.objects.filter(
|
||||
workspace__slug=slug,
|
||||
|
|
@ -651,18 +587,11 @@ class LabelAPIEndpoint(BaseAPIView):
|
|||
request=request,
|
||||
queryset=(self.get_queryset()),
|
||||
on_results=lambda labels: LabelSerializer(
|
||||
labels,
|
||||
many=True,
|
||||
fields=self.fields,
|
||||
expand=self.expand,
|
||||
labels, many=True, fields=self.fields, expand=self.expand
|
||||
).data,
|
||||
)
|
||||
label = self.get_queryset().get(pk=pk)
|
||||
serializer = LabelSerializer(
|
||||
label,
|
||||
fields=self.fields,
|
||||
expand=self.expand,
|
||||
)
|
||||
serializer = LabelSerializer(label, fields=self.fields, expand=self.expand)
|
||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||
|
||||
def patch(self, request, slug, project_id, pk=None):
|
||||
|
|
@ -705,9 +634,7 @@ class IssueLinkAPIEndpoint(BaseAPIView):
|
|||
|
||||
"""
|
||||
|
||||
permission_classes = [
|
||||
ProjectEntityPermission,
|
||||
]
|
||||
permission_classes = [ProjectEntityPermission]
|
||||
|
||||
model = IssueLink
|
||||
serializer_class = IssueLinkSerializer
|
||||
|
|
@ -730,46 +657,32 @@ class IssueLinkAPIEndpoint(BaseAPIView):
|
|||
if pk is None:
|
||||
issue_links = self.get_queryset()
|
||||
serializer = IssueLinkSerializer(
|
||||
issue_links,
|
||||
fields=self.fields,
|
||||
expand=self.expand,
|
||||
issue_links, fields=self.fields, expand=self.expand
|
||||
)
|
||||
return self.paginate(
|
||||
request=request,
|
||||
queryset=(self.get_queryset()),
|
||||
on_results=lambda issue_links: IssueLinkSerializer(
|
||||
issue_links,
|
||||
many=True,
|
||||
fields=self.fields,
|
||||
expand=self.expand,
|
||||
issue_links, many=True, fields=self.fields, expand=self.expand
|
||||
).data,
|
||||
)
|
||||
issue_link = self.get_queryset().get(pk=pk)
|
||||
serializer = IssueLinkSerializer(
|
||||
issue_link,
|
||||
fields=self.fields,
|
||||
expand=self.expand,
|
||||
issue_link, fields=self.fields, expand=self.expand
|
||||
)
|
||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||
|
||||
def post(self, request, slug, project_id, issue_id):
|
||||
serializer = IssueLinkSerializer(data=request.data)
|
||||
if serializer.is_valid():
|
||||
serializer.save(
|
||||
project_id=project_id,
|
||||
issue_id=issue_id,
|
||||
)
|
||||
serializer.save(project_id=project_id, issue_id=issue_id)
|
||||
|
||||
link = IssueLink.objects.get(pk=serializer.data["id"])
|
||||
link.created_by_id = request.data.get(
|
||||
"created_by", request.user.id
|
||||
)
|
||||
link.created_by_id = request.data.get("created_by", request.user.id)
|
||||
link.save(update_fields=["created_by"])
|
||||
issue_activity.delay(
|
||||
type="link.activity.created",
|
||||
requested_data=json.dumps(
|
||||
serializer.data, cls=DjangoJSONEncoder
|
||||
),
|
||||
requested_data=json.dumps(serializer.data, cls=DjangoJSONEncoder),
|
||||
issue_id=str(self.kwargs.get("issue_id")),
|
||||
project_id=str(self.kwargs.get("project_id")),
|
||||
actor_id=str(link.created_by_id),
|
||||
|
|
@ -781,19 +694,13 @@ class IssueLinkAPIEndpoint(BaseAPIView):
|
|||
|
||||
def patch(self, request, slug, project_id, issue_id, pk):
|
||||
issue_link = IssueLink.objects.get(
|
||||
workspace__slug=slug,
|
||||
project_id=project_id,
|
||||
issue_id=issue_id,
|
||||
pk=pk,
|
||||
workspace__slug=slug, project_id=project_id, issue_id=issue_id, pk=pk
|
||||
)
|
||||
requested_data = json.dumps(request.data, cls=DjangoJSONEncoder)
|
||||
current_instance = json.dumps(
|
||||
IssueLinkSerializer(issue_link).data,
|
||||
cls=DjangoJSONEncoder,
|
||||
)
|
||||
serializer = IssueLinkSerializer(
|
||||
issue_link, data=request.data, partial=True
|
||||
IssueLinkSerializer(issue_link).data, cls=DjangoJSONEncoder
|
||||
)
|
||||
serializer = IssueLinkSerializer(issue_link, data=request.data, partial=True)
|
||||
if serializer.is_valid():
|
||||
serializer.save()
|
||||
issue_activity.delay(
|
||||
|
|
@ -810,14 +717,10 @@ class IssueLinkAPIEndpoint(BaseAPIView):
|
|||
|
||||
def delete(self, request, slug, project_id, issue_id, pk):
|
||||
issue_link = IssueLink.objects.get(
|
||||
workspace__slug=slug,
|
||||
project_id=project_id,
|
||||
issue_id=issue_id,
|
||||
pk=pk,
|
||||
workspace__slug=slug, project_id=project_id, issue_id=issue_id, pk=pk
|
||||
)
|
||||
current_instance = json.dumps(
|
||||
IssueLinkSerializer(issue_link).data,
|
||||
cls=DjangoJSONEncoder,
|
||||
IssueLinkSerializer(issue_link).data, cls=DjangoJSONEncoder
|
||||
)
|
||||
issue_activity.delay(
|
||||
type="link.activity.deleted",
|
||||
|
|
@ -842,15 +745,11 @@ class IssueCommentAPIEndpoint(BaseAPIView):
|
|||
serializer_class = IssueCommentSerializer
|
||||
model = IssueComment
|
||||
webhook_event = "issue_comment"
|
||||
permission_classes = [
|
||||
ProjectLitePermission,
|
||||
]
|
||||
permission_classes = [ProjectLitePermission]
|
||||
|
||||
def get_queryset(self):
|
||||
return (
|
||||
IssueComment.objects.filter(
|
||||
workspace__slug=self.kwargs.get("slug")
|
||||
)
|
||||
IssueComment.objects.filter(workspace__slug=self.kwargs.get("slug"))
|
||||
.filter(project_id=self.kwargs.get("project_id"))
|
||||
.filter(issue_id=self.kwargs.get("issue_id"))
|
||||
.filter(
|
||||
|
|
@ -877,19 +776,14 @@ class IssueCommentAPIEndpoint(BaseAPIView):
|
|||
if pk:
|
||||
issue_comment = self.get_queryset().get(pk=pk)
|
||||
serializer = IssueCommentSerializer(
|
||||
issue_comment,
|
||||
fields=self.fields,
|
||||
expand=self.expand,
|
||||
issue_comment, fields=self.fields, expand=self.expand
|
||||
)
|
||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||
return self.paginate(
|
||||
request=request,
|
||||
queryset=(self.get_queryset()),
|
||||
on_results=lambda issue_comment: IssueCommentSerializer(
|
||||
issue_comment,
|
||||
many=True,
|
||||
fields=self.fields,
|
||||
expand=self.expand,
|
||||
issue_comment, many=True, fields=self.fields, expand=self.expand
|
||||
).data,
|
||||
)
|
||||
|
||||
|
|
@ -922,17 +816,11 @@ class IssueCommentAPIEndpoint(BaseAPIView):
|
|||
serializer = IssueCommentSerializer(data=request.data)
|
||||
if serializer.is_valid():
|
||||
serializer.save(
|
||||
project_id=project_id,
|
||||
issue_id=issue_id,
|
||||
actor=request.user,
|
||||
)
|
||||
issue_comment = IssueComment.objects.get(
|
||||
pk=serializer.data.get("id")
|
||||
project_id=project_id, issue_id=issue_id, actor=request.user
|
||||
)
|
||||
issue_comment = IssueComment.objects.get(pk=serializer.data.get("id"))
|
||||
# Update the created_at and the created_by and save the comment
|
||||
issue_comment.created_at = request.data.get(
|
||||
"created_at", timezone.now()
|
||||
)
|
||||
issue_comment.created_at = request.data.get("created_at", timezone.now())
|
||||
issue_comment.created_by_id = request.data.get(
|
||||
"created_by", request.user.id
|
||||
)
|
||||
|
|
@ -940,9 +828,7 @@ class IssueCommentAPIEndpoint(BaseAPIView):
|
|||
|
||||
issue_activity.delay(
|
||||
type="comment.activity.created",
|
||||
requested_data=json.dumps(
|
||||
serializer.data, cls=DjangoJSONEncoder
|
||||
),
|
||||
requested_data=json.dumps(serializer.data, cls=DjangoJSONEncoder),
|
||||
actor_id=str(issue_comment.created_by_id),
|
||||
issue_id=str(self.kwargs.get("issue_id")),
|
||||
project_id=str(self.kwargs.get("project_id")),
|
||||
|
|
@ -954,24 +840,17 @@ class IssueCommentAPIEndpoint(BaseAPIView):
|
|||
|
||||
def patch(self, request, slug, project_id, issue_id, pk):
|
||||
issue_comment = IssueComment.objects.get(
|
||||
workspace__slug=slug,
|
||||
project_id=project_id,
|
||||
issue_id=issue_id,
|
||||
pk=pk,
|
||||
workspace__slug=slug, project_id=project_id, issue_id=issue_id, pk=pk
|
||||
)
|
||||
requested_data = json.dumps(self.request.data, cls=DjangoJSONEncoder)
|
||||
current_instance = json.dumps(
|
||||
IssueCommentSerializer(issue_comment).data,
|
||||
cls=DjangoJSONEncoder,
|
||||
IssueCommentSerializer(issue_comment).data, cls=DjangoJSONEncoder
|
||||
)
|
||||
|
||||
# Validation check if the issue already exists
|
||||
if (
|
||||
request.data.get("external_id")
|
||||
and (
|
||||
issue_comment.external_id
|
||||
!= str(request.data.get("external_id"))
|
||||
)
|
||||
and (issue_comment.external_id != str(request.data.get("external_id")))
|
||||
and IssueComment.objects.filter(
|
||||
project_id=project_id,
|
||||
workspace__slug=slug,
|
||||
|
|
@ -1008,14 +887,10 @@ class IssueCommentAPIEndpoint(BaseAPIView):
|
|||
|
||||
def delete(self, request, slug, project_id, issue_id, pk):
|
||||
issue_comment = IssueComment.objects.get(
|
||||
workspace__slug=slug,
|
||||
project_id=project_id,
|
||||
issue_id=issue_id,
|
||||
pk=pk,
|
||||
workspace__slug=slug, project_id=project_id, issue_id=issue_id, pk=pk
|
||||
)
|
||||
current_instance = json.dumps(
|
||||
IssueCommentSerializer(issue_comment).data,
|
||||
cls=DjangoJSONEncoder,
|
||||
IssueCommentSerializer(issue_comment).data, cls=DjangoJSONEncoder
|
||||
)
|
||||
issue_comment.delete()
|
||||
issue_activity.delay(
|
||||
|
|
@ -1031,9 +906,7 @@ class IssueCommentAPIEndpoint(BaseAPIView):
|
|||
|
||||
|
||||
class IssueActivityAPIEndpoint(BaseAPIView):
|
||||
permission_classes = [
|
||||
ProjectEntityPermission,
|
||||
]
|
||||
permission_classes = [ProjectEntityPermission]
|
||||
|
||||
def get(self, request, slug, project_id, issue_id, pk=None):
|
||||
issue_activities = (
|
||||
|
|
@ -1058,19 +931,14 @@ class IssueActivityAPIEndpoint(BaseAPIView):
|
|||
request=request,
|
||||
queryset=(issue_activities),
|
||||
on_results=lambda issue_activity: IssueActivitySerializer(
|
||||
issue_activity,
|
||||
many=True,
|
||||
fields=self.fields,
|
||||
expand=self.expand,
|
||||
issue_activity, many=True, fields=self.fields, expand=self.expand
|
||||
).data,
|
||||
)
|
||||
|
||||
|
||||
class IssueAttachmentEndpoint(BaseAPIView):
|
||||
serializer_class = IssueAttachmentSerializer
|
||||
permission_classes = [
|
||||
ProjectEntityPermission,
|
||||
]
|
||||
permission_classes = [ProjectEntityPermission]
|
||||
model = FileAsset
|
||||
parser_classes = (MultiPartParser, FormParser)
|
||||
|
||||
|
|
@ -1109,10 +977,7 @@ class IssueAttachmentEndpoint(BaseAPIView):
|
|||
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(
|
||||
serializer.data,
|
||||
cls=DjangoJSONEncoder,
|
||||
),
|
||||
current_instance=json.dumps(serializer.data, cls=DjangoJSONEncoder),
|
||||
epoch=int(timezone.now().timestamp()),
|
||||
notification=True,
|
||||
origin=request.META.get("HTTP_ORIGIN"),
|
||||
|
|
|
|||
|
|
@ -13,24 +13,14 @@ from rest_framework import status
|
|||
# Module imports
|
||||
from .base import BaseAPIView
|
||||
from plane.api.serializers import UserLiteSerializer
|
||||
from plane.db.models import (
|
||||
User,
|
||||
Workspace,
|
||||
Project,
|
||||
WorkspaceMember,
|
||||
ProjectMember,
|
||||
)
|
||||
from plane.db.models import User, Workspace, Project, WorkspaceMember, ProjectMember
|
||||
|
||||
from plane.app.permissions import (
|
||||
ProjectMemberPermission,
|
||||
)
|
||||
from plane.app.permissions import ProjectMemberPermission
|
||||
|
||||
|
||||
# API endpoint to get and insert users inside the workspace
|
||||
class ProjectMemberAPIEndpoint(BaseAPIView):
|
||||
permission_classes = [
|
||||
ProjectMemberPermission,
|
||||
]
|
||||
permission_classes = [ProjectMemberPermission]
|
||||
|
||||
# Get all the users that are present inside the workspace
|
||||
def get(self, request, slug, project_id):
|
||||
|
|
@ -48,10 +38,7 @@ class ProjectMemberAPIEndpoint(BaseAPIView):
|
|||
|
||||
# Get all the users that are present inside the workspace
|
||||
users = UserLiteSerializer(
|
||||
User.objects.filter(
|
||||
id__in=project_members,
|
||||
),
|
||||
many=True,
|
||||
User.objects.filter(id__in=project_members), many=True
|
||||
).data
|
||||
|
||||
return Response(users, status=status.HTTP_200_OK)
|
||||
|
|
@ -78,8 +65,7 @@ class ProjectMemberAPIEndpoint(BaseAPIView):
|
|||
validate_email(email)
|
||||
except ValidationError:
|
||||
return Response(
|
||||
{"error": "Invalid email provided"},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
{"error": "Invalid email provided"}, status=status.HTTP_400_BAD_REQUEST
|
||||
)
|
||||
|
||||
workspace = Workspace.objects.filter(slug=slug).first()
|
||||
|
|
@ -108,9 +94,7 @@ class ProjectMemberAPIEndpoint(BaseAPIView):
|
|||
).first()
|
||||
if project_member:
|
||||
return Response(
|
||||
{
|
||||
"error": "User is already part of the workspace and project"
|
||||
},
|
||||
{"error": "User is already part of the workspace and project"},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
||||
|
|
@ -131,18 +115,14 @@ class ProjectMemberAPIEndpoint(BaseAPIView):
|
|||
# Create a workspace member for the user if not already a member
|
||||
if not workspace_member:
|
||||
workspace_member = WorkspaceMember.objects.create(
|
||||
workspace=workspace,
|
||||
member=user,
|
||||
role=request.data.get("role", 5),
|
||||
workspace=workspace, member=user, role=request.data.get("role", 5)
|
||||
)
|
||||
workspace_member.save()
|
||||
|
||||
# Create a project member for the user if not already a member
|
||||
if not project_member:
|
||||
project_member = ProjectMember.objects.create(
|
||||
project=project,
|
||||
member=user,
|
||||
role=request.data.get("role", 5),
|
||||
project=project, member=user, role=request.data.get("role", 5)
|
||||
)
|
||||
project_member.save()
|
||||
|
||||
|
|
@ -150,4 +130,3 @@ class ProjectMemberAPIEndpoint(BaseAPIView):
|
|||
user_data = UserLiteSerializer(user).data
|
||||
|
||||
return Response(user_data, status=status.HTTP_201_CREATED)
|
||||
|
||||
|
|
|
|||
|
|
@ -43,9 +43,7 @@ class ModuleAPIEndpoint(BaseAPIView):
|
|||
"""
|
||||
|
||||
model = Module
|
||||
permission_classes = [
|
||||
ProjectEntityPermission,
|
||||
]
|
||||
permission_classes = [ProjectEntityPermission]
|
||||
serializer_class = ModuleSerializer
|
||||
webhook_event = "module"
|
||||
|
||||
|
|
@ -60,9 +58,7 @@ class ModuleAPIEndpoint(BaseAPIView):
|
|||
.prefetch_related(
|
||||
Prefetch(
|
||||
"link_module",
|
||||
queryset=ModuleLink.objects.select_related(
|
||||
"module", "created_by"
|
||||
),
|
||||
queryset=ModuleLink.objects.select_related("module", "created_by"),
|
||||
)
|
||||
)
|
||||
.annotate(
|
||||
|
|
@ -74,7 +70,7 @@ class ModuleAPIEndpoint(BaseAPIView):
|
|||
issue_module__deleted_at__isnull=True,
|
||||
),
|
||||
distinct=True,
|
||||
),
|
||||
)
|
||||
)
|
||||
.annotate(
|
||||
completed_issues=Count(
|
||||
|
|
@ -143,10 +139,7 @@ class ModuleAPIEndpoint(BaseAPIView):
|
|||
project = Project.objects.get(pk=project_id, workspace__slug=slug)
|
||||
serializer = ModuleSerializer(
|
||||
data=request.data,
|
||||
context={
|
||||
"project_id": project_id,
|
||||
"workspace_id": project.workspace_id,
|
||||
},
|
||||
context={"project_id": project_id, "workspace_id": project.workspace_id},
|
||||
)
|
||||
if serializer.is_valid():
|
||||
if (
|
||||
|
|
@ -189,9 +182,7 @@ class ModuleAPIEndpoint(BaseAPIView):
|
|||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
def patch(self, request, slug, project_id, pk):
|
||||
module = Module.objects.get(
|
||||
pk=pk, project_id=project_id, workspace__slug=slug
|
||||
)
|
||||
module = Module.objects.get(pk=pk, project_id=project_id, workspace__slug=slug)
|
||||
|
||||
current_instance = json.dumps(
|
||||
ModuleSerializer(module).data, cls=DjangoJSONEncoder
|
||||
|
|
@ -203,10 +194,7 @@ class ModuleAPIEndpoint(BaseAPIView):
|
|||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
serializer = ModuleSerializer(
|
||||
module,
|
||||
data=request.data,
|
||||
context={"project_id": project_id},
|
||||
partial=True,
|
||||
module, data=request.data, context={"project_id": project_id}, partial=True
|
||||
)
|
||||
if serializer.is_valid():
|
||||
if (
|
||||
|
|
@ -246,33 +234,21 @@ class ModuleAPIEndpoint(BaseAPIView):
|
|||
|
||||
def get(self, request, slug, project_id, pk=None):
|
||||
if pk:
|
||||
queryset = (
|
||||
self.get_queryset().filter(archived_at__isnull=True).get(pk=pk)
|
||||
)
|
||||
queryset = self.get_queryset().filter(archived_at__isnull=True).get(pk=pk)
|
||||
data = ModuleSerializer(
|
||||
queryset,
|
||||
fields=self.fields,
|
||||
expand=self.expand,
|
||||
queryset, fields=self.fields, expand=self.expand
|
||||
).data
|
||||
return Response(
|
||||
data,
|
||||
status=status.HTTP_200_OK,
|
||||
)
|
||||
return Response(data, status=status.HTTP_200_OK)
|
||||
return self.paginate(
|
||||
request=request,
|
||||
queryset=(self.get_queryset().filter(archived_at__isnull=True)),
|
||||
on_results=lambda modules: ModuleSerializer(
|
||||
modules,
|
||||
many=True,
|
||||
fields=self.fields,
|
||||
expand=self.expand,
|
||||
modules, many=True, fields=self.fields, expand=self.expand
|
||||
).data,
|
||||
)
|
||||
|
||||
def delete(self, request, slug, project_id, pk):
|
||||
module = Module.objects.get(
|
||||
workspace__slug=slug, project_id=project_id, pk=pk
|
||||
)
|
||||
module = Module.objects.get(workspace__slug=slug, project_id=project_id, pk=pk)
|
||||
if module.created_by_id != request.user.id and (
|
||||
not ProjectMember.objects.filter(
|
||||
workspace__slug=slug,
|
||||
|
|
@ -288,9 +264,7 @@ class ModuleAPIEndpoint(BaseAPIView):
|
|||
)
|
||||
|
||||
module_issues = list(
|
||||
ModuleIssue.objects.filter(module_id=pk).values_list(
|
||||
"issue", flat=True
|
||||
)
|
||||
ModuleIssue.objects.filter(module_id=pk).values_list("issue", flat=True)
|
||||
)
|
||||
issue_activity.delay(
|
||||
type="module.activity.deleted",
|
||||
|
|
@ -304,24 +278,15 @@ class ModuleAPIEndpoint(BaseAPIView):
|
|||
actor_id=str(request.user.id),
|
||||
issue_id=None,
|
||||
project_id=str(project_id),
|
||||
current_instance=json.dumps(
|
||||
{
|
||||
"module_name": str(module.name),
|
||||
}
|
||||
),
|
||||
current_instance=json.dumps({"module_name": str(module.name)}),
|
||||
epoch=int(timezone.now().timestamp()),
|
||||
)
|
||||
module.delete()
|
||||
# Delete the module issues
|
||||
ModuleIssue.objects.filter(
|
||||
module=pk,
|
||||
project_id=project_id,
|
||||
).delete()
|
||||
ModuleIssue.objects.filter(module=pk, project_id=project_id).delete()
|
||||
# Delete the user favorite module
|
||||
UserFavorite.objects.filter(
|
||||
entity_type="module",
|
||||
entity_identifier=pk,
|
||||
project_id=project_id,
|
||||
entity_type="module", entity_identifier=pk, project_id=project_id
|
||||
).delete()
|
||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
|
|
@ -338,16 +303,12 @@ class ModuleIssueAPIEndpoint(BaseAPIView):
|
|||
webhook_event = "module_issue"
|
||||
bulk = True
|
||||
|
||||
permission_classes = [
|
||||
ProjectEntityPermission,
|
||||
]
|
||||
permission_classes = [ProjectEntityPermission]
|
||||
|
||||
def get_queryset(self):
|
||||
return (
|
||||
ModuleIssue.objects.annotate(
|
||||
sub_issues_count=Issue.issue_objects.filter(
|
||||
parent=OuterRef("issue")
|
||||
)
|
||||
sub_issues_count=Issue.issue_objects.filter(parent=OuterRef("issue"))
|
||||
.order_by()
|
||||
.annotate(count=Func(F("id"), function="Count"))
|
||||
.values("count")
|
||||
|
|
@ -374,13 +335,10 @@ class ModuleIssueAPIEndpoint(BaseAPIView):
|
|||
order_by = request.GET.get("order_by", "created_at")
|
||||
issues = (
|
||||
Issue.issue_objects.filter(
|
||||
issue_module__module_id=module_id,
|
||||
issue_module__deleted_at__isnull=True,
|
||||
issue_module__module_id=module_id, issue_module__deleted_at__isnull=True
|
||||
)
|
||||
.annotate(
|
||||
sub_issues_count=Issue.issue_objects.filter(
|
||||
parent=OuterRef("id")
|
||||
)
|
||||
sub_issues_count=Issue.issue_objects.filter(parent=OuterRef("id"))
|
||||
.order_by()
|
||||
.annotate(count=Func(F("id"), function="Count"))
|
||||
.values("count")
|
||||
|
|
@ -415,10 +373,7 @@ class ModuleIssueAPIEndpoint(BaseAPIView):
|
|||
request=request,
|
||||
queryset=(issues),
|
||||
on_results=lambda issues: IssueSerializer(
|
||||
issues,
|
||||
many=True,
|
||||
fields=self.fields,
|
||||
expand=self.expand,
|
||||
issues, many=True, fields=self.fields, expand=self.expand
|
||||
).data,
|
||||
)
|
||||
|
||||
|
|
@ -426,8 +381,7 @@ class ModuleIssueAPIEndpoint(BaseAPIView):
|
|||
issues = request.data.get("issues", [])
|
||||
if not len(issues):
|
||||
return Response(
|
||||
{"error": "Issues are required"},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
{"error": "Issues are required"}, status=status.HTTP_400_BAD_REQUEST
|
||||
)
|
||||
module = Module.objects.get(
|
||||
workspace__slug=slug, project_id=project_id, pk=module_id
|
||||
|
|
@ -474,16 +428,10 @@ class ModuleIssueAPIEndpoint(BaseAPIView):
|
|||
)
|
||||
|
||||
ModuleIssue.objects.bulk_create(
|
||||
record_to_create,
|
||||
batch_size=10,
|
||||
ignore_conflicts=True,
|
||||
record_to_create, batch_size=10, ignore_conflicts=True
|
||||
)
|
||||
|
||||
ModuleIssue.objects.bulk_update(
|
||||
records_to_update,
|
||||
["module"],
|
||||
batch_size=10,
|
||||
)
|
||||
ModuleIssue.objects.bulk_update(records_to_update, ["module"], batch_size=10)
|
||||
|
||||
# Capture Issue Activity
|
||||
issue_activity.delay(
|
||||
|
|
@ -519,10 +467,7 @@ class ModuleIssueAPIEndpoint(BaseAPIView):
|
|||
issue_activity.delay(
|
||||
type="module.activity.deleted",
|
||||
requested_data=json.dumps(
|
||||
{
|
||||
"module_id": str(module_id),
|
||||
"issues": [str(module_issue.issue_id)],
|
||||
}
|
||||
{"module_id": str(module_id), "issues": [str(module_issue.issue_id)]}
|
||||
),
|
||||
actor_id=str(request.user.id),
|
||||
issue_id=str(issue_id),
|
||||
|
|
@ -534,9 +479,7 @@ class ModuleIssueAPIEndpoint(BaseAPIView):
|
|||
|
||||
|
||||
class ModuleArchiveUnarchiveAPIEndpoint(BaseAPIView):
|
||||
permission_classes = [
|
||||
ProjectEntityPermission,
|
||||
]
|
||||
permission_classes = [ProjectEntityPermission]
|
||||
|
||||
def get_queryset(self):
|
||||
return (
|
||||
|
|
@ -550,9 +493,7 @@ class ModuleArchiveUnarchiveAPIEndpoint(BaseAPIView):
|
|||
.prefetch_related(
|
||||
Prefetch(
|
||||
"link_module",
|
||||
queryset=ModuleLink.objects.select_related(
|
||||
"module", "created_by"
|
||||
),
|
||||
queryset=ModuleLink.objects.select_related("module", "created_by"),
|
||||
)
|
||||
)
|
||||
.annotate(
|
||||
|
|
@ -564,7 +505,7 @@ class ModuleArchiveUnarchiveAPIEndpoint(BaseAPIView):
|
|||
issue_module__deleted_at__isnull=True,
|
||||
),
|
||||
distinct=True,
|
||||
),
|
||||
)
|
||||
)
|
||||
.annotate(
|
||||
completed_issues=Count(
|
||||
|
|
@ -634,22 +575,15 @@ class ModuleArchiveUnarchiveAPIEndpoint(BaseAPIView):
|
|||
request=request,
|
||||
queryset=(self.get_queryset()),
|
||||
on_results=lambda modules: ModuleSerializer(
|
||||
modules,
|
||||
many=True,
|
||||
fields=self.fields,
|
||||
expand=self.expand,
|
||||
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 = Module.objects.get(pk=pk, 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"
|
||||
},
|
||||
{"error": "Only completed or cancelled modules can be archived"},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
module.archived_at = timezone.now()
|
||||
|
|
@ -663,9 +597,7 @@ class ModuleArchiveUnarchiveAPIEndpoint(BaseAPIView):
|
|||
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 = 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)
|
||||
|
|
|
|||
|
|
@ -39,9 +39,7 @@ class ProjectAPIEndpoint(BaseAPIView):
|
|||
model = Project
|
||||
webhook_event = "project"
|
||||
|
||||
permission_classes = [
|
||||
ProjectBasePermission,
|
||||
]
|
||||
permission_classes = [ProjectBasePermission]
|
||||
|
||||
def get_queryset(self):
|
||||
return (
|
||||
|
|
@ -54,10 +52,7 @@ class ProjectAPIEndpoint(BaseAPIView):
|
|||
| Q(network=2)
|
||||
)
|
||||
.select_related(
|
||||
"workspace",
|
||||
"workspace__owner",
|
||||
"default_assignee",
|
||||
"project_lead",
|
||||
"workspace", "workspace__owner", "default_assignee", "project_lead"
|
||||
)
|
||||
.annotate(
|
||||
is_member=Exists(
|
||||
|
|
@ -71,9 +66,7 @@ class ProjectAPIEndpoint(BaseAPIView):
|
|||
)
|
||||
.annotate(
|
||||
total_members=ProjectMember.objects.filter(
|
||||
project_id=OuterRef("id"),
|
||||
member__is_bot=False,
|
||||
is_active=True,
|
||||
project_id=OuterRef("id"), member__is_bot=False, is_active=True
|
||||
)
|
||||
.order_by()
|
||||
.annotate(count=Func(F("id"), function="Count"))
|
||||
|
|
@ -125,8 +118,7 @@ class ProjectAPIEndpoint(BaseAPIView):
|
|||
Prefetch(
|
||||
"project_projectmember",
|
||||
queryset=ProjectMember.objects.filter(
|
||||
workspace__slug=slug,
|
||||
is_active=True,
|
||||
workspace__slug=slug, is_active=True
|
||||
).select_related("member"),
|
||||
)
|
||||
)
|
||||
|
|
@ -136,18 +128,11 @@ class ProjectAPIEndpoint(BaseAPIView):
|
|||
request=request,
|
||||
queryset=(projects),
|
||||
on_results=lambda projects: ProjectSerializer(
|
||||
projects,
|
||||
many=True,
|
||||
fields=self.fields,
|
||||
expand=self.expand,
|
||||
projects, many=True, fields=self.fields, expand=self.expand
|
||||
).data,
|
||||
)
|
||||
project = self.get_queryset().get(workspace__slug=slug, pk=pk)
|
||||
serializer = ProjectSerializer(
|
||||
project,
|
||||
fields=self.fields,
|
||||
expand=self.expand,
|
||||
)
|
||||
serializer = ProjectSerializer(project, fields=self.fields, expand=self.expand)
|
||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||
|
||||
def post(self, request, slug):
|
||||
|
|
@ -161,14 +146,11 @@ class ProjectAPIEndpoint(BaseAPIView):
|
|||
|
||||
# Add the user as Administrator to the project
|
||||
_ = ProjectMember.objects.create(
|
||||
project_id=serializer.data["id"],
|
||||
member=request.user,
|
||||
role=20,
|
||||
project_id=serializer.data["id"], member=request.user, role=20
|
||||
)
|
||||
# Also create the issue property for the user
|
||||
_ = IssueUserProperty.objects.create(
|
||||
project_id=serializer.data["id"],
|
||||
user=request.user,
|
||||
project_id=serializer.data["id"], user=request.user
|
||||
)
|
||||
|
||||
if serializer.data["project_lead"] is not None and str(
|
||||
|
|
@ -236,11 +218,7 @@ class ProjectAPIEndpoint(BaseAPIView):
|
|||
]
|
||||
)
|
||||
|
||||
project = (
|
||||
self.get_queryset()
|
||||
.filter(pk=serializer.data["id"])
|
||||
.first()
|
||||
)
|
||||
project = self.get_queryset().filter(pk=serializer.data["id"]).first()
|
||||
|
||||
# Model activity
|
||||
model_activity.delay(
|
||||
|
|
@ -254,13 +232,8 @@ class ProjectAPIEndpoint(BaseAPIView):
|
|||
)
|
||||
|
||||
serializer = ProjectSerializer(project)
|
||||
return Response(
|
||||
serializer.data, status=status.HTTP_201_CREATED
|
||||
)
|
||||
return Response(
|
||||
serializer.errors,
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
return Response(serializer.data, status=status.HTTP_201_CREATED)
|
||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
except IntegrityError as e:
|
||||
if "already exists" in str(e):
|
||||
return Response(
|
||||
|
|
@ -269,8 +242,7 @@ class ProjectAPIEndpoint(BaseAPIView):
|
|||
)
|
||||
except Workspace.DoesNotExist:
|
||||
return Response(
|
||||
{"error": "Workspace does not exist"},
|
||||
status=status.HTTP_404_NOT_FOUND,
|
||||
{"error": "Workspace does not exist"}, status=status.HTTP_404_NOT_FOUND
|
||||
)
|
||||
except ValidationError:
|
||||
return Response(
|
||||
|
|
@ -298,10 +270,7 @@ class ProjectAPIEndpoint(BaseAPIView):
|
|||
|
||||
serializer = ProjectSerializer(
|
||||
project,
|
||||
data={
|
||||
**request.data,
|
||||
"intake_view": intake_view,
|
||||
},
|
||||
data={**request.data, "intake_view": intake_view},
|
||||
context={"workspace_id": workspace.id},
|
||||
partial=True,
|
||||
)
|
||||
|
|
@ -310,8 +279,7 @@ class ProjectAPIEndpoint(BaseAPIView):
|
|||
serializer.save()
|
||||
if serializer.data["intake_view"]:
|
||||
intake = Intake.objects.filter(
|
||||
project=project,
|
||||
is_default=True,
|
||||
project=project, is_default=True
|
||||
).first()
|
||||
if not intake:
|
||||
Intake.objects.create(
|
||||
|
|
@ -330,11 +298,7 @@ class ProjectAPIEndpoint(BaseAPIView):
|
|||
is_triage=True,
|
||||
)
|
||||
|
||||
project = (
|
||||
self.get_queryset()
|
||||
.filter(pk=serializer.data["id"])
|
||||
.first()
|
||||
)
|
||||
project = self.get_queryset().filter(pk=serializer.data["id"]).first()
|
||||
|
||||
model_activity.delay(
|
||||
model_name="project",
|
||||
|
|
@ -348,9 +312,7 @@ class ProjectAPIEndpoint(BaseAPIView):
|
|||
|
||||
serializer = ProjectSerializer(project)
|
||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||
return Response(
|
||||
serializer.errors, status=status.HTTP_400_BAD_REQUEST
|
||||
)
|
||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
except IntegrityError as e:
|
||||
if "already exists" in str(e):
|
||||
return Response(
|
||||
|
|
@ -359,8 +321,7 @@ class ProjectAPIEndpoint(BaseAPIView):
|
|||
)
|
||||
except (Project.DoesNotExist, Workspace.DoesNotExist):
|
||||
return Response(
|
||||
{"error": "Project does not exist"},
|
||||
status=status.HTTP_404_NOT_FOUND,
|
||||
{"error": "Project does not exist"}, status=status.HTTP_404_NOT_FOUND
|
||||
)
|
||||
except ValidationError:
|
||||
return Response(
|
||||
|
|
@ -372,28 +333,20 @@ class ProjectAPIEndpoint(BaseAPIView):
|
|||
project = Project.objects.get(pk=pk, workspace__slug=slug)
|
||||
# Delete the user favorite cycle
|
||||
UserFavorite.objects.filter(
|
||||
entity_type="project",
|
||||
entity_identifier=pk,
|
||||
project_id=pk,
|
||||
entity_type="project", entity_identifier=pk, project_id=pk
|
||||
).delete()
|
||||
project.delete()
|
||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
|
||||
class ProjectArchiveUnarchiveAPIEndpoint(BaseAPIView):
|
||||
|
||||
permission_classes = [
|
||||
ProjectBasePermission,
|
||||
]
|
||||
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()
|
||||
UserFavorite.objects.filter(
|
||||
workspace__slug=slug,
|
||||
project=project_id,
|
||||
).delete()
|
||||
UserFavorite.objects.filter(workspace__slug=slug, project=project_id).delete()
|
||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
def delete(self, request, slug, project_id):
|
||||
|
|
|
|||
|
|
@ -16,9 +16,7 @@ from .base import BaseAPIView
|
|||
class StateAPIEndpoint(BaseAPIView):
|
||||
serializer_class = StateSerializer
|
||||
model = State
|
||||
permission_classes = [
|
||||
ProjectEntityPermission,
|
||||
]
|
||||
permission_classes = [ProjectEntityPermission]
|
||||
|
||||
def get_queryset(self):
|
||||
return (
|
||||
|
|
@ -67,9 +65,7 @@ class StateAPIEndpoint(BaseAPIView):
|
|||
|
||||
serializer.save(project_id=project_id)
|
||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||
return Response(
|
||||
serializer.errors, status=status.HTTP_400_BAD_REQUEST
|
||||
)
|
||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
except IntegrityError:
|
||||
state = State.objects.filter(
|
||||
workspace__slug=slug,
|
||||
|
|
@ -96,19 +92,13 @@ class StateAPIEndpoint(BaseAPIView):
|
|||
request=request,
|
||||
queryset=(self.get_queryset()),
|
||||
on_results=lambda states: StateSerializer(
|
||||
states,
|
||||
many=True,
|
||||
fields=self.fields,
|
||||
expand=self.expand,
|
||||
states, many=True, fields=self.fields, expand=self.expand
|
||||
).data,
|
||||
)
|
||||
|
||||
def delete(self, request, slug, project_id, state_id):
|
||||
state = State.objects.get(
|
||||
is_triage=False,
|
||||
pk=state_id,
|
||||
project_id=project_id,
|
||||
workspace__slug=slug,
|
||||
is_triage=False, pk=state_id, project_id=project_id, workspace__slug=slug
|
||||
)
|
||||
|
||||
if state.default:
|
||||
|
|
@ -122,9 +112,7 @@ class StateAPIEndpoint(BaseAPIView):
|
|||
|
||||
if issue_exist:
|
||||
return Response(
|
||||
{
|
||||
"error": "The state is not empty, only empty states can be deleted"
|
||||
},
|
||||
{"error": "The state is not empty, only empty states can be deleted"},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -25,10 +25,7 @@ class APIKeyAuthentication(authentication.BaseAuthentication):
|
|||
def validate_api_token(self, token):
|
||||
try:
|
||||
api_token = APIToken.objects.get(
|
||||
Q(
|
||||
Q(expired_at__gt=timezone.now())
|
||||
| Q(expired_at__isnull=True)
|
||||
),
|
||||
Q(Q(expired_at__gt=timezone.now()) | Q(expired_at__isnull=True)),
|
||||
token=token,
|
||||
is_active=True,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -12,4 +12,4 @@ from .project import (
|
|||
ProjectMemberPermission,
|
||||
ProjectLitePermission,
|
||||
)
|
||||
from .base import allow_permission, ROLE
|
||||
from .base import allow_permission, ROLE
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ from rest_framework import status
|
|||
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class ROLE(Enum):
|
||||
ADMIN = 20
|
||||
MEMBER = 15
|
||||
|
|
@ -15,7 +16,6 @@ def allow_permission(allowed_roles, level="PROJECT", creator=False, model=None):
|
|||
def decorator(view_func):
|
||||
@wraps(view_func)
|
||||
def _wrapped_view(instance, request, *args, **kwargs):
|
||||
|
||||
# Check for creator if required
|
||||
if creator and model:
|
||||
obj = model.objects.filter(
|
||||
|
|
@ -26,8 +26,7 @@ def allow_permission(allowed_roles, level="PROJECT", creator=False, model=None):
|
|||
|
||||
# Convert allowed_roles to their values if they are enum members
|
||||
allowed_role_values = [
|
||||
role.value if isinstance(role, ROLE) else role
|
||||
for role in allowed_roles
|
||||
role.value if isinstance(role, ROLE) else role for role in allowed_roles
|
||||
]
|
||||
|
||||
# Check role permissions
|
||||
|
|
|
|||
|
|
@ -18,9 +18,7 @@ class ProjectBasePermission(BasePermission):
|
|||
## Safe Methods -> Handle the filtering logic in queryset
|
||||
if request.method in SAFE_METHODS:
|
||||
return WorkspaceMember.objects.filter(
|
||||
workspace__slug=view.workspace_slug,
|
||||
member=request.user,
|
||||
is_active=True,
|
||||
workspace__slug=view.workspace_slug, member=request.user, is_active=True
|
||||
).exists()
|
||||
|
||||
## Only workspace owners or admins can create the projects
|
||||
|
|
@ -50,9 +48,7 @@ class ProjectMemberPermission(BasePermission):
|
|||
## Safe Methods -> Handle the filtering logic in queryset
|
||||
if request.method in SAFE_METHODS:
|
||||
return ProjectMember.objects.filter(
|
||||
workspace__slug=view.workspace_slug,
|
||||
member=request.user,
|
||||
is_active=True,
|
||||
workspace__slug=view.workspace_slug, member=request.user, is_active=True
|
||||
).exists()
|
||||
## Only workspace owners or admins can create the projects
|
||||
if request.method == "POST":
|
||||
|
|
|
|||
|
|
@ -50,9 +50,7 @@ class WorkspaceOwnerPermission(BasePermission):
|
|||
return False
|
||||
|
||||
return WorkspaceMember.objects.filter(
|
||||
workspace__slug=view.workspace_slug,
|
||||
member=request.user,
|
||||
role=Admin,
|
||||
workspace__slug=view.workspace_slug, member=request.user, role=Admin
|
||||
).exists()
|
||||
|
||||
|
||||
|
|
@ -77,9 +75,7 @@ class WorkspaceEntityPermission(BasePermission):
|
|||
## Safe Methods -> Handle the filtering logic in queryset
|
||||
if request.method in SAFE_METHODS:
|
||||
return WorkspaceMember.objects.filter(
|
||||
workspace__slug=view.workspace_slug,
|
||||
member=request.user,
|
||||
is_active=True,
|
||||
workspace__slug=view.workspace_slug, member=request.user, is_active=True
|
||||
).exists()
|
||||
|
||||
return WorkspaceMember.objects.filter(
|
||||
|
|
@ -96,9 +92,7 @@ class WorkspaceViewerPermission(BasePermission):
|
|||
return False
|
||||
|
||||
return WorkspaceMember.objects.filter(
|
||||
member=request.user,
|
||||
workspace__slug=view.workspace_slug,
|
||||
is_active=True,
|
||||
member=request.user, workspace__slug=view.workspace_slug, is_active=True
|
||||
).exists()
|
||||
|
||||
|
||||
|
|
@ -108,7 +102,5 @@ class WorkspaceUserPermission(BasePermission):
|
|||
return False
|
||||
|
||||
return WorkspaceMember.objects.filter(
|
||||
member=request.user,
|
||||
workspace__slug=view.workspace_slug,
|
||||
is_active=True,
|
||||
member=request.user, workspace__slug=view.workspace_slug, is_active=True
|
||||
).exists()
|
||||
|
|
|
|||
|
|
@ -36,9 +36,7 @@ from .project import (
|
|||
ProjectMemberRoleSerializer,
|
||||
)
|
||||
from .state import StateSerializer, StateLiteSerializer
|
||||
from .view import (
|
||||
IssueViewSerializer,
|
||||
)
|
||||
from .view import IssueViewSerializer
|
||||
from .cycle import (
|
||||
CycleSerializer,
|
||||
CycleIssueSerializer,
|
||||
|
|
@ -112,10 +110,7 @@ from .intake import (
|
|||
|
||||
from .analytic import AnalyticViewSerializer
|
||||
|
||||
from .notification import (
|
||||
NotificationSerializer,
|
||||
UserNotificationPreferenceSerializer,
|
||||
)
|
||||
from .notification import NotificationSerializer, UserNotificationPreferenceSerializer
|
||||
|
||||
from .exporter import ExporterHistorySerializer
|
||||
|
||||
|
|
|
|||
|
|
@ -7,10 +7,7 @@ class AnalyticViewSerializer(BaseSerializer):
|
|||
class Meta:
|
||||
model = AnalyticView
|
||||
fields = "__all__"
|
||||
read_only_fields = [
|
||||
"workspace",
|
||||
"query",
|
||||
]
|
||||
read_only_fields = ["workspace", "query"]
|
||||
|
||||
def create(self, validated_data):
|
||||
query_params = validated_data.get("query_dict", {})
|
||||
|
|
|
|||
|
|
@ -6,9 +6,4 @@ class FileAssetSerializer(BaseSerializer):
|
|||
class Meta:
|
||||
model = FileAsset
|
||||
fields = "__all__"
|
||||
read_only_fields = [
|
||||
"created_by",
|
||||
"updated_by",
|
||||
"created_at",
|
||||
"updated_at",
|
||||
]
|
||||
read_only_fields = ["created_by", "updated_by", "created_at", "updated_at"]
|
||||
|
|
|
|||
|
|
@ -178,15 +178,10 @@ class DynamicBaseSerializer(BaseSerializer):
|
|||
response[expand] = exp_serializer.data
|
||||
else:
|
||||
# You might need to handle this case differently
|
||||
response[expand] = getattr(
|
||||
instance, f"{expand}_id", None
|
||||
)
|
||||
response[expand] = getattr(instance, f"{expand}_id", None)
|
||||
|
||||
# Check if issue_attachments is in fields or expand
|
||||
if (
|
||||
"issue_attachments" in self.fields
|
||||
or "issue_attachments" in self.expand
|
||||
):
|
||||
if "issue_attachments" in self.fields or "issue_attachments" in self.expand:
|
||||
# Import the model here to avoid circular imports
|
||||
from plane.db.models import FileAsset
|
||||
|
||||
|
|
@ -199,11 +194,9 @@ class DynamicBaseSerializer(BaseSerializer):
|
|||
entity_type=FileAsset.EntityTypeContext.ISSUE_ATTACHMENT,
|
||||
)
|
||||
# Serialize issue_attachments and add them to the response
|
||||
response["issue_attachments"] = (
|
||||
IssueAttachmentLiteSerializer(
|
||||
issue_attachments, many=True
|
||||
).data
|
||||
)
|
||||
response["issue_attachments"] = IssueAttachmentLiteSerializer(
|
||||
issue_attachments, many=True
|
||||
).data
|
||||
else:
|
||||
response["issue_attachments"] = []
|
||||
|
||||
|
|
|
|||
|
|
@ -4,11 +4,7 @@ from rest_framework import serializers
|
|||
# Module imports
|
||||
from .base import BaseSerializer
|
||||
from .issue import IssueStateSerializer
|
||||
from plane.db.models import (
|
||||
Cycle,
|
||||
CycleIssue,
|
||||
CycleUserProperties,
|
||||
)
|
||||
from plane.db.models import Cycle, CycleIssue, CycleUserProperties
|
||||
|
||||
|
||||
class CycleWriteSerializer(BaseSerializer):
|
||||
|
|
@ -18,20 +14,13 @@ class CycleWriteSerializer(BaseSerializer):
|
|||
and data.get("end_date", None) is not None
|
||||
and data.get("start_date", None) > data.get("end_date", None)
|
||||
):
|
||||
raise serializers.ValidationError(
|
||||
"Start date cannot exceed end date"
|
||||
)
|
||||
raise serializers.ValidationError("Start date cannot exceed end date")
|
||||
return data
|
||||
|
||||
class Meta:
|
||||
model = Cycle
|
||||
fields = "__all__"
|
||||
read_only_fields = [
|
||||
"workspace",
|
||||
"project",
|
||||
"owned_by",
|
||||
"archived_at",
|
||||
]
|
||||
read_only_fields = ["workspace", "project", "owned_by", "archived_at"]
|
||||
|
||||
|
||||
class CycleSerializer(BaseSerializer):
|
||||
|
|
@ -87,18 +76,11 @@ class CycleIssueSerializer(BaseSerializer):
|
|||
class Meta:
|
||||
model = CycleIssue
|
||||
fields = "__all__"
|
||||
read_only_fields = [
|
||||
"workspace",
|
||||
"project",
|
||||
"cycle",
|
||||
]
|
||||
read_only_fields = ["workspace", "project", "cycle"]
|
||||
|
||||
|
||||
class CycleUserPropertiesSerializer(BaseSerializer):
|
||||
class Meta:
|
||||
model = CycleUserProperties
|
||||
fields = "__all__"
|
||||
read_only_fields = [
|
||||
"workspace",
|
||||
"project",
|
||||
"cycle" "user",
|
||||
]
|
||||
read_only_fields = ["workspace", "project", "cycle" "user"]
|
||||
|
|
|
|||
|
|
@ -22,16 +22,10 @@ from plane.db.models import (
|
|||
class DraftIssueCreateSerializer(BaseSerializer):
|
||||
# ids
|
||||
state_id = serializers.PrimaryKeyRelatedField(
|
||||
source="state",
|
||||
queryset=State.objects.all(),
|
||||
required=False,
|
||||
allow_null=True,
|
||||
source="state", queryset=State.objects.all(), required=False, allow_null=True
|
||||
)
|
||||
parent_id = serializers.PrimaryKeyRelatedField(
|
||||
source="parent",
|
||||
queryset=Issue.objects.all(),
|
||||
required=False,
|
||||
allow_null=True,
|
||||
source="parent", queryset=Issue.objects.all(), required=False, allow_null=True
|
||||
)
|
||||
label_ids = serializers.ListField(
|
||||
child=serializers.PrimaryKeyRelatedField(queryset=Label.objects.all()),
|
||||
|
|
@ -69,9 +63,7 @@ class DraftIssueCreateSerializer(BaseSerializer):
|
|||
and data.get("target_date", None) is not None
|
||||
and data.get("start_date", None) > data.get("target_date", None)
|
||||
):
|
||||
raise serializers.ValidationError(
|
||||
"Start date cannot exceed target date"
|
||||
)
|
||||
raise serializers.ValidationError("Start date cannot exceed target date")
|
||||
return data
|
||||
|
||||
def create(self, validated_data):
|
||||
|
|
@ -86,9 +78,7 @@ class DraftIssueCreateSerializer(BaseSerializer):
|
|||
|
||||
# Create Issue
|
||||
issue = DraftIssue.objects.create(
|
||||
**validated_data,
|
||||
workspace_id=workspace_id,
|
||||
project_id=project_id,
|
||||
**validated_data, workspace_id=workspace_id, project_id=project_id
|
||||
)
|
||||
|
||||
# Issue Audit Users
|
||||
|
|
@ -239,20 +229,11 @@ class DraftIssueCreateSerializer(BaseSerializer):
|
|||
class DraftIssueSerializer(BaseSerializer):
|
||||
# ids
|
||||
cycle_id = serializers.PrimaryKeyRelatedField(read_only=True)
|
||||
module_ids = serializers.ListField(
|
||||
child=serializers.UUIDField(),
|
||||
required=False,
|
||||
)
|
||||
module_ids = serializers.ListField(child=serializers.UUIDField(), required=False)
|
||||
|
||||
# Many to many
|
||||
label_ids = serializers.ListField(
|
||||
child=serializers.UUIDField(),
|
||||
required=False,
|
||||
)
|
||||
assignee_ids = serializers.ListField(
|
||||
child=serializers.UUIDField(),
|
||||
required=False,
|
||||
)
|
||||
label_ids = serializers.ListField(child=serializers.UUIDField(), required=False)
|
||||
assignee_ids = serializers.ListField(child=serializers.UUIDField(), required=False)
|
||||
|
||||
class Meta:
|
||||
model = DraftIssue
|
||||
|
|
@ -286,7 +267,5 @@ class DraftIssueDetailSerializer(DraftIssueSerializer):
|
|||
description_html = serializers.CharField()
|
||||
|
||||
class Meta(DraftIssueSerializer.Meta):
|
||||
fields = DraftIssueSerializer.Meta.fields + [
|
||||
"description_html",
|
||||
]
|
||||
fields = DraftIssueSerializer.Meta.fields + ["description_html"]
|
||||
read_only_fields = fields
|
||||
|
|
|
|||
|
|
@ -7,14 +7,10 @@ from rest_framework import serializers
|
|||
|
||||
|
||||
class EstimateSerializer(BaseSerializer):
|
||||
|
||||
class Meta:
|
||||
model = Estimate
|
||||
fields = "__all__"
|
||||
read_only_fields = [
|
||||
"workspace",
|
||||
"project",
|
||||
]
|
||||
read_only_fields = ["workspace", "project"]
|
||||
|
||||
|
||||
class EstimatePointSerializer(BaseSerializer):
|
||||
|
|
@ -23,19 +19,13 @@ class EstimatePointSerializer(BaseSerializer):
|
|||
raise serializers.ValidationError("Estimate points are required")
|
||||
value = data.get("value")
|
||||
if value and len(value) > 20:
|
||||
raise serializers.ValidationError(
|
||||
"Value can't be more than 20 characters"
|
||||
)
|
||||
raise serializers.ValidationError("Value can't be more than 20 characters")
|
||||
return data
|
||||
|
||||
class Meta:
|
||||
model = EstimatePoint
|
||||
fields = "__all__"
|
||||
read_only_fields = [
|
||||
"estimate",
|
||||
"workspace",
|
||||
"project",
|
||||
]
|
||||
read_only_fields = ["estimate", "workspace", "project"]
|
||||
|
||||
|
||||
class EstimateReadSerializer(BaseSerializer):
|
||||
|
|
@ -44,11 +34,7 @@ class EstimateReadSerializer(BaseSerializer):
|
|||
class Meta:
|
||||
model = Estimate
|
||||
fields = "__all__"
|
||||
read_only_fields = [
|
||||
"points",
|
||||
"name",
|
||||
"description",
|
||||
]
|
||||
read_only_fields = ["points", "name", "description"]
|
||||
|
||||
|
||||
class WorkspaceEstimateSerializer(BaseSerializer):
|
||||
|
|
@ -57,8 +43,4 @@ class WorkspaceEstimateSerializer(BaseSerializer):
|
|||
class Meta:
|
||||
model = Estimate
|
||||
fields = "__all__"
|
||||
read_only_fields = [
|
||||
"points",
|
||||
"name",
|
||||
"description",
|
||||
]
|
||||
read_only_fields = ["points", "name", "description"]
|
||||
|
|
|
|||
|
|
@ -5,9 +5,7 @@ from .user import UserLiteSerializer
|
|||
|
||||
|
||||
class ExporterHistorySerializer(BaseSerializer):
|
||||
initiated_by_detail = UserLiteSerializer(
|
||||
source="initiated_by", read_only=True
|
||||
)
|
||||
initiated_by_detail = UserLiteSerializer(source="initiated_by", read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = ExporterHistory
|
||||
|
|
|
|||
|
|
@ -1,18 +1,9 @@
|
|||
from rest_framework import serializers
|
||||
|
||||
from plane.db.models import (
|
||||
UserFavorite,
|
||||
Cycle,
|
||||
Module,
|
||||
Issue,
|
||||
IssueView,
|
||||
Page,
|
||||
Project,
|
||||
)
|
||||
from plane.db.models import UserFavorite, Cycle, Module, Issue, IssueView, Page, Project
|
||||
|
||||
|
||||
class ProjectFavoriteLiteSerializer(serializers.ModelSerializer):
|
||||
|
||||
class Meta:
|
||||
model = Project
|
||||
fields = ["id", "name", "logo_props"]
|
||||
|
|
@ -33,21 +24,18 @@ class PageFavoriteLiteSerializer(serializers.ModelSerializer):
|
|||
|
||||
|
||||
class CycleFavoriteLiteSerializer(serializers.ModelSerializer):
|
||||
|
||||
class Meta:
|
||||
model = Cycle
|
||||
fields = ["id", "name", "logo_props", "project_id"]
|
||||
|
||||
|
||||
class ModuleFavoriteLiteSerializer(serializers.ModelSerializer):
|
||||
|
||||
class Meta:
|
||||
model = Module
|
||||
fields = ["id", "name", "logo_props", "project_id"]
|
||||
|
||||
|
||||
class ViewFavoriteSerializer(serializers.ModelSerializer):
|
||||
|
||||
class Meta:
|
||||
model = IssueView
|
||||
fields = ["id", "name", "logo_props", "project_id"]
|
||||
|
|
@ -89,9 +77,7 @@ class UserFavoriteSerializer(serializers.ModelSerializer):
|
|||
entity_type = obj.entity_type
|
||||
entity_identifier = obj.entity_identifier
|
||||
|
||||
entity_model, entity_serializer = get_entity_model_and_serializer(
|
||||
entity_type
|
||||
)
|
||||
entity_model, entity_serializer = get_entity_model_and_serializer(entity_type)
|
||||
if entity_model and entity_serializer:
|
||||
try:
|
||||
entity = entity_model.objects.get(pk=entity_identifier)
|
||||
|
|
|
|||
|
|
@ -7,13 +7,9 @@ from plane.db.models import Importer
|
|||
|
||||
|
||||
class ImporterSerializer(BaseSerializer):
|
||||
initiated_by_detail = UserLiteSerializer(
|
||||
source="initiated_by", read_only=True
|
||||
)
|
||||
initiated_by_detail = UserLiteSerializer(source="initiated_by", read_only=True)
|
||||
project_detail = ProjectLiteSerializer(source="project", read_only=True)
|
||||
workspace_detail = WorkspaceLiteSerializer(
|
||||
source="workspace", read_only=True
|
||||
)
|
||||
workspace_detail = WorkspaceLiteSerializer(source="workspace", read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = Importer
|
||||
|
|
|
|||
|
|
@ -3,11 +3,7 @@ from rest_framework import serializers
|
|||
|
||||
# Module imports
|
||||
from .base import BaseSerializer
|
||||
from .issue import (
|
||||
IssueIntakeSerializer,
|
||||
LabelLiteSerializer,
|
||||
IssueDetailSerializer,
|
||||
)
|
||||
from .issue import IssueIntakeSerializer, LabelLiteSerializer, IssueDetailSerializer
|
||||
from .project import ProjectLiteSerializer
|
||||
from .state import StateLiteSerializer
|
||||
from .user import UserLiteSerializer
|
||||
|
|
@ -21,10 +17,7 @@ class IntakeSerializer(BaseSerializer):
|
|||
class Meta:
|
||||
model = Intake
|
||||
fields = "__all__"
|
||||
read_only_fields = [
|
||||
"project",
|
||||
"workspace",
|
||||
]
|
||||
read_only_fields = ["project", "workspace"]
|
||||
|
||||
|
||||
class IntakeIssueSerializer(BaseSerializer):
|
||||
|
|
@ -41,10 +34,7 @@ class IntakeIssueSerializer(BaseSerializer):
|
|||
"issue",
|
||||
"created_by",
|
||||
]
|
||||
read_only_fields = [
|
||||
"project",
|
||||
"workspace",
|
||||
]
|
||||
read_only_fields = ["project", "workspace"]
|
||||
|
||||
def to_representation(self, instance):
|
||||
# Pass the annotated fields to the Issue instance if they exist
|
||||
|
|
@ -70,10 +60,7 @@ class IntakeIssueDetailSerializer(BaseSerializer):
|
|||
"source",
|
||||
"issue",
|
||||
]
|
||||
read_only_fields = [
|
||||
"project",
|
||||
"workspace",
|
||||
]
|
||||
read_only_fields = ["project", "workspace"]
|
||||
|
||||
def to_representation(self, instance):
|
||||
# Pass the annotated fields to the Issue instance if they exist
|
||||
|
|
@ -95,12 +82,8 @@ class IntakeIssueLiteSerializer(BaseSerializer):
|
|||
class IssueStateIntakeSerializer(BaseSerializer):
|
||||
state_detail = StateLiteSerializer(read_only=True, source="state")
|
||||
project_detail = ProjectLiteSerializer(read_only=True, source="project")
|
||||
label_details = LabelLiteSerializer(
|
||||
read_only=True, source="labels", many=True
|
||||
)
|
||||
assignee_details = UserLiteSerializer(
|
||||
read_only=True, source="assignees", many=True
|
||||
)
|
||||
label_details = LabelLiteSerializer(read_only=True, source="labels", many=True)
|
||||
assignee_details = UserLiteSerializer(read_only=True, source="assignees", many=True)
|
||||
sub_issues_count = serializers.IntegerField(read_only=True)
|
||||
issue_intake = IntakeIssueLiteSerializer(read_only=True, many=True)
|
||||
|
||||
|
|
|
|||
|
|
@ -60,12 +60,7 @@ class IssueProjectLiteSerializer(BaseSerializer):
|
|||
|
||||
class Meta:
|
||||
model = Issue
|
||||
fields = [
|
||||
"id",
|
||||
"project_detail",
|
||||
"name",
|
||||
"sequence_id",
|
||||
]
|
||||
fields = ["id", "project_detail", "name", "sequence_id"]
|
||||
read_only_fields = fields
|
||||
|
||||
|
||||
|
|
@ -74,16 +69,10 @@ class IssueProjectLiteSerializer(BaseSerializer):
|
|||
class IssueCreateSerializer(BaseSerializer):
|
||||
# ids
|
||||
state_id = serializers.PrimaryKeyRelatedField(
|
||||
source="state",
|
||||
queryset=State.objects.all(),
|
||||
required=False,
|
||||
allow_null=True,
|
||||
source="state", queryset=State.objects.all(), required=False, allow_null=True
|
||||
)
|
||||
parent_id = serializers.PrimaryKeyRelatedField(
|
||||
source="parent",
|
||||
queryset=Issue.objects.all(),
|
||||
required=False,
|
||||
allow_null=True,
|
||||
source="parent", queryset=Issue.objects.all(), required=False, allow_null=True
|
||||
)
|
||||
label_ids = serializers.ListField(
|
||||
child=serializers.PrimaryKeyRelatedField(queryset=Label.objects.all()),
|
||||
|
|
@ -124,9 +113,7 @@ class IssueCreateSerializer(BaseSerializer):
|
|||
and data.get("target_date", None) is not None
|
||||
and data.get("start_date", None) > data.get("target_date", None)
|
||||
):
|
||||
raise serializers.ValidationError(
|
||||
"Start date cannot exceed target date"
|
||||
)
|
||||
raise serializers.ValidationError("Start date cannot exceed target date")
|
||||
return data
|
||||
|
||||
def create(self, validated_data):
|
||||
|
|
@ -138,10 +125,7 @@ class IssueCreateSerializer(BaseSerializer):
|
|||
default_assignee_id = self.context["default_assignee_id"]
|
||||
|
||||
# Create Issue
|
||||
issue = Issue.objects.create(
|
||||
**validated_data,
|
||||
project_id=project_id,
|
||||
)
|
||||
issue = Issue.objects.create(**validated_data, project_id=project_id)
|
||||
|
||||
# Issue Audit Users
|
||||
created_by_id = issue.created_by_id
|
||||
|
|
@ -245,9 +229,7 @@ class IssueActivitySerializer(BaseSerializer):
|
|||
actor_detail = UserLiteSerializer(read_only=True, source="actor")
|
||||
issue_detail = IssueFlatSerializer(read_only=True, source="issue")
|
||||
project_detail = ProjectLiteSerializer(read_only=True, source="project")
|
||||
workspace_detail = WorkspaceLiteSerializer(
|
||||
read_only=True, source="workspace"
|
||||
)
|
||||
workspace_detail = WorkspaceLiteSerializer(read_only=True, source="workspace")
|
||||
|
||||
class Meta:
|
||||
model = IssueActivity
|
||||
|
|
@ -258,11 +240,7 @@ class IssueUserPropertySerializer(BaseSerializer):
|
|||
class Meta:
|
||||
model = IssueUserProperty
|
||||
fields = "__all__"
|
||||
read_only_fields = [
|
||||
"user",
|
||||
"workspace",
|
||||
"project",
|
||||
]
|
||||
read_only_fields = ["user", "workspace", "project"]
|
||||
|
||||
|
||||
class LabelSerializer(BaseSerializer):
|
||||
|
|
@ -277,30 +255,20 @@ class LabelSerializer(BaseSerializer):
|
|||
"workspace_id",
|
||||
"sort_order",
|
||||
]
|
||||
read_only_fields = [
|
||||
"workspace",
|
||||
"project",
|
||||
]
|
||||
read_only_fields = ["workspace", "project"]
|
||||
|
||||
|
||||
class LabelLiteSerializer(BaseSerializer):
|
||||
class Meta:
|
||||
model = Label
|
||||
fields = [
|
||||
"id",
|
||||
"name",
|
||||
"color",
|
||||
]
|
||||
fields = ["id", "name", "color"]
|
||||
|
||||
|
||||
class IssueLabelSerializer(BaseSerializer):
|
||||
class Meta:
|
||||
model = IssueLabel
|
||||
fields = "__all__"
|
||||
read_only_fields = [
|
||||
"workspace",
|
||||
"project",
|
||||
]
|
||||
read_only_fields = ["workspace", "project"]
|
||||
|
||||
|
||||
class IssueRelationSerializer(BaseSerializer):
|
||||
|
|
@ -316,17 +284,8 @@ class IssueRelationSerializer(BaseSerializer):
|
|||
|
||||
class Meta:
|
||||
model = IssueRelation
|
||||
fields = [
|
||||
"id",
|
||||
"project_id",
|
||||
"sequence_id",
|
||||
"relation_type",
|
||||
"name",
|
||||
]
|
||||
read_only_fields = [
|
||||
"workspace",
|
||||
"project",
|
||||
]
|
||||
fields = ["id", "project_id", "sequence_id", "relation_type", "name"]
|
||||
read_only_fields = ["workspace", "project"]
|
||||
|
||||
|
||||
class RelatedIssueSerializer(BaseSerializer):
|
||||
|
|
@ -334,25 +293,14 @@ class RelatedIssueSerializer(BaseSerializer):
|
|||
project_id = serializers.PrimaryKeyRelatedField(
|
||||
source="issue.project_id", read_only=True
|
||||
)
|
||||
sequence_id = serializers.IntegerField(
|
||||
source="issue.sequence_id", read_only=True
|
||||
)
|
||||
sequence_id = serializers.IntegerField(source="issue.sequence_id", read_only=True)
|
||||
name = serializers.CharField(source="issue.name", read_only=True)
|
||||
relation_type = serializers.CharField(read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = IssueRelation
|
||||
fields = [
|
||||
"id",
|
||||
"project_id",
|
||||
"sequence_id",
|
||||
"relation_type",
|
||||
"name",
|
||||
]
|
||||
read_only_fields = [
|
||||
"workspace",
|
||||
"project",
|
||||
]
|
||||
fields = ["id", "project_id", "sequence_id", "relation_type", "name"]
|
||||
read_only_fields = ["workspace", "project"]
|
||||
|
||||
|
||||
class IssueAssigneeSerializer(BaseSerializer):
|
||||
|
|
@ -460,8 +408,7 @@ class IssueLinkSerializer(BaseSerializer):
|
|||
# Validation if url already exists
|
||||
def create(self, validated_data):
|
||||
if IssueLink.objects.filter(
|
||||
url=validated_data.get("url"),
|
||||
issue_id=validated_data.get("issue_id"),
|
||||
url=validated_data.get("url"), issue_id=validated_data.get("issue_id")
|
||||
).exists():
|
||||
raise serializers.ValidationError(
|
||||
{"error": "URL already exists for this Issue"}
|
||||
|
|
@ -471,8 +418,7 @@ class IssueLinkSerializer(BaseSerializer):
|
|||
def update(self, instance, validated_data):
|
||||
if (
|
||||
IssueLink.objects.filter(
|
||||
url=validated_data.get("url"),
|
||||
issue_id=instance.issue_id,
|
||||
url=validated_data.get("url"), issue_id=instance.issue_id
|
||||
)
|
||||
.exclude(pk=instance.id)
|
||||
.exists()
|
||||
|
|
@ -500,7 +446,6 @@ class IssueLinkLiteSerializer(BaseSerializer):
|
|||
|
||||
|
||||
class IssueAttachmentSerializer(BaseSerializer):
|
||||
|
||||
asset_url = serializers.CharField(read_only=True)
|
||||
|
||||
class Meta:
|
||||
|
|
@ -538,37 +483,20 @@ class IssueReactionSerializer(BaseSerializer):
|
|||
class Meta:
|
||||
model = IssueReaction
|
||||
fields = "__all__"
|
||||
read_only_fields = [
|
||||
"workspace",
|
||||
"project",
|
||||
"issue",
|
||||
"actor",
|
||||
"deleted_at",
|
||||
]
|
||||
read_only_fields = ["workspace", "project", "issue", "actor", "deleted_at"]
|
||||
|
||||
|
||||
class IssueReactionLiteSerializer(DynamicBaseSerializer):
|
||||
class Meta:
|
||||
model = IssueReaction
|
||||
fields = [
|
||||
"id",
|
||||
"actor",
|
||||
"issue",
|
||||
"reaction",
|
||||
]
|
||||
fields = ["id", "actor", "issue", "reaction"]
|
||||
|
||||
|
||||
class CommentReactionSerializer(BaseSerializer):
|
||||
class Meta:
|
||||
model = CommentReaction
|
||||
fields = "__all__"
|
||||
read_only_fields = [
|
||||
"workspace",
|
||||
"project",
|
||||
"comment",
|
||||
"actor",
|
||||
"deleted_at",
|
||||
]
|
||||
read_only_fields = ["workspace", "project", "comment", "actor", "deleted_at"]
|
||||
|
||||
|
||||
class IssueVoteSerializer(BaseSerializer):
|
||||
|
|
@ -576,14 +504,7 @@ class IssueVoteSerializer(BaseSerializer):
|
|||
|
||||
class Meta:
|
||||
model = IssueVote
|
||||
fields = [
|
||||
"issue",
|
||||
"vote",
|
||||
"workspace",
|
||||
"project",
|
||||
"actor",
|
||||
"actor_detail",
|
||||
]
|
||||
fields = ["issue", "vote", "workspace", "project", "actor", "actor_detail"]
|
||||
read_only_fields = fields
|
||||
|
||||
|
||||
|
|
@ -591,9 +512,7 @@ class IssueCommentSerializer(BaseSerializer):
|
|||
actor_detail = UserLiteSerializer(read_only=True, source="actor")
|
||||
issue_detail = IssueFlatSerializer(read_only=True, source="issue")
|
||||
project_detail = ProjectLiteSerializer(read_only=True, source="project")
|
||||
workspace_detail = WorkspaceLiteSerializer(
|
||||
read_only=True, source="workspace"
|
||||
)
|
||||
workspace_detail = WorkspaceLiteSerializer(read_only=True, source="workspace")
|
||||
comment_reactions = CommentReactionSerializer(read_only=True, many=True)
|
||||
is_member = serializers.BooleanField(read_only=True)
|
||||
|
||||
|
|
@ -617,25 +536,15 @@ class IssueStateFlatSerializer(BaseSerializer):
|
|||
|
||||
class Meta:
|
||||
model = Issue
|
||||
fields = [
|
||||
"id",
|
||||
"sequence_id",
|
||||
"name",
|
||||
"state_detail",
|
||||
"project_detail",
|
||||
]
|
||||
fields = ["id", "sequence_id", "name", "state_detail", "project_detail"]
|
||||
|
||||
|
||||
# Issue Serializer with state details
|
||||
class IssueStateSerializer(DynamicBaseSerializer):
|
||||
label_details = LabelLiteSerializer(
|
||||
read_only=True, source="labels", many=True
|
||||
)
|
||||
label_details = LabelLiteSerializer(read_only=True, source="labels", many=True)
|
||||
state_detail = StateLiteSerializer(read_only=True, source="state")
|
||||
project_detail = ProjectLiteSerializer(read_only=True, source="project")
|
||||
assignee_details = UserLiteSerializer(
|
||||
read_only=True, source="assignees", many=True
|
||||
)
|
||||
assignee_details = UserLiteSerializer(read_only=True, source="assignees", many=True)
|
||||
sub_issues_count = serializers.IntegerField(read_only=True)
|
||||
attachment_count = serializers.IntegerField(read_only=True)
|
||||
link_count = serializers.IntegerField(read_only=True)
|
||||
|
|
@ -646,10 +555,7 @@ class IssueStateSerializer(DynamicBaseSerializer):
|
|||
|
||||
|
||||
class IssueIntakeSerializer(DynamicBaseSerializer):
|
||||
label_ids = serializers.ListField(
|
||||
child=serializers.UUIDField(),
|
||||
required=False,
|
||||
)
|
||||
label_ids = serializers.ListField(child=serializers.UUIDField(), required=False)
|
||||
|
||||
class Meta:
|
||||
model = Issue
|
||||
|
|
@ -669,20 +575,11 @@ class IssueIntakeSerializer(DynamicBaseSerializer):
|
|||
class IssueSerializer(DynamicBaseSerializer):
|
||||
# ids
|
||||
cycle_id = serializers.PrimaryKeyRelatedField(read_only=True)
|
||||
module_ids = serializers.ListField(
|
||||
child=serializers.UUIDField(),
|
||||
required=False,
|
||||
)
|
||||
module_ids = serializers.ListField(child=serializers.UUIDField(), required=False)
|
||||
|
||||
# Many to many
|
||||
label_ids = serializers.ListField(
|
||||
child=serializers.UUIDField(),
|
||||
required=False,
|
||||
)
|
||||
assignee_ids = serializers.ListField(
|
||||
child=serializers.UUIDField(),
|
||||
required=False,
|
||||
)
|
||||
label_ids = serializers.ListField(child=serializers.UUIDField(), required=False)
|
||||
assignee_ids = serializers.ListField(child=serializers.UUIDField(), required=False)
|
||||
|
||||
# Count items
|
||||
sub_issues_count = serializers.IntegerField(read_only=True)
|
||||
|
|
@ -724,11 +621,7 @@ class IssueSerializer(DynamicBaseSerializer):
|
|||
class IssueLiteSerializer(DynamicBaseSerializer):
|
||||
class Meta:
|
||||
model = Issue
|
||||
fields = [
|
||||
"id",
|
||||
"sequence_id",
|
||||
"project_id",
|
||||
]
|
||||
fields = ["id", "sequence_id", "project_id"]
|
||||
read_only_fields = fields
|
||||
|
||||
|
||||
|
|
@ -737,10 +630,7 @@ class IssueDetailSerializer(IssueSerializer):
|
|||
is_subscribed = serializers.BooleanField(read_only=True)
|
||||
|
||||
class Meta(IssueSerializer.Meta):
|
||||
fields = IssueSerializer.Meta.fields + [
|
||||
"description_html",
|
||||
"is_subscribed",
|
||||
]
|
||||
fields = IssueSerializer.Meta.fields + ["description_html", "is_subscribed"]
|
||||
read_only_fields = fields
|
||||
|
||||
|
||||
|
|
@ -776,8 +666,4 @@ class IssueSubscriberSerializer(BaseSerializer):
|
|||
class Meta:
|
||||
model = IssueSubscriber
|
||||
fields = "__all__"
|
||||
read_only_fields = [
|
||||
"workspace",
|
||||
"project",
|
||||
"issue",
|
||||
]
|
||||
read_only_fields = ["workspace", "project", "issue"]
|
||||
|
|
|
|||
|
|
@ -21,10 +21,7 @@ from plane.db.models import (
|
|||
|
||||
class ModuleWriteSerializer(BaseSerializer):
|
||||
lead_id = serializers.PrimaryKeyRelatedField(
|
||||
source="lead",
|
||||
queryset=User.objects.all(),
|
||||
required=False,
|
||||
allow_null=True,
|
||||
source="lead", queryset=User.objects.all(), required=False, allow_null=True
|
||||
)
|
||||
member_ids = serializers.ListField(
|
||||
child=serializers.PrimaryKeyRelatedField(queryset=User.objects.all()),
|
||||
|
|
@ -48,9 +45,7 @@ class ModuleWriteSerializer(BaseSerializer):
|
|||
|
||||
def to_representation(self, instance):
|
||||
data = super().to_representation(instance)
|
||||
data["member_ids"] = [
|
||||
str(member.id) for member in instance.members.all()
|
||||
]
|
||||
data["member_ids"] = [str(member.id) for member in instance.members.all()]
|
||||
return data
|
||||
|
||||
def validate(self, data):
|
||||
|
|
@ -59,9 +54,7 @@ class ModuleWriteSerializer(BaseSerializer):
|
|||
and data.get("target_date", None) is not None
|
||||
and data.get("start_date", None) > data.get("target_date", None)
|
||||
):
|
||||
raise serializers.ValidationError(
|
||||
"Start date cannot exceed target date"
|
||||
)
|
||||
raise serializers.ValidationError("Start date cannot exceed target date")
|
||||
return data
|
||||
|
||||
def create(self, validated_data):
|
||||
|
|
@ -71,9 +64,7 @@ class ModuleWriteSerializer(BaseSerializer):
|
|||
module_name = validated_data.get("name")
|
||||
if module_name:
|
||||
# Lookup for the module name in the module table for that project
|
||||
if Module.objects.filter(
|
||||
name=module_name, project=project
|
||||
).exists():
|
||||
if Module.objects.filter(name=module_name, project=project).exists():
|
||||
raise serializers.ValidationError(
|
||||
{"error": "Module with this name already exists"}
|
||||
)
|
||||
|
|
@ -104,9 +95,7 @@ class ModuleWriteSerializer(BaseSerializer):
|
|||
if module_name:
|
||||
# Lookup for the module name in the module table for that project
|
||||
if (
|
||||
Module.objects.filter(
|
||||
name=module_name, project=instance.project
|
||||
)
|
||||
Module.objects.filter(name=module_name, project=instance.project)
|
||||
.exclude(id=instance.id)
|
||||
.exists()
|
||||
):
|
||||
|
|
@ -203,8 +192,7 @@ class ModuleLinkSerializer(BaseSerializer):
|
|||
def create(self, validated_data):
|
||||
validated_data["url"] = self.validate_url(validated_data.get("url"))
|
||||
if ModuleLink.objects.filter(
|
||||
url=validated_data.get("url"),
|
||||
module_id=validated_data.get("module_id"),
|
||||
url=validated_data.get("url"), module_id=validated_data.get("module_id")
|
||||
).exists():
|
||||
raise serializers.ValidationError({"error": "URL already exists."})
|
||||
return super().create(validated_data)
|
||||
|
|
@ -213,8 +201,7 @@ class ModuleLinkSerializer(BaseSerializer):
|
|||
validated_data["url"] = self.validate_url(validated_data.get("url"))
|
||||
if (
|
||||
ModuleLink.objects.filter(
|
||||
url=validated_data.get("url"),
|
||||
module_id=instance.module_id,
|
||||
url=validated_data.get("url"), module_id=instance.module_id
|
||||
)
|
||||
.exclude(pk=instance.id)
|
||||
.exists()
|
||||
|
|
|
|||
|
|
@ -8,9 +8,7 @@ from rest_framework import serializers
|
|||
|
||||
|
||||
class NotificationSerializer(BaseSerializer):
|
||||
triggered_by_details = UserLiteSerializer(
|
||||
read_only=True, source="triggered_by"
|
||||
)
|
||||
triggered_by_details = UserLiteSerializer(read_only=True, source="triggered_by")
|
||||
is_inbox_issue = serializers.BooleanField(read_only=True)
|
||||
is_intake_issue = serializers.BooleanField(read_only=True)
|
||||
is_mentioned_notification = serializers.BooleanField(read_only=True)
|
||||
|
|
|
|||
|
|
@ -22,14 +22,8 @@ class PageSerializer(BaseSerializer):
|
|||
required=False,
|
||||
)
|
||||
# Many to many
|
||||
label_ids = serializers.ListField(
|
||||
child=serializers.UUIDField(),
|
||||
required=False,
|
||||
)
|
||||
project_ids = serializers.ListField(
|
||||
child=serializers.UUIDField(),
|
||||
required=False,
|
||||
)
|
||||
label_ids = serializers.ListField(child=serializers.UUIDField(), required=False)
|
||||
project_ids = serializers.ListField(child=serializers.UUIDField(), required=False)
|
||||
|
||||
class Meta:
|
||||
model = Page
|
||||
|
|
@ -54,10 +48,7 @@ class PageSerializer(BaseSerializer):
|
|||
"label_ids",
|
||||
"project_ids",
|
||||
]
|
||||
read_only_fields = [
|
||||
"workspace",
|
||||
"owned_by",
|
||||
]
|
||||
read_only_fields = ["workspace", "owned_by"]
|
||||
|
||||
def create(self, validated_data):
|
||||
labels = validated_data.pop("labels", None)
|
||||
|
|
@ -127,9 +118,7 @@ class PageDetailSerializer(PageSerializer):
|
|||
description_html = serializers.CharField()
|
||||
|
||||
class Meta(PageSerializer.Meta):
|
||||
fields = PageSerializer.Meta.fields + [
|
||||
"description_html",
|
||||
]
|
||||
fields = PageSerializer.Meta.fields + ["description_html"]
|
||||
|
||||
|
||||
class SubPageSerializer(BaseSerializer):
|
||||
|
|
@ -138,10 +127,7 @@ class SubPageSerializer(BaseSerializer):
|
|||
class Meta:
|
||||
model = PageLog
|
||||
fields = "__all__"
|
||||
read_only_fields = [
|
||||
"workspace",
|
||||
"page",
|
||||
]
|
||||
read_only_fields = ["workspace", "page"]
|
||||
|
||||
def get_entity_details(self, obj):
|
||||
entity_name = obj.entity_name
|
||||
|
|
@ -158,10 +144,7 @@ class PageLogSerializer(BaseSerializer):
|
|||
class Meta:
|
||||
model = PageLog
|
||||
fields = "__all__"
|
||||
read_only_fields = [
|
||||
"workspace",
|
||||
"page",
|
||||
]
|
||||
read_only_fields = ["workspace", "page"]
|
||||
|
||||
|
||||
class PageVersionSerializer(BaseSerializer):
|
||||
|
|
@ -178,10 +161,7 @@ class PageVersionSerializer(BaseSerializer):
|
|||
"created_by",
|
||||
"updated_by",
|
||||
]
|
||||
read_only_fields = [
|
||||
"workspace",
|
||||
"page",
|
||||
]
|
||||
read_only_fields = ["workspace", "page"]
|
||||
|
||||
|
||||
class PageVersionDetailSerializer(BaseSerializer):
|
||||
|
|
@ -201,7 +181,4 @@ class PageVersionDetailSerializer(BaseSerializer):
|
|||
"created_by",
|
||||
"updated_by",
|
||||
]
|
||||
read_only_fields = [
|
||||
"workspace",
|
||||
"page",
|
||||
]
|
||||
read_only_fields = ["workspace", "page"]
|
||||
|
|
|
|||
|
|
@ -4,10 +4,7 @@ from rest_framework import serializers
|
|||
# Module imports
|
||||
from .base import BaseSerializer, DynamicBaseSerializer
|
||||
from plane.app.serializers.workspace import WorkspaceLiteSerializer
|
||||
from plane.app.serializers.user import (
|
||||
UserLiteSerializer,
|
||||
UserAdminLiteSerializer,
|
||||
)
|
||||
from plane.app.serializers.user import UserLiteSerializer, UserAdminLiteSerializer
|
||||
from plane.db.models import (
|
||||
Project,
|
||||
ProjectMember,
|
||||
|
|
@ -19,32 +16,23 @@ from plane.db.models import (
|
|||
|
||||
|
||||
class ProjectSerializer(BaseSerializer):
|
||||
workspace_detail = WorkspaceLiteSerializer(
|
||||
source="workspace", read_only=True
|
||||
)
|
||||
workspace_detail = WorkspaceLiteSerializer(source="workspace", read_only=True)
|
||||
inbox_view = serializers.BooleanField(read_only=True, source="intake_view")
|
||||
|
||||
class Meta:
|
||||
model = Project
|
||||
fields = "__all__"
|
||||
read_only_fields = [
|
||||
"workspace",
|
||||
"deleted_at",
|
||||
]
|
||||
read_only_fields = ["workspace", "deleted_at"]
|
||||
|
||||
def create(self, validated_data):
|
||||
identifier = validated_data.get("identifier", "").strip().upper()
|
||||
if identifier == "":
|
||||
raise serializers.ValidationError(
|
||||
detail="Project Identifier is required"
|
||||
)
|
||||
raise serializers.ValidationError(detail="Project Identifier is required")
|
||||
|
||||
if ProjectIdentifier.objects.filter(
|
||||
name=identifier, workspace_id=self.context["workspace_id"]
|
||||
).exists():
|
||||
raise serializers.ValidationError(
|
||||
detail="Project Identifier is taken"
|
||||
)
|
||||
raise serializers.ValidationError(detail="Project Identifier is taken")
|
||||
project = Project.objects.create(
|
||||
**validated_data, workspace_id=self.context["workspace_id"]
|
||||
)
|
||||
|
|
@ -83,9 +71,7 @@ class ProjectSerializer(BaseSerializer):
|
|||
return project
|
||||
|
||||
# If not same fail update
|
||||
raise serializers.ValidationError(
|
||||
detail="Project Identifier is already taken"
|
||||
)
|
||||
raise serializers.ValidationError(detail="Project Identifier is already taken")
|
||||
|
||||
|
||||
class ProjectLiteSerializer(BaseSerializer):
|
||||
|
|
@ -214,26 +200,16 @@ class ProjectMemberLiteSerializer(BaseSerializer):
|
|||
|
||||
class DeployBoardSerializer(BaseSerializer):
|
||||
project_details = ProjectLiteSerializer(read_only=True, source="project")
|
||||
workspace_detail = WorkspaceLiteSerializer(
|
||||
read_only=True, source="workspace"
|
||||
)
|
||||
workspace_detail = WorkspaceLiteSerializer(read_only=True, source="workspace")
|
||||
|
||||
class Meta:
|
||||
model = DeployBoard
|
||||
fields = "__all__"
|
||||
read_only_fields = [
|
||||
"workspace",
|
||||
"project",
|
||||
"anchor",
|
||||
]
|
||||
read_only_fields = ["workspace", "project", "anchor"]
|
||||
|
||||
|
||||
class ProjectPublicMemberSerializer(BaseSerializer):
|
||||
class Meta:
|
||||
model = ProjectPublicMember
|
||||
fields = "__all__"
|
||||
read_only_fields = [
|
||||
"workspace",
|
||||
"project",
|
||||
"member",
|
||||
]
|
||||
read_only_fields = ["workspace", "project", "member"]
|
||||
|
|
|
|||
|
|
@ -19,19 +19,11 @@ class StateSerializer(BaseSerializer):
|
|||
"description",
|
||||
"sequence",
|
||||
]
|
||||
read_only_fields = [
|
||||
"workspace",
|
||||
"project",
|
||||
]
|
||||
read_only_fields = ["workspace", "project"]
|
||||
|
||||
|
||||
class StateLiteSerializer(BaseSerializer):
|
||||
class Meta:
|
||||
model = State
|
||||
fields = [
|
||||
"id",
|
||||
"name",
|
||||
"color",
|
||||
"group",
|
||||
]
|
||||
fields = ["id", "name", "color", "group"]
|
||||
read_only_fields = fields
|
||||
|
|
|
|||
|
|
@ -2,13 +2,7 @@
|
|||
from rest_framework import serializers
|
||||
|
||||
# Module import
|
||||
from plane.db.models import (
|
||||
Account,
|
||||
Profile,
|
||||
User,
|
||||
Workspace,
|
||||
WorkspaceMemberInvite,
|
||||
)
|
||||
from plane.db.models import Account, Profile, User, Workspace, WorkspaceMemberInvite
|
||||
|
||||
from .base import BaseSerializer
|
||||
|
||||
|
|
@ -17,11 +11,7 @@ class UserSerializer(BaseSerializer):
|
|||
class Meta:
|
||||
model = User
|
||||
# Exclude password field from the serializer
|
||||
fields = [
|
||||
field.name
|
||||
for field in User._meta.fields
|
||||
if field.name != "password"
|
||||
]
|
||||
fields = [field.name for field in User._meta.fields if field.name != "password"]
|
||||
# Make all system fields and email read only
|
||||
read_only_fields = [
|
||||
"id",
|
||||
|
|
@ -56,7 +46,6 @@ class UserSerializer(BaseSerializer):
|
|||
|
||||
|
||||
class UserMeSerializer(BaseSerializer):
|
||||
|
||||
class Meta:
|
||||
model = User
|
||||
fields = [
|
||||
|
|
@ -87,11 +76,7 @@ class UserMeSettingsSerializer(BaseSerializer):
|
|||
|
||||
class Meta:
|
||||
model = User
|
||||
fields = [
|
||||
"id",
|
||||
"email",
|
||||
"workspace",
|
||||
]
|
||||
fields = ["id", "email", "workspace"]
|
||||
read_only_fields = fields
|
||||
|
||||
def get_workspace(self, obj):
|
||||
|
|
@ -128,8 +113,7 @@ class UserMeSettingsSerializer(BaseSerializer):
|
|||
else:
|
||||
fallback_workspace = (
|
||||
Workspace.objects.filter(
|
||||
workspace_member__member_id=obj.id,
|
||||
workspace_member__is_active=True,
|
||||
workspace_member__member_id=obj.id, workspace_member__is_active=True
|
||||
)
|
||||
.order_by("created_at")
|
||||
.first()
|
||||
|
|
@ -138,14 +122,10 @@ class UserMeSettingsSerializer(BaseSerializer):
|
|||
"last_workspace_id": None,
|
||||
"last_workspace_slug": None,
|
||||
"fallback_workspace_id": (
|
||||
fallback_workspace.id
|
||||
if fallback_workspace is not None
|
||||
else None
|
||||
fallback_workspace.id if fallback_workspace is not None else None
|
||||
),
|
||||
"fallback_workspace_slug": (
|
||||
fallback_workspace.slug
|
||||
if fallback_workspace is not None
|
||||
else None
|
||||
fallback_workspace.slug if fallback_workspace is not None else None
|
||||
),
|
||||
"invites": workspace_invites,
|
||||
}
|
||||
|
|
@ -163,10 +143,7 @@ class UserLiteSerializer(BaseSerializer):
|
|||
"is_bot",
|
||||
"display_name",
|
||||
]
|
||||
read_only_fields = [
|
||||
"id",
|
||||
"is_bot",
|
||||
]
|
||||
read_only_fields = ["id", "is_bot"]
|
||||
|
||||
|
||||
class UserAdminLiteSerializer(BaseSerializer):
|
||||
|
|
@ -183,10 +160,7 @@ class UserAdminLiteSerializer(BaseSerializer):
|
|||
"email",
|
||||
"last_login_medium",
|
||||
]
|
||||
read_only_fields = [
|
||||
"id",
|
||||
"is_bot",
|
||||
]
|
||||
read_only_fields = ["id", "is_bot"]
|
||||
|
||||
|
||||
class ChangePasswordSerializer(serializers.Serializer):
|
||||
|
|
@ -207,9 +181,7 @@ class ChangePasswordSerializer(serializers.Serializer):
|
|||
|
||||
if data.get("new_password") != data.get("confirm_password"):
|
||||
raise serializers.ValidationError(
|
||||
{
|
||||
"error": "Confirm password should be same as the new password."
|
||||
}
|
||||
{"error": "Confirm password should be same as the new password."}
|
||||
)
|
||||
|
||||
return data
|
||||
|
|
@ -227,15 +199,11 @@ class ProfileSerializer(BaseSerializer):
|
|||
class Meta:
|
||||
model = Profile
|
||||
fields = "__all__"
|
||||
read_only_fields = [
|
||||
"user",
|
||||
]
|
||||
read_only_fields = ["user"]
|
||||
|
||||
|
||||
class AccountSerializer(BaseSerializer):
|
||||
class Meta:
|
||||
model = Account
|
||||
fields = "__all__"
|
||||
read_only_fields = [
|
||||
"user",
|
||||
]
|
||||
read_only_fields = ["user"]
|
||||
|
|
|
|||
|
|
@ -47,13 +47,9 @@ class WebhookSerializer(DynamicBaseSerializer):
|
|||
|
||||
# Additional validation for multiple request domains and their subdomains
|
||||
request = self.context.get("request")
|
||||
disallowed_domains = [
|
||||
"plane.so",
|
||||
] # Add your disallowed domains here
|
||||
disallowed_domains = ["plane.so"] # Add your disallowed domains here
|
||||
if request:
|
||||
request_host = request.get_host().split(":")[
|
||||
0
|
||||
] # Remove port if present
|
||||
request_host = request.get_host().split(":")[0] # Remove port if present
|
||||
disallowed_domains.append(request_host)
|
||||
|
||||
# Check if hostname is a subdomain or exact match of any disallowed domain
|
||||
|
|
@ -99,9 +95,7 @@ class WebhookSerializer(DynamicBaseSerializer):
|
|||
|
||||
# Additional validation for multiple request domains and their subdomains
|
||||
request = self.context.get("request")
|
||||
disallowed_domains = [
|
||||
"plane.so",
|
||||
] # Add your disallowed domains here
|
||||
disallowed_domains = ["plane.so"] # Add your disallowed domains here
|
||||
if request:
|
||||
request_host = request.get_host().split(":")[
|
||||
0
|
||||
|
|
@ -122,10 +116,7 @@ class WebhookSerializer(DynamicBaseSerializer):
|
|||
class Meta:
|
||||
model = Webhook
|
||||
fields = "__all__"
|
||||
read_only_fields = [
|
||||
"workspace",
|
||||
"secret_key",
|
||||
]
|
||||
read_only_fields = ["workspace", "secret_key"]
|
||||
|
||||
|
||||
class WebhookLogSerializer(DynamicBaseSerializer):
|
||||
|
|
|
|||
|
|
@ -47,11 +47,7 @@ class WorkSpaceSerializer(DynamicBaseSerializer):
|
|||
class WorkspaceLiteSerializer(BaseSerializer):
|
||||
class Meta:
|
||||
model = Workspace
|
||||
fields = [
|
||||
"name",
|
||||
"slug",
|
||||
"id",
|
||||
]
|
||||
fields = ["name", "slug", "id"]
|
||||
read_only_fields = fields
|
||||
|
||||
|
||||
|
|
@ -66,6 +62,7 @@ class WorkSpaceMemberSerializer(DynamicBaseSerializer):
|
|||
|
||||
class WorkspaceMemberMeSerializer(BaseSerializer):
|
||||
draft_issue_count = serializers.IntegerField(read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = WorkspaceMember
|
||||
fields = "__all__"
|
||||
|
|
@ -101,9 +98,7 @@ class WorkSpaceMemberInviteSerializer(BaseSerializer):
|
|||
|
||||
|
||||
class TeamSerializer(BaseSerializer):
|
||||
members_detail = UserLiteSerializer(
|
||||
read_only=True, source="members", many=True
|
||||
)
|
||||
members_detail = UserLiteSerializer(read_only=True, source="members", many=True)
|
||||
members = serializers.ListField(
|
||||
child=serializers.PrimaryKeyRelatedField(queryset=User.objects.all()),
|
||||
write_only=True,
|
||||
|
|
@ -140,9 +135,7 @@ class TeamSerializer(BaseSerializer):
|
|||
members = validated_data.pop("members")
|
||||
TeamMember.objects.filter(team=instance).delete()
|
||||
team_members = [
|
||||
TeamMember(
|
||||
member=member, team=instance, workspace=instance.workspace
|
||||
)
|
||||
TeamMember(member=member, team=instance, workspace=instance.workspace)
|
||||
for member in members
|
||||
]
|
||||
TeamMember.objects.bulk_create(team_members, batch_size=10)
|
||||
|
|
@ -154,17 +147,11 @@ class WorkspaceThemeSerializer(BaseSerializer):
|
|||
class Meta:
|
||||
model = WorkspaceTheme
|
||||
fields = "__all__"
|
||||
read_only_fields = [
|
||||
"workspace",
|
||||
"actor",
|
||||
]
|
||||
read_only_fields = ["workspace", "actor"]
|
||||
|
||||
|
||||
class WorkspaceUserPropertiesSerializer(BaseSerializer):
|
||||
class Meta:
|
||||
model = WorkspaceUserProperties
|
||||
fields = "__all__"
|
||||
read_only_fields = [
|
||||
"workspace",
|
||||
"user",
|
||||
]
|
||||
read_only_fields = ["workspace", "user"]
|
||||
|
|
|
|||
|
|
@ -26,11 +26,7 @@ urlpatterns = [
|
|||
FileAssetEndpoint.as_view(),
|
||||
name="file-assets",
|
||||
),
|
||||
path(
|
||||
"users/file-assets/",
|
||||
UserAssetsEndpoint.as_view(),
|
||||
name="user-file-assets",
|
||||
),
|
||||
path("users/file-assets/", UserAssetsEndpoint.as_view(), name="user-file-assets"),
|
||||
path(
|
||||
"users/file-assets/<str:asset_key>/",
|
||||
UserAssetsEndpoint.as_view(),
|
||||
|
|
@ -38,11 +34,7 @@ urlpatterns = [
|
|||
),
|
||||
path(
|
||||
"workspaces/file-assets/<uuid:workspace_id>/<str:asset_key>/restore/",
|
||||
FileAssetViewSet.as_view(
|
||||
{
|
||||
"post": "restore",
|
||||
}
|
||||
),
|
||||
FileAssetViewSet.as_view({"post": "restore"}),
|
||||
name="file-assets-restore",
|
||||
),
|
||||
# V2 Endpoints
|
||||
|
|
|
|||
|
|
@ -17,12 +17,7 @@ from plane.app.views import (
|
|||
urlpatterns = [
|
||||
path(
|
||||
"workspaces/<str:slug>/projects/<uuid:project_id>/cycles/",
|
||||
CycleViewSet.as_view(
|
||||
{
|
||||
"get": "list",
|
||||
"post": "create",
|
||||
}
|
||||
),
|
||||
CycleViewSet.as_view({"get": "list", "post": "create"}),
|
||||
name="project-cycle",
|
||||
),
|
||||
path(
|
||||
|
|
@ -39,12 +34,7 @@ urlpatterns = [
|
|||
),
|
||||
path(
|
||||
"workspaces/<str:slug>/projects/<uuid:project_id>/cycles/<uuid:cycle_id>/cycle-issues/",
|
||||
CycleIssueViewSet.as_view(
|
||||
{
|
||||
"get": "list",
|
||||
"post": "create",
|
||||
}
|
||||
),
|
||||
CycleIssueViewSet.as_view({"get": "list", "post": "create"}),
|
||||
name="project-issue-cycle",
|
||||
),
|
||||
path(
|
||||
|
|
@ -66,21 +56,12 @@ urlpatterns = [
|
|||
),
|
||||
path(
|
||||
"workspaces/<str:slug>/projects/<uuid:project_id>/user-favorite-cycles/",
|
||||
CycleFavoriteViewSet.as_view(
|
||||
{
|
||||
"get": "list",
|
||||
"post": "create",
|
||||
}
|
||||
),
|
||||
CycleFavoriteViewSet.as_view({"get": "list", "post": "create"}),
|
||||
name="user-favorite-cycle",
|
||||
),
|
||||
path(
|
||||
"workspaces/<str:slug>/projects/<uuid:project_id>/user-favorite-cycles/<uuid:cycle_id>/",
|
||||
CycleFavoriteViewSet.as_view(
|
||||
{
|
||||
"delete": "destroy",
|
||||
}
|
||||
),
|
||||
CycleFavoriteViewSet.as_view({"delete": "destroy"}),
|
||||
name="user-favorite-cycle",
|
||||
),
|
||||
path(
|
||||
|
|
|
|||
|
|
@ -16,42 +16,24 @@ urlpatterns = [
|
|||
),
|
||||
path(
|
||||
"workspaces/<str:slug>/projects/<uuid:project_id>/estimates/",
|
||||
BulkEstimatePointEndpoint.as_view(
|
||||
{
|
||||
"get": "list",
|
||||
"post": "create",
|
||||
}
|
||||
),
|
||||
BulkEstimatePointEndpoint.as_view({"get": "list", "post": "create"}),
|
||||
name="bulk-create-estimate-points",
|
||||
),
|
||||
path(
|
||||
"workspaces/<str:slug>/projects/<uuid:project_id>/estimates/<uuid:estimate_id>/",
|
||||
BulkEstimatePointEndpoint.as_view(
|
||||
{
|
||||
"get": "retrieve",
|
||||
"patch": "partial_update",
|
||||
"delete": "destroy",
|
||||
}
|
||||
{"get": "retrieve", "patch": "partial_update", "delete": "destroy"}
|
||||
),
|
||||
name="bulk-create-estimate-points",
|
||||
),
|
||||
path(
|
||||
"workspaces/<str:slug>/projects/<uuid:project_id>/estimates/<uuid:estimate_id>/estimate-points/",
|
||||
EstimatePointEndpoint.as_view(
|
||||
{
|
||||
"post": "create",
|
||||
}
|
||||
),
|
||||
EstimatePointEndpoint.as_view({"post": "create"}),
|
||||
name="estimate-points",
|
||||
),
|
||||
path(
|
||||
"workspaces/<str:slug>/projects/<uuid:project_id>/estimates/<uuid:estimate_id>/estimate-points/<estimate_point_id>/",
|
||||
EstimatePointEndpoint.as_view(
|
||||
{
|
||||
"patch": "partial_update",
|
||||
"delete": "destroy",
|
||||
}
|
||||
),
|
||||
EstimatePointEndpoint.as_view({"patch": "partial_update", "delete": "destroy"}),
|
||||
name="estimate-points",
|
||||
),
|
||||
]
|
||||
|
|
|
|||
|
|
@ -6,17 +6,13 @@ from plane.app.views import GPTIntegrationEndpoint, WorkspaceGPTIntegrationEndpo
|
|||
|
||||
|
||||
urlpatterns = [
|
||||
path(
|
||||
"unsplash/",
|
||||
UnsplashEndpoint.as_view(),
|
||||
name="unsplash",
|
||||
),
|
||||
path("unsplash/", UnsplashEndpoint.as_view(), name="unsplash"),
|
||||
path(
|
||||
"workspaces/<str:slug>/projects/<uuid:project_id>/ai-assistant/",
|
||||
GPTIntegrationEndpoint.as_view(),
|
||||
name="importer",
|
||||
),
|
||||
path(
|
||||
path(
|
||||
"workspaces/<str:slug>/ai-assistant/",
|
||||
WorkspaceGPTIntegrationEndpoint.as_view(),
|
||||
name="importer",
|
||||
|
|
|
|||
|
|
@ -1,94 +1,55 @@
|
|||
from django.urls import path
|
||||
|
||||
|
||||
from plane.app.views import (
|
||||
IntakeViewSet,
|
||||
IntakeIssueViewSet,
|
||||
)
|
||||
from plane.app.views import IntakeViewSet, IntakeIssueViewSet
|
||||
|
||||
|
||||
urlpatterns = [
|
||||
path(
|
||||
"workspaces/<str:slug>/projects/<uuid:project_id>/intakes/",
|
||||
IntakeViewSet.as_view(
|
||||
{
|
||||
"get": "list",
|
||||
"post": "create",
|
||||
}
|
||||
),
|
||||
IntakeViewSet.as_view({"get": "list", "post": "create"}),
|
||||
name="intake",
|
||||
),
|
||||
path(
|
||||
"workspaces/<str:slug>/projects/<uuid:project_id>/intakes/<uuid:pk>/",
|
||||
IntakeViewSet.as_view(
|
||||
{
|
||||
"get": "retrieve",
|
||||
"patch": "partial_update",
|
||||
"delete": "destroy",
|
||||
}
|
||||
{"get": "retrieve", "patch": "partial_update", "delete": "destroy"}
|
||||
),
|
||||
name="intake",
|
||||
),
|
||||
path(
|
||||
"workspaces/<str:slug>/projects/<uuid:project_id>/intake-issues/",
|
||||
IntakeIssueViewSet.as_view(
|
||||
{
|
||||
"get": "list",
|
||||
"post": "create",
|
||||
}
|
||||
),
|
||||
IntakeIssueViewSet.as_view({"get": "list", "post": "create"}),
|
||||
name="intake-issue",
|
||||
),
|
||||
path(
|
||||
"workspaces/<str:slug>/projects/<uuid:project_id>/intake-issues/<uuid:pk>/",
|
||||
IntakeIssueViewSet.as_view(
|
||||
{
|
||||
"get": "retrieve",
|
||||
"patch": "partial_update",
|
||||
"delete": "destroy",
|
||||
}
|
||||
{"get": "retrieve", "patch": "partial_update", "delete": "destroy"}
|
||||
),
|
||||
name="intake-issue",
|
||||
),
|
||||
path(
|
||||
"workspaces/<str:slug>/projects/<uuid:project_id>/inboxes/",
|
||||
IntakeViewSet.as_view(
|
||||
{
|
||||
"get": "list",
|
||||
"post": "create",
|
||||
}
|
||||
),
|
||||
IntakeViewSet.as_view({"get": "list", "post": "create"}),
|
||||
name="inbox",
|
||||
),
|
||||
path(
|
||||
"workspaces/<str:slug>/projects/<uuid:project_id>/inboxes/<uuid:pk>/",
|
||||
IntakeViewSet.as_view(
|
||||
{
|
||||
"get": "retrieve",
|
||||
"patch": "partial_update",
|
||||
"delete": "destroy",
|
||||
}
|
||||
{"get": "retrieve", "patch": "partial_update", "delete": "destroy"}
|
||||
),
|
||||
name="inbox",
|
||||
),
|
||||
path(
|
||||
"workspaces/<str:slug>/projects/<uuid:project_id>/inbox-issues/",
|
||||
IntakeIssueViewSet.as_view(
|
||||
{
|
||||
"get": "list",
|
||||
"post": "create",
|
||||
}
|
||||
),
|
||||
IntakeIssueViewSet.as_view({"get": "list", "post": "create"}),
|
||||
name="inbox-issue",
|
||||
),
|
||||
path(
|
||||
"workspaces/<str:slug>/projects/<uuid:project_id>/inbox-issues/<uuid:pk>/",
|
||||
IntakeIssueViewSet.as_view(
|
||||
{
|
||||
"get": "retrieve",
|
||||
"patch": "partial_update",
|
||||
"delete": "destroy",
|
||||
}
|
||||
{"get": "retrieve", "patch": "partial_update", "delete": "destroy"}
|
||||
),
|
||||
name="inbox-issue",
|
||||
),
|
||||
|
|
|
|||
|
|
@ -34,12 +34,7 @@ urlpatterns = [
|
|||
),
|
||||
path(
|
||||
"workspaces/<str:slug>/projects/<uuid:project_id>/issues/",
|
||||
IssueViewSet.as_view(
|
||||
{
|
||||
"get": "list",
|
||||
"post": "create",
|
||||
}
|
||||
),
|
||||
IssueViewSet.as_view({"get": "list", "post": "create"}),
|
||||
name="project-issue",
|
||||
),
|
||||
path(
|
||||
|
|
@ -68,12 +63,7 @@ urlpatterns = [
|
|||
),
|
||||
path(
|
||||
"workspaces/<str:slug>/projects/<uuid:project_id>/issue-labels/",
|
||||
LabelViewSet.as_view(
|
||||
{
|
||||
"get": "list",
|
||||
"post": "create",
|
||||
}
|
||||
),
|
||||
LabelViewSet.as_view({"get": "list", "post": "create"}),
|
||||
name="project-issue-labels",
|
||||
),
|
||||
path(
|
||||
|
|
@ -111,12 +101,7 @@ urlpatterns = [
|
|||
),
|
||||
path(
|
||||
"workspaces/<str:slug>/projects/<uuid:project_id>/issues/<uuid:issue_id>/issue-links/",
|
||||
IssueLinkViewSet.as_view(
|
||||
{
|
||||
"get": "list",
|
||||
"post": "create",
|
||||
}
|
||||
),
|
||||
IssueLinkViewSet.as_view({"get": "list", "post": "create"}),
|
||||
name="project-issue-links",
|
||||
),
|
||||
path(
|
||||
|
|
@ -169,12 +154,7 @@ urlpatterns = [
|
|||
## IssueComments
|
||||
path(
|
||||
"workspaces/<str:slug>/projects/<uuid:project_id>/issues/<uuid:issue_id>/comments/",
|
||||
IssueCommentViewSet.as_view(
|
||||
{
|
||||
"get": "list",
|
||||
"post": "create",
|
||||
}
|
||||
),
|
||||
IssueCommentViewSet.as_view({"get": "list", "post": "create"}),
|
||||
name="project-issue-comment",
|
||||
),
|
||||
path(
|
||||
|
|
@ -193,12 +173,7 @@ urlpatterns = [
|
|||
# Issue Subscribers
|
||||
path(
|
||||
"workspaces/<str:slug>/projects/<uuid:project_id>/issues/<uuid:issue_id>/issue-subscribers/",
|
||||
IssueSubscriberViewSet.as_view(
|
||||
{
|
||||
"get": "list",
|
||||
"post": "create",
|
||||
}
|
||||
),
|
||||
IssueSubscriberViewSet.as_view({"get": "list", "post": "create"}),
|
||||
name="project-issue-subscribers",
|
||||
),
|
||||
path(
|
||||
|
|
@ -209,11 +184,7 @@ urlpatterns = [
|
|||
path(
|
||||
"workspaces/<str:slug>/projects/<uuid:project_id>/issues/<uuid:issue_id>/subscribe/",
|
||||
IssueSubscriberViewSet.as_view(
|
||||
{
|
||||
"get": "subscription_status",
|
||||
"post": "subscribe",
|
||||
"delete": "unsubscribe",
|
||||
}
|
||||
{"get": "subscription_status", "post": "subscribe", "delete": "unsubscribe"}
|
||||
),
|
||||
name="project-issue-subscribers",
|
||||
),
|
||||
|
|
@ -221,42 +192,24 @@ urlpatterns = [
|
|||
# Issue Reactions
|
||||
path(
|
||||
"workspaces/<str:slug>/projects/<uuid:project_id>/issues/<uuid:issue_id>/reactions/",
|
||||
IssueReactionViewSet.as_view(
|
||||
{
|
||||
"get": "list",
|
||||
"post": "create",
|
||||
}
|
||||
),
|
||||
IssueReactionViewSet.as_view({"get": "list", "post": "create"}),
|
||||
name="project-issue-reactions",
|
||||
),
|
||||
path(
|
||||
"workspaces/<str:slug>/projects/<uuid:project_id>/issues/<uuid:issue_id>/reactions/<str:reaction_code>/",
|
||||
IssueReactionViewSet.as_view(
|
||||
{
|
||||
"delete": "destroy",
|
||||
}
|
||||
),
|
||||
IssueReactionViewSet.as_view({"delete": "destroy"}),
|
||||
name="project-issue-reactions",
|
||||
),
|
||||
## End Issue Reactions
|
||||
# Comment Reactions
|
||||
path(
|
||||
"workspaces/<str:slug>/projects/<uuid:project_id>/comments/<uuid:comment_id>/reactions/",
|
||||
CommentReactionViewSet.as_view(
|
||||
{
|
||||
"get": "list",
|
||||
"post": "create",
|
||||
}
|
||||
),
|
||||
CommentReactionViewSet.as_view({"get": "list", "post": "create"}),
|
||||
name="project-issue-comment-reactions",
|
||||
),
|
||||
path(
|
||||
"workspaces/<str:slug>/projects/<uuid:project_id>/comments/<uuid:comment_id>/reactions/<str:reaction_code>/",
|
||||
CommentReactionViewSet.as_view(
|
||||
{
|
||||
"delete": "destroy",
|
||||
}
|
||||
),
|
||||
CommentReactionViewSet.as_view({"delete": "destroy"}),
|
||||
name="project-issue-comment-reactions",
|
||||
),
|
||||
## End Comment Reactions
|
||||
|
|
@ -270,21 +223,13 @@ urlpatterns = [
|
|||
## Issue Archives
|
||||
path(
|
||||
"workspaces/<str:slug>/projects/<uuid:project_id>/archived-issues/",
|
||||
IssueArchiveViewSet.as_view(
|
||||
{
|
||||
"get": "list",
|
||||
}
|
||||
),
|
||||
IssueArchiveViewSet.as_view({"get": "list"}),
|
||||
name="project-issue-archive",
|
||||
),
|
||||
path(
|
||||
"workspaces/<str:slug>/projects/<uuid:project_id>/issues/<uuid:pk>/archive/",
|
||||
IssueArchiveViewSet.as_view(
|
||||
{
|
||||
"get": "retrieve",
|
||||
"post": "archive",
|
||||
"delete": "unarchive",
|
||||
}
|
||||
{"get": "retrieve", "post": "archive", "delete": "unarchive"}
|
||||
),
|
||||
name="project-issue-archive-unarchive",
|
||||
),
|
||||
|
|
@ -292,21 +237,12 @@ urlpatterns = [
|
|||
## Issue Relation
|
||||
path(
|
||||
"workspaces/<str:slug>/projects/<uuid:project_id>/issues/<uuid:issue_id>/issue-relation/",
|
||||
IssueRelationViewSet.as_view(
|
||||
{
|
||||
"get": "list",
|
||||
"post": "create",
|
||||
}
|
||||
),
|
||||
IssueRelationViewSet.as_view({"get": "list", "post": "create"}),
|
||||
name="issue-relation",
|
||||
),
|
||||
path(
|
||||
"workspaces/<str:slug>/projects/<uuid:project_id>/issues/<uuid:issue_id>/remove-relation/",
|
||||
IssueRelationViewSet.as_view(
|
||||
{
|
||||
"post": "remove_relation",
|
||||
}
|
||||
),
|
||||
IssueRelationViewSet.as_view({"post": "remove_relation"}),
|
||||
name="issue-relation",
|
||||
),
|
||||
## End Issue Relation
|
||||
|
|
|
|||
|
|
@ -14,12 +14,7 @@ from plane.app.views import (
|
|||
urlpatterns = [
|
||||
path(
|
||||
"workspaces/<str:slug>/projects/<uuid:project_id>/modules/",
|
||||
ModuleViewSet.as_view(
|
||||
{
|
||||
"get": "list",
|
||||
"post": "create",
|
||||
}
|
||||
),
|
||||
ModuleViewSet.as_view({"get": "list", "post": "create"}),
|
||||
name="project-modules",
|
||||
),
|
||||
path(
|
||||
|
|
@ -36,21 +31,12 @@ urlpatterns = [
|
|||
),
|
||||
path(
|
||||
"workspaces/<str:slug>/projects/<uuid:project_id>/issues/<uuid:issue_id>/modules/",
|
||||
ModuleIssueViewSet.as_view(
|
||||
{
|
||||
"post": "create_issue_modules",
|
||||
}
|
||||
),
|
||||
ModuleIssueViewSet.as_view({"post": "create_issue_modules"}),
|
||||
name="issue-module",
|
||||
),
|
||||
path(
|
||||
"workspaces/<str:slug>/projects/<uuid:project_id>/modules/<uuid:module_id>/issues/",
|
||||
ModuleIssueViewSet.as_view(
|
||||
{
|
||||
"post": "create_module_issues",
|
||||
"get": "list",
|
||||
}
|
||||
),
|
||||
ModuleIssueViewSet.as_view({"post": "create_module_issues", "get": "list"}),
|
||||
name="project-module-issues",
|
||||
),
|
||||
path(
|
||||
|
|
@ -67,12 +53,7 @@ urlpatterns = [
|
|||
),
|
||||
path(
|
||||
"workspaces/<str:slug>/projects/<uuid:project_id>/modules/<uuid:module_id>/module-links/",
|
||||
ModuleLinkViewSet.as_view(
|
||||
{
|
||||
"get": "list",
|
||||
"post": "create",
|
||||
}
|
||||
),
|
||||
ModuleLinkViewSet.as_view({"get": "list", "post": "create"}),
|
||||
name="project-issue-module-links",
|
||||
),
|
||||
path(
|
||||
|
|
@ -89,21 +70,12 @@ urlpatterns = [
|
|||
),
|
||||
path(
|
||||
"workspaces/<str:slug>/projects/<uuid:project_id>/user-favorite-modules/",
|
||||
ModuleFavoriteViewSet.as_view(
|
||||
{
|
||||
"get": "list",
|
||||
"post": "create",
|
||||
}
|
||||
),
|
||||
ModuleFavoriteViewSet.as_view({"get": "list", "post": "create"}),
|
||||
name="user-favorite-module",
|
||||
),
|
||||
path(
|
||||
"workspaces/<str:slug>/projects/<uuid:project_id>/user-favorite-modules/<uuid:module_id>/",
|
||||
ModuleFavoriteViewSet.as_view(
|
||||
{
|
||||
"delete": "destroy",
|
||||
}
|
||||
),
|
||||
ModuleFavoriteViewSet.as_view({"delete": "destroy"}),
|
||||
name="user-favorite-module",
|
||||
),
|
||||
path(
|
||||
|
|
|
|||
|
|
@ -12,42 +12,24 @@ from plane.app.views import (
|
|||
urlpatterns = [
|
||||
path(
|
||||
"workspaces/<str:slug>/users/notifications/",
|
||||
NotificationViewSet.as_view(
|
||||
{
|
||||
"get": "list",
|
||||
}
|
||||
),
|
||||
NotificationViewSet.as_view({"get": "list"}),
|
||||
name="notifications",
|
||||
),
|
||||
path(
|
||||
"workspaces/<str:slug>/users/notifications/<uuid:pk>/",
|
||||
NotificationViewSet.as_view(
|
||||
{
|
||||
"get": "retrieve",
|
||||
"patch": "partial_update",
|
||||
"delete": "destroy",
|
||||
}
|
||||
{"get": "retrieve", "patch": "partial_update", "delete": "destroy"}
|
||||
),
|
||||
name="notifications",
|
||||
),
|
||||
path(
|
||||
"workspaces/<str:slug>/users/notifications/<uuid:pk>/read/",
|
||||
NotificationViewSet.as_view(
|
||||
{
|
||||
"post": "mark_read",
|
||||
"delete": "mark_unread",
|
||||
}
|
||||
),
|
||||
NotificationViewSet.as_view({"post": "mark_read", "delete": "mark_unread"}),
|
||||
name="notifications",
|
||||
),
|
||||
path(
|
||||
"workspaces/<str:slug>/users/notifications/<uuid:pk>/archive/",
|
||||
NotificationViewSet.as_view(
|
||||
{
|
||||
"post": "archive",
|
||||
"delete": "unarchive",
|
||||
}
|
||||
),
|
||||
NotificationViewSet.as_view({"post": "archive", "delete": "unarchive"}),
|
||||
name="notifications",
|
||||
),
|
||||
path(
|
||||
|
|
@ -57,11 +39,7 @@ urlpatterns = [
|
|||
),
|
||||
path(
|
||||
"workspaces/<str:slug>/users/notifications/mark-all-read/",
|
||||
MarkAllReadNotificationViewSet.as_view(
|
||||
{
|
||||
"post": "create",
|
||||
}
|
||||
),
|
||||
MarkAllReadNotificationViewSet.as_view({"post": "create"}),
|
||||
name="mark-all-read-notifications",
|
||||
),
|
||||
path(
|
||||
|
|
|
|||
|
|
@ -14,66 +14,38 @@ from plane.app.views import (
|
|||
urlpatterns = [
|
||||
path(
|
||||
"workspaces/<str:slug>/projects/<uuid:project_id>/pages/",
|
||||
PageViewSet.as_view(
|
||||
{
|
||||
"get": "list",
|
||||
"post": "create",
|
||||
}
|
||||
),
|
||||
PageViewSet.as_view({"get": "list", "post": "create"}),
|
||||
name="project-pages",
|
||||
),
|
||||
path(
|
||||
"workspaces/<str:slug>/projects/<uuid:project_id>/pages/<uuid:pk>/",
|
||||
PageViewSet.as_view(
|
||||
{
|
||||
"get": "retrieve",
|
||||
"patch": "partial_update",
|
||||
"delete": "destroy",
|
||||
}
|
||||
{"get": "retrieve", "patch": "partial_update", "delete": "destroy"}
|
||||
),
|
||||
name="project-pages",
|
||||
),
|
||||
# favorite pages
|
||||
path(
|
||||
"workspaces/<str:slug>/projects/<uuid:project_id>/favorite-pages/<uuid:pk>/",
|
||||
PageFavoriteViewSet.as_view(
|
||||
{
|
||||
"post": "create",
|
||||
"delete": "destroy",
|
||||
}
|
||||
),
|
||||
PageFavoriteViewSet.as_view({"post": "create", "delete": "destroy"}),
|
||||
name="user-favorite-pages",
|
||||
),
|
||||
# archived pages
|
||||
path(
|
||||
"workspaces/<str:slug>/projects/<uuid:project_id>/pages/<uuid:pk>/archive/",
|
||||
PageViewSet.as_view(
|
||||
{
|
||||
"post": "archive",
|
||||
"delete": "unarchive",
|
||||
}
|
||||
),
|
||||
PageViewSet.as_view({"post": "archive", "delete": "unarchive"}),
|
||||
name="project-page-archive-unarchive",
|
||||
),
|
||||
# lock and unlock
|
||||
path(
|
||||
"workspaces/<str:slug>/projects/<uuid:project_id>/pages/<uuid:pk>/lock/",
|
||||
PageViewSet.as_view(
|
||||
{
|
||||
"post": "lock",
|
||||
"delete": "unlock",
|
||||
}
|
||||
),
|
||||
PageViewSet.as_view({"post": "lock", "delete": "unlock"}),
|
||||
name="project-pages-lock-unlock",
|
||||
),
|
||||
# private and public page
|
||||
path(
|
||||
"workspaces/<str:slug>/projects/<uuid:project_id>/pages/<uuid:pk>/access/",
|
||||
PageViewSet.as_view(
|
||||
{
|
||||
"post": "access",
|
||||
}
|
||||
),
|
||||
PageViewSet.as_view({"post": "access"}),
|
||||
name="project-pages-access",
|
||||
),
|
||||
path(
|
||||
|
|
@ -93,12 +65,7 @@ urlpatterns = [
|
|||
),
|
||||
path(
|
||||
"workspaces/<str:slug>/projects/<uuid:project_id>/pages/<uuid:pk>/description/",
|
||||
PagesDescriptionViewSet.as_view(
|
||||
{
|
||||
"get": "retrieve",
|
||||
"patch": "partial_update",
|
||||
}
|
||||
),
|
||||
PagesDescriptionViewSet.as_view({"get": "retrieve", "patch": "partial_update"}),
|
||||
name="page-description",
|
||||
),
|
||||
path(
|
||||
|
|
|
|||
|
|
@ -21,12 +21,7 @@ from plane.app.views import (
|
|||
urlpatterns = [
|
||||
path(
|
||||
"workspaces/<str:slug>/projects/",
|
||||
ProjectViewSet.as_view(
|
||||
{
|
||||
"get": "list",
|
||||
"post": "create",
|
||||
}
|
||||
),
|
||||
ProjectViewSet.as_view({"get": "list", "post": "create"}),
|
||||
name="project",
|
||||
),
|
||||
path(
|
||||
|
|
@ -48,32 +43,17 @@ urlpatterns = [
|
|||
),
|
||||
path(
|
||||
"workspaces/<str:slug>/projects/<uuid:project_id>/invitations/",
|
||||
ProjectInvitationsViewset.as_view(
|
||||
{
|
||||
"get": "list",
|
||||
"post": "create",
|
||||
},
|
||||
),
|
||||
ProjectInvitationsViewset.as_view({"get": "list", "post": "create"}),
|
||||
name="project-member-invite",
|
||||
),
|
||||
path(
|
||||
"workspaces/<str:slug>/projects/<uuid:project_id>/invitations/<uuid:pk>/",
|
||||
ProjectInvitationsViewset.as_view(
|
||||
{
|
||||
"get": "retrieve",
|
||||
"delete": "destroy",
|
||||
}
|
||||
),
|
||||
ProjectInvitationsViewset.as_view({"get": "retrieve", "delete": "destroy"}),
|
||||
name="project-member-invite",
|
||||
),
|
||||
path(
|
||||
"users/me/workspaces/<str:slug>/projects/invitations/",
|
||||
UserProjectInvitationsViewset.as_view(
|
||||
{
|
||||
"get": "list",
|
||||
"post": "create",
|
||||
},
|
||||
),
|
||||
UserProjectInvitationsViewset.as_view({"get": "list", "post": "create"}),
|
||||
name="user-project-invitations",
|
||||
),
|
||||
path(
|
||||
|
|
@ -88,32 +68,19 @@ urlpatterns = [
|
|||
),
|
||||
path(
|
||||
"workspaces/<str:slug>/projects/<uuid:project_id>/members/",
|
||||
ProjectMemberViewSet.as_view(
|
||||
{
|
||||
"get": "list",
|
||||
"post": "create",
|
||||
}
|
||||
),
|
||||
ProjectMemberViewSet.as_view({"get": "list", "post": "create"}),
|
||||
name="project-member",
|
||||
),
|
||||
path(
|
||||
"workspaces/<str:slug>/projects/<uuid:project_id>/members/<uuid:pk>/",
|
||||
ProjectMemberViewSet.as_view(
|
||||
{
|
||||
"get": "retrieve",
|
||||
"patch": "partial_update",
|
||||
"delete": "destroy",
|
||||
}
|
||||
{"get": "retrieve", "patch": "partial_update", "delete": "destroy"}
|
||||
),
|
||||
name="project-member",
|
||||
),
|
||||
path(
|
||||
"workspaces/<str:slug>/projects/<uuid:project_id>/members/leave/",
|
||||
ProjectMemberViewSet.as_view(
|
||||
{
|
||||
"post": "leave",
|
||||
}
|
||||
),
|
||||
ProjectMemberViewSet.as_view({"post": "leave"}),
|
||||
name="project-member",
|
||||
),
|
||||
path(
|
||||
|
|
@ -133,21 +100,12 @@ urlpatterns = [
|
|||
),
|
||||
path(
|
||||
"workspaces/<str:slug>/user-favorite-projects/",
|
||||
ProjectFavoritesViewSet.as_view(
|
||||
{
|
||||
"get": "list",
|
||||
"post": "create",
|
||||
}
|
||||
),
|
||||
ProjectFavoritesViewSet.as_view({"get": "list", "post": "create"}),
|
||||
name="project-favorite",
|
||||
),
|
||||
path(
|
||||
"workspaces/<str:slug>/user-favorite-projects/<uuid:project_id>/",
|
||||
ProjectFavoritesViewSet.as_view(
|
||||
{
|
||||
"delete": "destroy",
|
||||
}
|
||||
),
|
||||
ProjectFavoritesViewSet.as_view({"delete": "destroy"}),
|
||||
name="project-favorite",
|
||||
),
|
||||
path(
|
||||
|
|
@ -157,22 +115,13 @@ urlpatterns = [
|
|||
),
|
||||
path(
|
||||
"workspaces/<str:slug>/projects/<uuid:project_id>/project-deploy-boards/",
|
||||
DeployBoardViewSet.as_view(
|
||||
{
|
||||
"get": "list",
|
||||
"post": "create",
|
||||
}
|
||||
),
|
||||
DeployBoardViewSet.as_view({"get": "list", "post": "create"}),
|
||||
name="project-deploy-board",
|
||||
),
|
||||
path(
|
||||
"workspaces/<str:slug>/projects/<uuid:project_id>/project-deploy-boards/<uuid:pk>/",
|
||||
DeployBoardViewSet.as_view(
|
||||
{
|
||||
"get": "retrieve",
|
||||
"patch": "partial_update",
|
||||
"delete": "destroy",
|
||||
}
|
||||
{"get": "retrieve", "patch": "partial_update", "delete": "destroy"}
|
||||
),
|
||||
name="project-deploy-board",
|
||||
),
|
||||
|
|
|
|||
|
|
@ -1,10 +1,7 @@
|
|||
from django.urls import path
|
||||
|
||||
|
||||
from plane.app.views import (
|
||||
GlobalSearchEndpoint,
|
||||
IssueSearchEndpoint,
|
||||
)
|
||||
from plane.app.views import GlobalSearchEndpoint, IssueSearchEndpoint
|
||||
|
||||
|
||||
urlpatterns = [
|
||||
|
|
|
|||
|
|
@ -7,32 +7,19 @@ from plane.app.views import StateViewSet
|
|||
urlpatterns = [
|
||||
path(
|
||||
"workspaces/<str:slug>/projects/<uuid:project_id>/states/",
|
||||
StateViewSet.as_view(
|
||||
{
|
||||
"get": "list",
|
||||
"post": "create",
|
||||
}
|
||||
),
|
||||
StateViewSet.as_view({"get": "list", "post": "create"}),
|
||||
name="project-states",
|
||||
),
|
||||
path(
|
||||
"workspaces/<str:slug>/projects/<uuid:project_id>/states/<uuid:pk>/",
|
||||
StateViewSet.as_view(
|
||||
{
|
||||
"get": "retrieve",
|
||||
"patch": "partial_update",
|
||||
"delete": "destroy",
|
||||
}
|
||||
{"get": "retrieve", "patch": "partial_update", "delete": "destroy"}
|
||||
),
|
||||
name="project-state",
|
||||
),
|
||||
path(
|
||||
"workspaces/<str:slug>/projects/<uuid:project_id>/states/<uuid:pk>/mark-default/",
|
||||
StateViewSet.as_view(
|
||||
{
|
||||
"post": "mark_as_default",
|
||||
}
|
||||
),
|
||||
StateViewSet.as_view({"post": "mark_as_default"}),
|
||||
name="project-state",
|
||||
),
|
||||
]
|
||||
|
|
|
|||
|
|
@ -22,60 +22,30 @@ urlpatterns = [
|
|||
path(
|
||||
"users/me/",
|
||||
UserEndpoint.as_view(
|
||||
{
|
||||
"get": "retrieve",
|
||||
"patch": "partial_update",
|
||||
"delete": "deactivate",
|
||||
}
|
||||
{"get": "retrieve", "patch": "partial_update", "delete": "deactivate"}
|
||||
),
|
||||
name="users",
|
||||
),
|
||||
path(
|
||||
"users/session/",
|
||||
UserSessionEndpoint.as_view(),
|
||||
name="user-session",
|
||||
),
|
||||
path("users/session/", UserSessionEndpoint.as_view(), name="user-session"),
|
||||
path(
|
||||
"users/me/settings/",
|
||||
UserEndpoint.as_view(
|
||||
{
|
||||
"get": "retrieve_user_settings",
|
||||
}
|
||||
),
|
||||
UserEndpoint.as_view({"get": "retrieve_user_settings"}),
|
||||
name="users",
|
||||
),
|
||||
# Profile
|
||||
path(
|
||||
"users/me/profile/",
|
||||
ProfileEndpoint.as_view(),
|
||||
name="accounts",
|
||||
),
|
||||
path("users/me/profile/", ProfileEndpoint.as_view(), name="accounts"),
|
||||
# End profile
|
||||
# Accounts
|
||||
path(
|
||||
"users/me/accounts/",
|
||||
AccountEndpoint.as_view(),
|
||||
name="accounts",
|
||||
),
|
||||
path(
|
||||
"users/me/accounts/<uuid:pk>/",
|
||||
AccountEndpoint.as_view(),
|
||||
name="accounts",
|
||||
),
|
||||
path("users/me/accounts/", AccountEndpoint.as_view(), name="accounts"),
|
||||
path("users/me/accounts/<uuid:pk>/", AccountEndpoint.as_view(), name="accounts"),
|
||||
## End Accounts
|
||||
path(
|
||||
"users/me/instance-admin/",
|
||||
UserEndpoint.as_view(
|
||||
{
|
||||
"get": "retrieve_instance_admin",
|
||||
}
|
||||
),
|
||||
UserEndpoint.as_view({"get": "retrieve_instance_admin"}),
|
||||
name="users",
|
||||
),
|
||||
path(
|
||||
"users/me/onboard/",
|
||||
UpdateUserOnBoardedEndpoint.as_view(),
|
||||
name="user-onboard",
|
||||
"users/me/onboard/", UpdateUserOnBoardedEndpoint.as_view(), name="user-onboard"
|
||||
),
|
||||
path(
|
||||
"users/me/tour-completed/",
|
||||
|
|
@ -83,15 +53,11 @@ urlpatterns = [
|
|||
name="user-tour",
|
||||
),
|
||||
path(
|
||||
"users/me/activities/",
|
||||
UserActivityEndpoint.as_view(),
|
||||
name="user-activities",
|
||||
"users/me/activities/", UserActivityEndpoint.as_view(), name="user-activities"
|
||||
),
|
||||
# user workspaces
|
||||
path(
|
||||
"users/me/workspaces/",
|
||||
UserWorkSpacesEndpoint.as_view(),
|
||||
name="user-workspace",
|
||||
"users/me/workspaces/", UserWorkSpacesEndpoint.as_view(), name="user-workspace"
|
||||
),
|
||||
# User Graphs
|
||||
path(
|
||||
|
|
|
|||
|
|
@ -12,12 +12,7 @@ from plane.app.views import (
|
|||
urlpatterns = [
|
||||
path(
|
||||
"workspaces/<str:slug>/projects/<uuid:project_id>/views/",
|
||||
IssueViewViewSet.as_view(
|
||||
{
|
||||
"get": "list",
|
||||
"post": "create",
|
||||
}
|
||||
),
|
||||
IssueViewViewSet.as_view({"get": "list", "post": "create"}),
|
||||
name="project-view",
|
||||
),
|
||||
path(
|
||||
|
|
@ -34,12 +29,7 @@ urlpatterns = [
|
|||
),
|
||||
path(
|
||||
"workspaces/<str:slug>/views/",
|
||||
WorkspaceViewViewSet.as_view(
|
||||
{
|
||||
"get": "list",
|
||||
"post": "create",
|
||||
}
|
||||
),
|
||||
WorkspaceViewViewSet.as_view({"get": "list", "post": "create"}),
|
||||
name="global-view",
|
||||
),
|
||||
path(
|
||||
|
|
@ -56,30 +46,17 @@ urlpatterns = [
|
|||
),
|
||||
path(
|
||||
"workspaces/<str:slug>/issues/",
|
||||
WorkspaceViewIssuesViewSet.as_view(
|
||||
{
|
||||
"get": "list",
|
||||
}
|
||||
),
|
||||
WorkspaceViewIssuesViewSet.as_view({"get": "list"}),
|
||||
name="global-view-issues",
|
||||
),
|
||||
path(
|
||||
"workspaces/<str:slug>/projects/<uuid:project_id>/user-favorite-views/",
|
||||
IssueViewFavoriteViewSet.as_view(
|
||||
{
|
||||
"get": "list",
|
||||
"post": "create",
|
||||
}
|
||||
),
|
||||
IssueViewFavoriteViewSet.as_view({"get": "list", "post": "create"}),
|
||||
name="user-favorite-view",
|
||||
),
|
||||
path(
|
||||
"workspaces/<str:slug>/projects/<uuid:project_id>/user-favorite-views/<uuid:view_id>/",
|
||||
IssueViewFavoriteViewSet.as_view(
|
||||
{
|
||||
"delete": "destroy",
|
||||
}
|
||||
),
|
||||
IssueViewFavoriteViewSet.as_view({"delete": "destroy"}),
|
||||
name="user-favorite-view",
|
||||
),
|
||||
]
|
||||
|
|
|
|||
|
|
@ -8,11 +8,7 @@ from plane.app.views import (
|
|||
|
||||
|
||||
urlpatterns = [
|
||||
path(
|
||||
"workspaces/<str:slug>/webhooks/",
|
||||
WebhookEndpoint.as_view(),
|
||||
name="webhooks",
|
||||
),
|
||||
path("workspaces/<str:slug>/webhooks/", WebhookEndpoint.as_view(), name="webhooks"),
|
||||
path(
|
||||
"workspaces/<str:slug>/webhooks/<uuid:pk>/",
|
||||
WebhookEndpoint.as_view(),
|
||||
|
|
|
|||
|
|
@ -39,12 +39,7 @@ urlpatterns = [
|
|||
),
|
||||
path(
|
||||
"workspaces/",
|
||||
WorkSpaceViewSet.as_view(
|
||||
{
|
||||
"get": "list",
|
||||
"post": "create",
|
||||
}
|
||||
),
|
||||
WorkSpaceViewSet.as_view({"get": "list", "post": "create"}),
|
||||
name="workspace",
|
||||
),
|
||||
path(
|
||||
|
|
@ -61,34 +56,20 @@ urlpatterns = [
|
|||
),
|
||||
path(
|
||||
"workspaces/<str:slug>/invitations/",
|
||||
WorkspaceInvitationsViewset.as_view(
|
||||
{
|
||||
"get": "list",
|
||||
"post": "create",
|
||||
},
|
||||
),
|
||||
WorkspaceInvitationsViewset.as_view({"get": "list", "post": "create"}),
|
||||
name="workspace-invitations",
|
||||
),
|
||||
path(
|
||||
"workspaces/<str:slug>/invitations/<uuid:pk>/",
|
||||
WorkspaceInvitationsViewset.as_view(
|
||||
{
|
||||
"delete": "destroy",
|
||||
"get": "retrieve",
|
||||
"patch": "partial_update",
|
||||
}
|
||||
{"delete": "destroy", "get": "retrieve", "patch": "partial_update"}
|
||||
),
|
||||
name="workspace-invitations",
|
||||
),
|
||||
# user workspace invitations
|
||||
path(
|
||||
"users/me/workspaces/invitations/",
|
||||
UserWorkspaceInvitationsViewSet.as_view(
|
||||
{
|
||||
"get": "list",
|
||||
"post": "create",
|
||||
},
|
||||
),
|
||||
UserWorkspaceInvitationsViewSet.as_view({"get": "list", "post": "create"}),
|
||||
name="user-workspace-invitations",
|
||||
),
|
||||
path(
|
||||
|
|
@ -110,31 +91,18 @@ urlpatterns = [
|
|||
path(
|
||||
"workspaces/<str:slug>/members/<uuid:pk>/",
|
||||
WorkSpaceMemberViewSet.as_view(
|
||||
{
|
||||
"patch": "partial_update",
|
||||
"delete": "destroy",
|
||||
"get": "retrieve",
|
||||
}
|
||||
{"patch": "partial_update", "delete": "destroy", "get": "retrieve"}
|
||||
),
|
||||
name="workspace-member",
|
||||
),
|
||||
path(
|
||||
"workspaces/<str:slug>/members/leave/",
|
||||
WorkSpaceMemberViewSet.as_view(
|
||||
{
|
||||
"post": "leave",
|
||||
},
|
||||
),
|
||||
WorkSpaceMemberViewSet.as_view({"post": "leave"}),
|
||||
name="leave-workspace-members",
|
||||
),
|
||||
path(
|
||||
"workspaces/<str:slug>/teams/",
|
||||
TeamMemberViewSet.as_view(
|
||||
{
|
||||
"get": "list",
|
||||
"post": "create",
|
||||
}
|
||||
),
|
||||
TeamMemberViewSet.as_view({"get": "list", "post": "create"}),
|
||||
name="workspace-team-members",
|
||||
),
|
||||
path(
|
||||
|
|
@ -166,22 +134,13 @@ urlpatterns = [
|
|||
),
|
||||
path(
|
||||
"workspaces/<str:slug>/workspace-themes/",
|
||||
WorkspaceThemeViewSet.as_view(
|
||||
{
|
||||
"get": "list",
|
||||
"post": "create",
|
||||
}
|
||||
),
|
||||
WorkspaceThemeViewSet.as_view({"get": "list", "post": "create"}),
|
||||
name="workspace-themes",
|
||||
),
|
||||
path(
|
||||
"workspaces/<str:slug>/workspace-themes/<uuid:pk>/",
|
||||
WorkspaceThemeViewSet.as_view(
|
||||
{
|
||||
"get": "retrieve",
|
||||
"patch": "partial_update",
|
||||
"delete": "destroy",
|
||||
}
|
||||
{"get": "retrieve", "patch": "partial_update", "delete": "destroy"}
|
||||
),
|
||||
name="workspace-themes",
|
||||
),
|
||||
|
|
@ -257,22 +216,13 @@ urlpatterns = [
|
|||
),
|
||||
path(
|
||||
"workspaces/<str:slug>/draft-issues/",
|
||||
WorkspaceDraftIssueViewSet.as_view(
|
||||
{
|
||||
"get": "list",
|
||||
"post": "create",
|
||||
}
|
||||
),
|
||||
WorkspaceDraftIssueViewSet.as_view({"get": "list", "post": "create"}),
|
||||
name="workspace-draft-issues",
|
||||
),
|
||||
path(
|
||||
"workspaces/<str:slug>/draft-issues/<uuid:pk>/",
|
||||
WorkspaceDraftIssueViewSet.as_view(
|
||||
{
|
||||
"get": "retrieve",
|
||||
"patch": "partial_update",
|
||||
"delete": "destroy",
|
||||
}
|
||||
{"get": "retrieve", "patch": "partial_update", "delete": "destroy"}
|
||||
),
|
||||
name="workspace-drafts-issues",
|
||||
),
|
||||
|
|
|
|||
|
|
@ -59,12 +59,8 @@ from .workspace.invite import (
|
|||
WorkspaceJoinEndpoint,
|
||||
UserWorkspaceInvitationsViewSet,
|
||||
)
|
||||
from .workspace.label import (
|
||||
WorkspaceLabelsEndpoint,
|
||||
)
|
||||
from .workspace.state import (
|
||||
WorkspaceStatesEndpoint,
|
||||
)
|
||||
from .workspace.label import WorkspaceLabelsEndpoint
|
||||
from .workspace.state import WorkspaceStatesEndpoint
|
||||
from .workspace.user import (
|
||||
UserLastProjectWithWorkspaceEndpoint,
|
||||
WorkspaceUserProfileIssuesEndpoint,
|
||||
|
|
@ -75,15 +71,9 @@ from .workspace.user import (
|
|||
UserActivityGraphEndpoint,
|
||||
UserIssueCompletedGraphEndpoint,
|
||||
)
|
||||
from .workspace.estimate import (
|
||||
WorkspaceEstimatesEndpoint,
|
||||
)
|
||||
from .workspace.module import (
|
||||
WorkspaceModulesEndpoint,
|
||||
)
|
||||
from .workspace.cycle import (
|
||||
WorkspaceCyclesEndpoint,
|
||||
)
|
||||
from .workspace.estimate import WorkspaceEstimatesEndpoint
|
||||
from .workspace.module import WorkspaceModulesEndpoint
|
||||
from .workspace.cycle import WorkspaceCyclesEndpoint
|
||||
|
||||
from .state.base import StateViewSet
|
||||
from .view.base import (
|
||||
|
|
@ -103,18 +93,10 @@ from .cycle.base import (
|
|||
CycleAnalyticsEndpoint,
|
||||
CycleProgressEndpoint,
|
||||
)
|
||||
from .cycle.issue import (
|
||||
CycleIssueViewSet,
|
||||
)
|
||||
from .cycle.archive import (
|
||||
CycleArchiveUnarchiveEndpoint,
|
||||
)
|
||||
from .cycle.issue import CycleIssueViewSet
|
||||
from .cycle.archive import CycleArchiveUnarchiveEndpoint
|
||||
|
||||
from .asset.base import (
|
||||
FileAssetEndpoint,
|
||||
UserAssetsEndpoint,
|
||||
FileAssetViewSet,
|
||||
)
|
||||
from .asset.base import FileAssetEndpoint, UserAssetsEndpoint, FileAssetViewSet
|
||||
from .asset.v2 import (
|
||||
WorkspaceFileAssetEndpoint,
|
||||
UserAssetsV2Endpoint,
|
||||
|
|
@ -134,9 +116,7 @@ from .issue.base import (
|
|||
IssueBulkUpdateDateEndpoint,
|
||||
)
|
||||
|
||||
from .issue.activity import (
|
||||
IssueActivityEndpoint,
|
||||
)
|
||||
from .issue.activity import IssueActivityEndpoint
|
||||
|
||||
from .issue.archive import IssueArchiveViewSet, BulkArchiveIssuesEndpoint
|
||||
|
||||
|
|
@ -146,35 +126,19 @@ from .issue.attachment import (
|
|||
IssueAttachmentV2Endpoint,
|
||||
)
|
||||
|
||||
from .issue.comment import (
|
||||
IssueCommentViewSet,
|
||||
CommentReactionViewSet,
|
||||
)
|
||||
from .issue.comment import IssueCommentViewSet, CommentReactionViewSet
|
||||
|
||||
from .issue.label import (
|
||||
LabelViewSet,
|
||||
BulkCreateIssueLabelsEndpoint,
|
||||
)
|
||||
from .issue.label import LabelViewSet, BulkCreateIssueLabelsEndpoint
|
||||
|
||||
from .issue.link import (
|
||||
IssueLinkViewSet,
|
||||
)
|
||||
from .issue.link import IssueLinkViewSet
|
||||
|
||||
from .issue.relation import (
|
||||
IssueRelationViewSet,
|
||||
)
|
||||
from .issue.relation import IssueRelationViewSet
|
||||
|
||||
from .issue.reaction import (
|
||||
IssueReactionViewSet,
|
||||
)
|
||||
from .issue.reaction import IssueReactionViewSet
|
||||
|
||||
from .issue.sub_issue import (
|
||||
SubIssuesEndpoint,
|
||||
)
|
||||
from .issue.sub_issue import SubIssuesEndpoint
|
||||
|
||||
from .issue.subscriber import (
|
||||
IssueSubscriberViewSet,
|
||||
)
|
||||
from .issue.subscriber import IssueSubscriberViewSet
|
||||
|
||||
from .module.base import (
|
||||
ModuleViewSet,
|
||||
|
|
@ -183,18 +147,11 @@ from .module.base import (
|
|||
ModuleUserPropertiesEndpoint,
|
||||
)
|
||||
|
||||
from .module.issue import (
|
||||
ModuleIssueViewSet,
|
||||
)
|
||||
from .module.issue import ModuleIssueViewSet
|
||||
|
||||
from .module.archive import (
|
||||
ModuleArchiveUnarchiveEndpoint,
|
||||
)
|
||||
from .module.archive import ModuleArchiveUnarchiveEndpoint
|
||||
|
||||
from .api import (
|
||||
ApiTokenEndpoint,
|
||||
ServiceApiTokenEndpoint,
|
||||
)
|
||||
from .api import ApiTokenEndpoint, ServiceApiTokenEndpoint
|
||||
|
||||
from .page.base import (
|
||||
PageViewSet,
|
||||
|
|
|
|||
|
|
@ -22,14 +22,7 @@ from plane.app.permissions import allow_permission, ROLE
|
|||
|
||||
|
||||
class AnalyticsEndpoint(BaseAPIView):
|
||||
|
||||
@allow_permission(
|
||||
[
|
||||
ROLE.ADMIN,
|
||||
ROLE.MEMBER,
|
||||
],
|
||||
level="WORKSPACE",
|
||||
)
|
||||
@allow_permission([ROLE.ADMIN, ROLE.MEMBER], level="WORKSPACE")
|
||||
def get(self, request, slug):
|
||||
x_axis = request.GET.get("x_axis", False)
|
||||
y_axis = request.GET.get("y_axis", False)
|
||||
|
|
@ -50,10 +43,7 @@ class AnalyticsEndpoint(BaseAPIView):
|
|||
"completed_at",
|
||||
]
|
||||
|
||||
valid_yaxis = [
|
||||
"issue_count",
|
||||
"estimate",
|
||||
]
|
||||
valid_yaxis = ["issue_count", "estimate"]
|
||||
|
||||
# Check for x-axis and y-axis as thery are required parameters
|
||||
if (
|
||||
|
|
@ -70,9 +60,7 @@ class AnalyticsEndpoint(BaseAPIView):
|
|||
)
|
||||
|
||||
# If segment is present it cannot be same as x-axis
|
||||
if segment and (
|
||||
segment not in valid_xaxis_segment or x_axis == segment
|
||||
):
|
||||
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"
|
||||
|
|
@ -97,10 +85,7 @@ class AnalyticsEndpoint(BaseAPIView):
|
|||
state_details = {}
|
||||
if x_axis in ["state_id"] or segment in ["state_id"]:
|
||||
state_details = (
|
||||
Issue.issue_objects.filter(
|
||||
workspace__slug=slug,
|
||||
**filters,
|
||||
)
|
||||
Issue.issue_objects.filter(workspace__slug=slug, **filters)
|
||||
.distinct("state_id")
|
||||
.order_by("state_id")
|
||||
.values("state_id", "state__name", "state__color")
|
||||
|
|
@ -163,9 +148,7 @@ class AnalyticsEndpoint(BaseAPIView):
|
|||
)
|
||||
|
||||
cycle_details = {}
|
||||
if x_axis in ["issue_cycle__cycle_id"] or segment in [
|
||||
"issue_cycle__cycle_id"
|
||||
]:
|
||||
if x_axis in ["issue_cycle__cycle_id"] or segment in ["issue_cycle__cycle_id"]:
|
||||
cycle_details = (
|
||||
Issue.issue_objects.filter(
|
||||
workspace__slug=slug,
|
||||
|
|
@ -175,10 +158,7 @@ class AnalyticsEndpoint(BaseAPIView):
|
|||
)
|
||||
.distinct("issue_cycle__cycle_id")
|
||||
.order_by("issue_cycle__cycle_id")
|
||||
.values(
|
||||
"issue_cycle__cycle_id",
|
||||
"issue_cycle__cycle__name",
|
||||
)
|
||||
.values("issue_cycle__cycle_id", "issue_cycle__cycle__name")
|
||||
)
|
||||
|
||||
module_details = {}
|
||||
|
|
@ -194,10 +174,7 @@ class AnalyticsEndpoint(BaseAPIView):
|
|||
)
|
||||
.distinct("issue_module__module_id")
|
||||
.order_by("issue_module__module_id")
|
||||
.values(
|
||||
"issue_module__module_id",
|
||||
"issue_module__module__name",
|
||||
)
|
||||
.values("issue_module__module_id", "issue_module__module__name")
|
||||
)
|
||||
|
||||
return Response(
|
||||
|
|
@ -217,9 +194,7 @@ class AnalyticsEndpoint(BaseAPIView):
|
|||
|
||||
|
||||
class AnalyticViewViewset(BaseViewSet):
|
||||
permission_classes = [
|
||||
WorkSpaceAdminPermission,
|
||||
]
|
||||
permission_classes = [WorkSpaceAdminPermission]
|
||||
model = AnalyticView
|
||||
serializer_class = AnalyticViewSerializer
|
||||
|
||||
|
|
@ -229,25 +204,14 @@ class AnalyticViewViewset(BaseViewSet):
|
|||
|
||||
def get_queryset(self):
|
||||
return self.filter_queryset(
|
||||
super()
|
||||
.get_queryset()
|
||||
.filter(workspace__slug=self.kwargs.get("slug"))
|
||||
super().get_queryset().filter(workspace__slug=self.kwargs.get("slug"))
|
||||
)
|
||||
|
||||
|
||||
class SavedAnalyticEndpoint(BaseAPIView):
|
||||
|
||||
@allow_permission(
|
||||
[
|
||||
ROLE.ADMIN,
|
||||
ROLE.MEMBER,
|
||||
],
|
||||
level="WORKSPACE",
|
||||
)
|
||||
@allow_permission([ROLE.ADMIN, ROLE.MEMBER], level="WORKSPACE")
|
||||
def get(self, request, slug, analytic_id):
|
||||
analytic_view = AnalyticView.objects.get(
|
||||
pk=analytic_id, workspace__slug=slug
|
||||
)
|
||||
analytic_view = AnalyticView.objects.get(pk=analytic_id, workspace__slug=slug)
|
||||
|
||||
filter = analytic_view.query
|
||||
queryset = Issue.issue_objects.filter(**filter)
|
||||
|
|
@ -273,14 +237,7 @@ class SavedAnalyticEndpoint(BaseAPIView):
|
|||
|
||||
|
||||
class ExportAnalyticsEndpoint(BaseAPIView):
|
||||
|
||||
@allow_permission(
|
||||
[
|
||||
ROLE.ADMIN,
|
||||
ROLE.MEMBER,
|
||||
],
|
||||
level="WORKSPACE",
|
||||
)
|
||||
@allow_permission([ROLE.ADMIN, ROLE.MEMBER], level="WORKSPACE")
|
||||
def post(self, request, slug):
|
||||
x_axis = request.data.get("x_axis", False)
|
||||
y_axis = request.data.get("y_axis", False)
|
||||
|
|
@ -301,10 +258,7 @@ class ExportAnalyticsEndpoint(BaseAPIView):
|
|||
"completed_at",
|
||||
]
|
||||
|
||||
valid_yaxis = [
|
||||
"issue_count",
|
||||
"estimate",
|
||||
]
|
||||
valid_yaxis = ["issue_count", "estimate"]
|
||||
|
||||
# Check for x-axis and y-axis as thery are required parameters
|
||||
if (
|
||||
|
|
@ -321,9 +275,7 @@ class ExportAnalyticsEndpoint(BaseAPIView):
|
|||
)
|
||||
|
||||
# If segment is present it cannot be same as x-axis
|
||||
if segment and (
|
||||
segment not in valid_xaxis_segment or x_axis == segment
|
||||
):
|
||||
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"
|
||||
|
|
@ -344,13 +296,10 @@ class ExportAnalyticsEndpoint(BaseAPIView):
|
|||
|
||||
|
||||
class DefaultAnalyticsEndpoint(BaseAPIView):
|
||||
|
||||
@allow_permission([ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST], level="WORKSPACE")
|
||||
def get(self, request, slug):
|
||||
filters = issue_filters(request.GET, "GET")
|
||||
base_issues = Issue.issue_objects.filter(
|
||||
workspace__slug=slug, **filters
|
||||
)
|
||||
base_issues = Issue.issue_objects.filter(workspace__slug=slug, **filters)
|
||||
|
||||
total_issues = base_issues.count()
|
||||
|
||||
|
|
@ -363,9 +312,7 @@ class DefaultAnalyticsEndpoint(BaseAPIView):
|
|||
)
|
||||
|
||||
open_issues_groups = ["backlog", "unstarted", "started"]
|
||||
open_issues_queryset = state_groups.filter(
|
||||
state__group__in=open_issues_groups
|
||||
)
|
||||
open_issues_queryset = state_groups.filter(state__group__in=open_issues_groups)
|
||||
|
||||
open_issues = open_issues_queryset.count()
|
||||
open_issues_classified = (
|
||||
|
|
@ -407,8 +354,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",
|
||||
created_by__avatar_asset__isnull=True, then="created_by__avatar"
|
||||
),
|
||||
default=Value(None),
|
||||
output_field=models.CharField(),
|
||||
|
|
@ -441,8 +387,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",
|
||||
assignees__avatar_asset__isnull=True, then="assignees__avatar"
|
||||
),
|
||||
default=Value(None),
|
||||
output_field=models.CharField(),
|
||||
|
|
@ -469,8 +414,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",
|
||||
assignees__avatar_asset__isnull=True, then="assignees__avatar"
|
||||
),
|
||||
default=Value(None),
|
||||
output_field=models.CharField(),
|
||||
|
|
@ -479,9 +423,7 @@ class DefaultAnalyticsEndpoint(BaseAPIView):
|
|||
.order_by("-count")
|
||||
)
|
||||
|
||||
open_estimate_sum = open_issues_queryset.aggregate(sum=Sum("point"))[
|
||||
"sum"
|
||||
]
|
||||
open_estimate_sum = open_issues_queryset.aggregate(sum=Sum("point"))["sum"]
|
||||
total_estimate_sum = base_issues.aggregate(sum=Sum("point"))["sum"]
|
||||
|
||||
return Response(
|
||||
|
|
|
|||
|
|
@ -13,9 +13,7 @@ from plane.app.permissions import WorkspaceOwnerPermission
|
|||
|
||||
|
||||
class ApiTokenEndpoint(BaseAPIView):
|
||||
permission_classes = [
|
||||
WorkspaceOwnerPermission,
|
||||
]
|
||||
permission_classes = [WorkspaceOwnerPermission]
|
||||
|
||||
def post(self, request, slug):
|
||||
label = request.data.get("label", str(uuid4().hex))
|
||||
|
|
@ -37,10 +35,7 @@ class ApiTokenEndpoint(BaseAPIView):
|
|||
|
||||
serializer = APITokenSerializer(api_token)
|
||||
# Token will be only visible while creating
|
||||
return Response(
|
||||
serializer.data,
|
||||
status=status.HTTP_201_CREATED,
|
||||
)
|
||||
return Response(serializer.data, status=status.HTTP_201_CREATED)
|
||||
|
||||
def get(self, request, slug, pk=None):
|
||||
if pk is None:
|
||||
|
|
@ -58,23 +53,14 @@ class ApiTokenEndpoint(BaseAPIView):
|
|||
|
||||
def delete(self, request, slug, pk):
|
||||
api_token = APIToken.objects.get(
|
||||
workspace__slug=slug,
|
||||
user=request.user,
|
||||
pk=pk,
|
||||
is_service=False,
|
||||
workspace__slug=slug, user=request.user, pk=pk, is_service=False
|
||||
)
|
||||
api_token.delete()
|
||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
def patch(self, request, slug, pk):
|
||||
api_token = APIToken.objects.get(
|
||||
workspace__slug=slug,
|
||||
user=request.user,
|
||||
pk=pk,
|
||||
)
|
||||
serializer = APITokenSerializer(
|
||||
api_token, data=request.data, partial=True
|
||||
)
|
||||
api_token = APIToken.objects.get(workspace__slug=slug, user=request.user, pk=pk)
|
||||
serializer = APITokenSerializer(api_token, data=request.data, partial=True)
|
||||
if serializer.is_valid():
|
||||
serializer.save()
|
||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||
|
|
@ -82,25 +68,17 @@ class ApiTokenEndpoint(BaseAPIView):
|
|||
|
||||
|
||||
class ServiceApiTokenEndpoint(BaseAPIView):
|
||||
permission_classes = [
|
||||
WorkspaceOwnerPermission,
|
||||
]
|
||||
permission_classes = [WorkspaceOwnerPermission]
|
||||
|
||||
def post(self, request, slug):
|
||||
workspace = Workspace.objects.get(slug=slug)
|
||||
|
||||
api_token = APIToken.objects.filter(
|
||||
workspace=workspace,
|
||||
is_service=True,
|
||||
workspace=workspace, is_service=True
|
||||
).first()
|
||||
|
||||
if api_token:
|
||||
return Response(
|
||||
{
|
||||
"token": str(api_token.token),
|
||||
},
|
||||
status=status.HTTP_200_OK,
|
||||
)
|
||||
return Response({"token": str(api_token.token)}, status=status.HTTP_200_OK)
|
||||
else:
|
||||
# Check the user type
|
||||
user_type = 1 if request.user.is_bot else 0
|
||||
|
|
@ -114,9 +92,5 @@ class ServiceApiTokenEndpoint(BaseAPIView):
|
|||
is_service=True,
|
||||
)
|
||||
return Response(
|
||||
{
|
||||
"token": str(api_token.token),
|
||||
},
|
||||
status=status.HTTP_201_CREATED,
|
||||
{"token": str(api_token.token)}, status=status.HTTP_201_CREATED
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -10,11 +10,7 @@ from plane.app.serializers import FileAssetSerializer
|
|||
|
||||
|
||||
class FileAssetEndpoint(BaseAPIView):
|
||||
parser_classes = (
|
||||
MultiPartParser,
|
||||
FormParser,
|
||||
JSONParser,
|
||||
)
|
||||
parser_classes = (MultiPartParser, FormParser, JSONParser)
|
||||
|
||||
"""
|
||||
A viewset for viewing and editing task instances.
|
||||
|
|
@ -28,8 +24,7 @@ class FileAssetEndpoint(BaseAPIView):
|
|||
files, context={"request": request}, many=True
|
||||
)
|
||||
return Response(
|
||||
{"data": serializer.data, "status": True},
|
||||
status=status.HTTP_200_OK,
|
||||
{"data": serializer.data, "status": True}, status=status.HTTP_200_OK
|
||||
)
|
||||
else:
|
||||
return Response(
|
||||
|
|
@ -67,16 +62,11 @@ class UserAssetsEndpoint(BaseAPIView):
|
|||
parser_classes = (MultiPartParser, FormParser)
|
||||
|
||||
def get(self, request, asset_key):
|
||||
files = FileAsset.objects.filter(
|
||||
asset=asset_key, created_by=request.user
|
||||
)
|
||||
files = FileAsset.objects.filter(asset=asset_key, created_by=request.user)
|
||||
if files.exists():
|
||||
serializer = FileAssetSerializer(
|
||||
files, context={"request": request}
|
||||
)
|
||||
serializer = FileAssetSerializer(files, context={"request": request})
|
||||
return Response(
|
||||
{"data": serializer.data, "status": True},
|
||||
status=status.HTTP_200_OK,
|
||||
{"data": serializer.data, "status": True}, status=status.HTTP_200_OK
|
||||
)
|
||||
else:
|
||||
return Response(
|
||||
|
|
@ -92,9 +82,7 @@ class UserAssetsEndpoint(BaseAPIView):
|
|||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
def delete(self, request, asset_key):
|
||||
file_asset = FileAsset.objects.get(
|
||||
asset=asset_key, created_by=request.user
|
||||
)
|
||||
file_asset = FileAsset.objects.get(asset=asset_key, created_by=request.user)
|
||||
file_asset.is_deleted = True
|
||||
file_asset.save(update_fields=["is_deleted"])
|
||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||
|
|
|
|||
|
|
@ -13,12 +13,7 @@ from rest_framework.permissions import AllowAny
|
|||
|
||||
# Module imports
|
||||
from ..base import BaseAPIView
|
||||
from plane.db.models import (
|
||||
FileAsset,
|
||||
Workspace,
|
||||
Project,
|
||||
User,
|
||||
)
|
||||
from plane.db.models import FileAsset, Workspace, Project, User
|
||||
from plane.settings.storage import S3Storage
|
||||
from plane.app.permissions import allow_permission, ROLE
|
||||
from plane.utils.cache import invalidate_cache_directly
|
||||
|
|
@ -49,10 +44,7 @@ class UserAssetsV2Endpoint(BaseAPIView):
|
|||
user.avatar_asset_id = asset_id
|
||||
user.save()
|
||||
invalidate_cache_directly(
|
||||
path="/api/users/me/",
|
||||
url_params=False,
|
||||
user=True,
|
||||
request=request,
|
||||
path="/api/users/me/", url_params=False, user=True, request=request
|
||||
)
|
||||
invalidate_cache_directly(
|
||||
path="/api/users/me/settings/",
|
||||
|
|
@ -72,10 +64,7 @@ class UserAssetsV2Endpoint(BaseAPIView):
|
|||
user.cover_image_asset_id = asset_id
|
||||
user.save()
|
||||
invalidate_cache_directly(
|
||||
path="/api/users/me/",
|
||||
url_params=False,
|
||||
user=True,
|
||||
request=request,
|
||||
path="/api/users/me/", url_params=False, user=True, request=request
|
||||
)
|
||||
invalidate_cache_directly(
|
||||
path="/api/users/me/settings/",
|
||||
|
|
@ -93,10 +82,7 @@ class UserAssetsV2Endpoint(BaseAPIView):
|
|||
user.avatar_asset_id = None
|
||||
user.save()
|
||||
invalidate_cache_directly(
|
||||
path="/api/users/me/",
|
||||
url_params=False,
|
||||
user=True,
|
||||
request=request,
|
||||
path="/api/users/me/", url_params=False, user=True, request=request
|
||||
)
|
||||
invalidate_cache_directly(
|
||||
path="/api/users/me/settings/",
|
||||
|
|
@ -111,10 +97,7 @@ class UserAssetsV2Endpoint(BaseAPIView):
|
|||
user.cover_image_asset_id = None
|
||||
user.save()
|
||||
invalidate_cache_directly(
|
||||
path="/api/users/me/",
|
||||
url_params=False,
|
||||
user=True,
|
||||
request=request,
|
||||
path="/api/users/me/", url_params=False, user=True, request=request
|
||||
)
|
||||
invalidate_cache_directly(
|
||||
path="/api/users/me/settings/",
|
||||
|
|
@ -138,20 +121,12 @@ class UserAssetsV2Endpoint(BaseAPIView):
|
|||
# Check if the entity type is allowed
|
||||
if not entity_type or entity_type not in ["USER_AVATAR", "USER_COVER"]:
|
||||
return Response(
|
||||
{
|
||||
"error": "Invalid entity type.",
|
||||
"status": False,
|
||||
},
|
||||
{"error": "Invalid entity type.", "status": False},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
||||
# Check if the file type is allowed
|
||||
allowed_types = [
|
||||
"image/jpeg",
|
||||
"image/png",
|
||||
"image/webp",
|
||||
"image/jpg",
|
||||
]
|
||||
allowed_types = ["image/jpeg", "image/png", "image/webp", "image/jpg"]
|
||||
if type not in allowed_types:
|
||||
return Response(
|
||||
{
|
||||
|
|
@ -166,11 +141,7 @@ class UserAssetsV2Endpoint(BaseAPIView):
|
|||
|
||||
# Create a File Asset
|
||||
asset = FileAsset.objects.create(
|
||||
attributes={
|
||||
"name": name,
|
||||
"type": type,
|
||||
"size": size_limit,
|
||||
},
|
||||
attributes={"name": name, "type": type, "size": size_limit},
|
||||
asset=asset_key,
|
||||
size=size_limit,
|
||||
user=request.user,
|
||||
|
|
@ -182,9 +153,7 @@ class UserAssetsV2Endpoint(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,
|
||||
object_name=asset_key, file_type=type, file_size=size_limit
|
||||
)
|
||||
# Return the presigned URL
|
||||
return Response(
|
||||
|
|
@ -235,45 +204,33 @@ class WorkspaceFileAssetEndpoint(BaseAPIView):
|
|||
def get_entity_id_field(self, entity_type, entity_id):
|
||||
# Workspace Logo
|
||||
if entity_type == FileAsset.EntityTypeContext.WORKSPACE_LOGO:
|
||||
return {
|
||||
"workspace_id": entity_id,
|
||||
}
|
||||
return {"workspace_id": entity_id}
|
||||
|
||||
# Project Cover
|
||||
if entity_type == FileAsset.EntityTypeContext.PROJECT_COVER:
|
||||
return {
|
||||
"project_id": entity_id,
|
||||
}
|
||||
return {"project_id": entity_id}
|
||||
|
||||
# User Avatar and Cover
|
||||
if entity_type in [
|
||||
FileAsset.EntityTypeContext.USER_AVATAR,
|
||||
FileAsset.EntityTypeContext.USER_COVER,
|
||||
]:
|
||||
return {
|
||||
"user_id": entity_id,
|
||||
}
|
||||
return {"user_id": entity_id}
|
||||
|
||||
# Issue Attachment and Description
|
||||
if entity_type in [
|
||||
FileAsset.EntityTypeContext.ISSUE_ATTACHMENT,
|
||||
FileAsset.EntityTypeContext.ISSUE_DESCRIPTION,
|
||||
]:
|
||||
return {
|
||||
"issue_id": entity_id,
|
||||
}
|
||||
return {"issue_id": entity_id}
|
||||
|
||||
# Page Description
|
||||
if entity_type == FileAsset.EntityTypeContext.PAGE_DESCRIPTION:
|
||||
return {
|
||||
"page_id": entity_id,
|
||||
}
|
||||
return {"page_id": entity_id}
|
||||
|
||||
# Comment Description
|
||||
if entity_type == FileAsset.EntityTypeContext.COMMENT_DESCRIPTION:
|
||||
return {
|
||||
"comment_id": entity_id,
|
||||
}
|
||||
return {"comment_id": entity_id}
|
||||
return {}
|
||||
|
||||
def asset_delete(self, asset_id):
|
||||
|
|
@ -301,10 +258,7 @@ class WorkspaceFileAssetEndpoint(BaseAPIView):
|
|||
workspace.logo_asset_id = asset_id
|
||||
workspace.save()
|
||||
invalidate_cache_directly(
|
||||
path="/api/workspaces/",
|
||||
url_params=False,
|
||||
user=False,
|
||||
request=request,
|
||||
path="/api/workspaces/", url_params=False, user=False, request=request
|
||||
)
|
||||
invalidate_cache_directly(
|
||||
path="/api/users/me/workspaces/",
|
||||
|
|
@ -313,10 +267,7 @@ class WorkspaceFileAssetEndpoint(BaseAPIView):
|
|||
request=request,
|
||||
)
|
||||
invalidate_cache_directly(
|
||||
path="/api/instances/",
|
||||
url_params=False,
|
||||
user=False,
|
||||
request=request,
|
||||
path="/api/instances/", url_params=False, user=False, request=request
|
||||
)
|
||||
return
|
||||
|
||||
|
|
@ -345,10 +296,7 @@ class WorkspaceFileAssetEndpoint(BaseAPIView):
|
|||
workspace.logo_asset_id = None
|
||||
workspace.save()
|
||||
invalidate_cache_directly(
|
||||
path="/api/workspaces/",
|
||||
url_params=False,
|
||||
user=False,
|
||||
request=request,
|
||||
path="/api/workspaces/", url_params=False, user=False, request=request
|
||||
)
|
||||
invalidate_cache_directly(
|
||||
path="/api/users/me/workspaces/",
|
||||
|
|
@ -357,10 +305,7 @@ class WorkspaceFileAssetEndpoint(BaseAPIView):
|
|||
request=request,
|
||||
)
|
||||
invalidate_cache_directly(
|
||||
path="/api/instances/",
|
||||
url_params=False,
|
||||
user=False,
|
||||
request=request,
|
||||
path="/api/instances/", url_params=False, user=False, request=request
|
||||
)
|
||||
return
|
||||
# Project Cover
|
||||
|
|
@ -384,10 +329,7 @@ class WorkspaceFileAssetEndpoint(BaseAPIView):
|
|||
# Check if the entity type is allowed
|
||||
if entity_type not in FileAsset.EntityTypeContext.values:
|
||||
return Response(
|
||||
{
|
||||
"error": "Invalid entity type.",
|
||||
"status": False,
|
||||
},
|
||||
{"error": "Invalid entity type.", "status": False},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
||||
|
|
@ -419,11 +361,7 @@ class WorkspaceFileAssetEndpoint(BaseAPIView):
|
|||
|
||||
# Create a File Asset
|
||||
asset = FileAsset.objects.create(
|
||||
attributes={
|
||||
"name": name,
|
||||
"type": type,
|
||||
"size": size_limit,
|
||||
},
|
||||
attributes={"name": name, "type": type, "size": size_limit},
|
||||
asset=asset_key,
|
||||
size=size_limit,
|
||||
workspace=workspace,
|
||||
|
|
@ -438,9 +376,7 @@ class WorkspaceFileAssetEndpoint(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,
|
||||
object_name=asset_key, file_type=type, file_size=size_limit
|
||||
)
|
||||
# Return the presigned URL
|
||||
return Response(
|
||||
|
|
@ -491,18 +427,14 @@ class WorkspaceFileAssetEndpoint(BaseAPIView):
|
|||
# Check if the asset is uploaded
|
||||
if not asset.is_uploaded:
|
||||
return Response(
|
||||
{
|
||||
"error": "The requested asset could not be found.",
|
||||
},
|
||||
{"error": "The requested asset could not be found."},
|
||||
status=status.HTTP_404_NOT_FOUND,
|
||||
)
|
||||
|
||||
# Get the presigned URL
|
||||
storage = S3Storage(request=request)
|
||||
# Generate a presigned URL to share an S3 object
|
||||
signed_url = storage.generate_presigned_url(
|
||||
object_name=asset.asset.name,
|
||||
)
|
||||
signed_url = storage.generate_presigned_url(object_name=asset.asset.name)
|
||||
# Redirect to the signed URL
|
||||
return HttpResponseRedirect(signed_url)
|
||||
|
||||
|
|
@ -510,9 +442,7 @@ class WorkspaceFileAssetEndpoint(BaseAPIView):
|
|||
class StaticFileAssetEndpoint(BaseAPIView):
|
||||
"""This endpoint is used to get the signed URL for a static asset."""
|
||||
|
||||
permission_classes = [
|
||||
AllowAny,
|
||||
]
|
||||
permission_classes = [AllowAny]
|
||||
|
||||
def get(self, request, asset_id):
|
||||
# get the asset id
|
||||
|
|
@ -521,9 +451,7 @@ class StaticFileAssetEndpoint(BaseAPIView):
|
|||
# Check if the asset is uploaded
|
||||
if not asset.is_uploaded:
|
||||
return Response(
|
||||
{
|
||||
"error": "The requested asset could not be found.",
|
||||
},
|
||||
{"error": "The requested asset could not be found."},
|
||||
status=status.HTTP_404_NOT_FOUND,
|
||||
)
|
||||
|
||||
|
|
@ -535,19 +463,14 @@ class StaticFileAssetEndpoint(BaseAPIView):
|
|||
FileAsset.EntityTypeContext.PROJECT_COVER,
|
||||
]:
|
||||
return Response(
|
||||
{
|
||||
"error": "Invalid entity type.",
|
||||
"status": False,
|
||||
},
|
||||
{"error": "Invalid entity type.", "status": False},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
||||
# Get the presigned URL
|
||||
storage = S3Storage(request=request)
|
||||
# Generate a presigned URL to share an S3 object
|
||||
signed_url = storage.generate_presigned_url(
|
||||
object_name=asset.asset.name,
|
||||
)
|
||||
signed_url = storage.generate_presigned_url(object_name=asset.asset.name)
|
||||
# Redirect to the signed URL
|
||||
return HttpResponseRedirect(signed_url)
|
||||
|
||||
|
|
@ -569,50 +492,34 @@ class ProjectAssetEndpoint(BaseAPIView):
|
|||
|
||||
def get_entity_id_field(self, entity_type, entity_id):
|
||||
if entity_type == FileAsset.EntityTypeContext.WORKSPACE_LOGO:
|
||||
return {
|
||||
"workspace_id": entity_id,
|
||||
}
|
||||
return {"workspace_id": entity_id}
|
||||
|
||||
if entity_type == FileAsset.EntityTypeContext.PROJECT_COVER:
|
||||
return {
|
||||
"project_id": entity_id,
|
||||
}
|
||||
return {"project_id": entity_id}
|
||||
|
||||
if entity_type in [
|
||||
FileAsset.EntityTypeContext.USER_AVATAR,
|
||||
FileAsset.EntityTypeContext.USER_COVER,
|
||||
]:
|
||||
return {
|
||||
"user_id": entity_id,
|
||||
}
|
||||
return {"user_id": entity_id}
|
||||
|
||||
if entity_type in [
|
||||
FileAsset.EntityTypeContext.ISSUE_ATTACHMENT,
|
||||
FileAsset.EntityTypeContext.ISSUE_DESCRIPTION,
|
||||
]:
|
||||
return {
|
||||
"issue_id": entity_id,
|
||||
}
|
||||
return {"issue_id": entity_id}
|
||||
|
||||
if entity_type == FileAsset.EntityTypeContext.PAGE_DESCRIPTION:
|
||||
return {
|
||||
"page_id": entity_id,
|
||||
}
|
||||
return {"page_id": entity_id}
|
||||
|
||||
if entity_type == FileAsset.EntityTypeContext.COMMENT_DESCRIPTION:
|
||||
return {
|
||||
"comment_id": entity_id,
|
||||
}
|
||||
return {"comment_id": entity_id}
|
||||
|
||||
if entity_type == FileAsset.EntityTypeContext.DRAFT_ISSUE_DESCRIPTION:
|
||||
return {
|
||||
"draft_issue_id": entity_id,
|
||||
}
|
||||
return {"draft_issue_id": entity_id}
|
||||
return {}
|
||||
|
||||
@allow_permission(
|
||||
[ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST],
|
||||
)
|
||||
@allow_permission([ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST])
|
||||
def post(self, request, slug, project_id):
|
||||
name = request.data.get("name")
|
||||
type = request.data.get("type", "image/jpeg")
|
||||
|
|
@ -623,10 +530,7 @@ class ProjectAssetEndpoint(BaseAPIView):
|
|||
# Check if the entity type is allowed
|
||||
if entity_type not in FileAsset.EntityTypeContext.values:
|
||||
return Response(
|
||||
{
|
||||
"error": "Invalid entity type.",
|
||||
"status": False,
|
||||
},
|
||||
{"error": "Invalid entity type.", "status": False},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
||||
|
|
@ -658,11 +562,7 @@ class ProjectAssetEndpoint(BaseAPIView):
|
|||
|
||||
# Create a File Asset
|
||||
asset = FileAsset.objects.create(
|
||||
attributes={
|
||||
"name": name,
|
||||
"type": type,
|
||||
"size": size_limit,
|
||||
},
|
||||
attributes={"name": name, "type": type, "size": size_limit},
|
||||
asset=asset_key,
|
||||
size=size_limit,
|
||||
workspace=workspace,
|
||||
|
|
@ -676,9 +576,7 @@ class ProjectAssetEndpoint(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,
|
||||
object_name=asset_key, file_type=type, file_size=size_limit
|
||||
)
|
||||
# Return the presigned URL
|
||||
return Response(
|
||||
|
|
@ -690,14 +588,10 @@ class ProjectAssetEndpoint(BaseAPIView):
|
|||
status=status.HTTP_200_OK,
|
||||
)
|
||||
|
||||
@allow_permission(
|
||||
[ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST],
|
||||
)
|
||||
@allow_permission([ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST])
|
||||
def patch(self, request, slug, project_id, pk):
|
||||
# get the asset id
|
||||
asset = FileAsset.objects.get(
|
||||
id=pk,
|
||||
)
|
||||
asset = FileAsset.objects.get(id=pk)
|
||||
# get the storage metadata
|
||||
asset.is_uploaded = True
|
||||
# get the storage metadata
|
||||
|
|
@ -714,9 +608,7 @@ class ProjectAssetEndpoint(BaseAPIView):
|
|||
def delete(self, request, slug, project_id, pk):
|
||||
# Get the asset
|
||||
asset = FileAsset.objects.get(
|
||||
id=pk,
|
||||
workspace__slug=slug,
|
||||
project_id=project_id,
|
||||
id=pk, workspace__slug=slug, project_id=project_id
|
||||
)
|
||||
# Check deleted assets
|
||||
asset.is_deleted = True
|
||||
|
|
@ -729,32 +621,25 @@ class ProjectAssetEndpoint(BaseAPIView):
|
|||
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,
|
||||
workspace__slug=slug, project_id=project_id, pk=pk
|
||||
)
|
||||
|
||||
# Check if the asset is uploaded
|
||||
if not asset.is_uploaded:
|
||||
return Response(
|
||||
{
|
||||
"error": "The requested asset could not be found.",
|
||||
},
|
||||
{"error": "The requested asset could not be found."},
|
||||
status=status.HTTP_404_NOT_FOUND,
|
||||
)
|
||||
|
||||
# Get the presigned URL
|
||||
storage = S3Storage(request=request)
|
||||
# Generate a presigned URL to share an S3 object
|
||||
signed_url = storage.generate_presigned_url(
|
||||
object_name=asset.asset.name,
|
||||
)
|
||||
signed_url = storage.generate_presigned_url(object_name=asset.asset.name)
|
||||
# Redirect to the signed URL
|
||||
return HttpResponseRedirect(signed_url)
|
||||
|
||||
|
||||
class ProjectBulkAssetEndpoint(BaseAPIView):
|
||||
|
||||
def save_project_cover(self, asset, project_id):
|
||||
project = Project.objects.get(id=project_id)
|
||||
project.cover_image_asset_id = asset.id
|
||||
|
|
@ -767,60 +652,36 @@ 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,
|
||||
{"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,
|
||||
)
|
||||
assets = FileAsset.objects.filter(id__in=asset_ids, workspace__slug=slug)
|
||||
|
||||
# Get the first asset
|
||||
asset = assets.first()
|
||||
|
||||
if not asset:
|
||||
return Response(
|
||||
{
|
||||
"error": "The requested asset could not be found.",
|
||||
},
|
||||
{"error": "The requested asset could not be found."},
|
||||
status=status.HTTP_404_NOT_FOUND,
|
||||
)
|
||||
|
||||
# Check if the asset is uploaded
|
||||
if asset.entity_type == FileAsset.EntityTypeContext.PROJECT_COVER:
|
||||
assets.update(
|
||||
project_id=project_id,
|
||||
)
|
||||
assets.update(project_id=project_id)
|
||||
[self.save_project_cover(asset, project_id) for asset in assets]
|
||||
|
||||
if asset.entity_type == FileAsset.EntityTypeContext.ISSUE_DESCRIPTION:
|
||||
assets.update(
|
||||
issue_id=entity_id,
|
||||
)
|
||||
assets.update(issue_id=entity_id)
|
||||
|
||||
if (
|
||||
asset.entity_type
|
||||
== FileAsset.EntityTypeContext.COMMENT_DESCRIPTION
|
||||
):
|
||||
assets.update(
|
||||
comment_id=entity_id,
|
||||
)
|
||||
if asset.entity_type == FileAsset.EntityTypeContext.COMMENT_DESCRIPTION:
|
||||
assets.update(comment_id=entity_id)
|
||||
|
||||
if asset.entity_type == FileAsset.EntityTypeContext.PAGE_DESCRIPTION:
|
||||
assets.update(
|
||||
page_id=entity_id,
|
||||
)
|
||||
assets.update(page_id=entity_id)
|
||||
|
||||
if (
|
||||
asset.entity_type
|
||||
== FileAsset.EntityTypeContext.DRAFT_ISSUE_DESCRIPTION
|
||||
):
|
||||
assets.update(
|
||||
draft_issue_id=entity_id,
|
||||
)
|
||||
if asset.entity_type == FileAsset.EntityTypeContext.DRAFT_ISSUE_DESCRIPTION:
|
||||
assets.update(draft_issue_id=entity_id)
|
||||
|
||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||
|
|
|
|||
|
|
@ -43,18 +43,11 @@ class TimezoneMixin:
|
|||
class BaseViewSet(TimezoneMixin, ModelViewSet, BasePaginator):
|
||||
model = None
|
||||
|
||||
permission_classes = [
|
||||
IsAuthenticated,
|
||||
]
|
||||
permission_classes = [IsAuthenticated]
|
||||
|
||||
filter_backends = (
|
||||
DjangoFilterBackend,
|
||||
SearchFilter,
|
||||
)
|
||||
filter_backends = (DjangoFilterBackend, SearchFilter)
|
||||
|
||||
authentication_classes = [
|
||||
BaseSessionAuthentication,
|
||||
]
|
||||
authentication_classes = [BaseSessionAuthentication]
|
||||
|
||||
filterset_fields = []
|
||||
|
||||
|
|
@ -65,9 +58,7 @@ class BaseViewSet(TimezoneMixin, ModelViewSet, BasePaginator):
|
|||
return self.model.objects.all()
|
||||
except Exception as e:
|
||||
log_exception(e)
|
||||
raise APIException(
|
||||
"Please check the view", status.HTTP_400_BAD_REQUEST
|
||||
)
|
||||
raise APIException("Please check the view", status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
def handle_exception(self, exc):
|
||||
"""
|
||||
|
|
@ -146,35 +137,24 @@ class BaseViewSet(TimezoneMixin, ModelViewSet, BasePaginator):
|
|||
@property
|
||||
def fields(self):
|
||||
fields = [
|
||||
field
|
||||
for field in self.request.GET.get("fields", "").split(",")
|
||||
if field
|
||||
field for field in self.request.GET.get("fields", "").split(",") if field
|
||||
]
|
||||
return fields if fields else None
|
||||
|
||||
@property
|
||||
def expand(self):
|
||||
expand = [
|
||||
expand
|
||||
for expand in self.request.GET.get("expand", "").split(",")
|
||||
if expand
|
||||
expand for expand in self.request.GET.get("expand", "").split(",") if expand
|
||||
]
|
||||
return expand if expand else None
|
||||
|
||||
|
||||
class BaseAPIView(TimezoneMixin, APIView, BasePaginator):
|
||||
permission_classes = [
|
||||
IsAuthenticated,
|
||||
]
|
||||
permission_classes = [IsAuthenticated]
|
||||
|
||||
filter_backends = (
|
||||
DjangoFilterBackend,
|
||||
SearchFilter,
|
||||
)
|
||||
filter_backends = (DjangoFilterBackend, SearchFilter)
|
||||
|
||||
authentication_classes = [
|
||||
BaseSessionAuthentication,
|
||||
]
|
||||
authentication_classes = [BaseSessionAuthentication]
|
||||
|
||||
filterset_fields = []
|
||||
|
||||
|
|
@ -251,17 +231,13 @@ class BaseAPIView(TimezoneMixin, APIView, BasePaginator):
|
|||
@property
|
||||
def fields(self):
|
||||
fields = [
|
||||
field
|
||||
for field in self.request.GET.get("fields", "").split(",")
|
||||
if field
|
||||
field for field in self.request.GET.get("fields", "").split(",") if field
|
||||
]
|
||||
return fields if fields else None
|
||||
|
||||
@property
|
||||
def expand(self):
|
||||
expand = [
|
||||
expand
|
||||
for expand in self.request.GET.get("expand", "").split(",")
|
||||
if expand
|
||||
expand for expand in self.request.GET.get("expand", "").split(",") if expand
|
||||
]
|
||||
return expand if expand else None
|
||||
|
|
|
|||
|
|
@ -34,7 +34,6 @@ from .. import BaseAPIView
|
|||
|
||||
|
||||
class CycleArchiveUnarchiveEndpoint(BaseAPIView):
|
||||
|
||||
def get_queryset(self):
|
||||
favorite_subquery = UserFavorite.objects.filter(
|
||||
user=self.request.user,
|
||||
|
|
@ -52,9 +51,7 @@ class CycleArchiveUnarchiveEndpoint(BaseAPIView):
|
|||
)
|
||||
.values("issue_cycle__cycle_id")
|
||||
.annotate(
|
||||
backlog_estimate_point=Sum(
|
||||
Cast("estimate_point__value", FloatField())
|
||||
)
|
||||
backlog_estimate_point=Sum(Cast("estimate_point__value", FloatField()))
|
||||
)
|
||||
.values("backlog_estimate_point")[:1]
|
||||
)
|
||||
|
|
@ -82,9 +79,7 @@ class CycleArchiveUnarchiveEndpoint(BaseAPIView):
|
|||
)
|
||||
.values("issue_cycle__cycle_id")
|
||||
.annotate(
|
||||
started_estimate_point=Sum(
|
||||
Cast("estimate_point__value", FloatField())
|
||||
)
|
||||
started_estimate_point=Sum(Cast("estimate_point__value", FloatField()))
|
||||
)
|
||||
.values("started_estimate_point")[:1]
|
||||
)
|
||||
|
|
@ -126,9 +121,7 @@ class CycleArchiveUnarchiveEndpoint(BaseAPIView):
|
|||
)
|
||||
.values("issue_cycle__cycle_id")
|
||||
.annotate(
|
||||
total_estimate_points=Sum(
|
||||
Cast("estimate_point__value", FloatField())
|
||||
)
|
||||
total_estimate_points=Sum(Cast("estimate_point__value", FloatField()))
|
||||
)
|
||||
.values("total_estimate_points")[:1]
|
||||
)
|
||||
|
|
@ -153,9 +146,7 @@ class CycleArchiveUnarchiveEndpoint(BaseAPIView):
|
|||
.prefetch_related(
|
||||
Prefetch(
|
||||
"issue_cycle__issue__labels",
|
||||
queryset=Label.objects.only(
|
||||
"name", "color", "id"
|
||||
).distinct(),
|
||||
queryset=Label.objects.only("name", "color", "id").distinct(),
|
||||
)
|
||||
)
|
||||
.annotate(is_favorite=Exists(favorite_subquery))
|
||||
|
|
@ -237,9 +228,7 @@ class CycleArchiveUnarchiveEndpoint(BaseAPIView):
|
|||
& Q(end_date__gte=timezone.now()),
|
||||
then=Value("CURRENT"),
|
||||
),
|
||||
When(
|
||||
start_date__gt=timezone.now(), then=Value("UPCOMING")
|
||||
),
|
||||
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),
|
||||
|
|
@ -254,9 +243,7 @@ class CycleArchiveUnarchiveEndpoint(BaseAPIView):
|
|||
ArrayAgg(
|
||||
"issue_cycle__issue__assignees__id",
|
||||
distinct=True,
|
||||
filter=~Q(
|
||||
issue_cycle__issue__assignees__id__isnull=True
|
||||
),
|
||||
filter=~Q(issue_cycle__issue__assignees__id__isnull=True),
|
||||
),
|
||||
Value([], output_field=ArrayField(UUIDField())),
|
||||
)
|
||||
|
|
@ -265,48 +252,42 @@ class CycleArchiveUnarchiveEndpoint(BaseAPIView):
|
|||
backlog_estimate_points=Coalesce(
|
||||
Subquery(backlog_estimate_point),
|
||||
Value(0, output_field=FloatField()),
|
||||
),
|
||||
)
|
||||
)
|
||||
.annotate(
|
||||
unstarted_estimate_points=Coalesce(
|
||||
Subquery(unstarted_estimate_point),
|
||||
Value(0, output_field=FloatField()),
|
||||
),
|
||||
)
|
||||
)
|
||||
.annotate(
|
||||
started_estimate_points=Coalesce(
|
||||
Subquery(started_estimate_point),
|
||||
Value(0, output_field=FloatField()),
|
||||
),
|
||||
)
|
||||
)
|
||||
.annotate(
|
||||
cancelled_estimate_points=Coalesce(
|
||||
Subquery(cancelled_estimate_point),
|
||||
Value(0, output_field=FloatField()),
|
||||
),
|
||||
)
|
||||
)
|
||||
.annotate(
|
||||
completed_estimate_points=Coalesce(
|
||||
Subquery(completed_estimate_point),
|
||||
Value(0, output_field=FloatField()),
|
||||
),
|
||||
)
|
||||
)
|
||||
.annotate(
|
||||
total_estimate_points=Coalesce(
|
||||
Subquery(total_estimate_point),
|
||||
Value(0, output_field=FloatField()),
|
||||
),
|
||||
Subquery(total_estimate_point), Value(0, output_field=FloatField())
|
||||
)
|
||||
)
|
||||
.order_by("-is_favorite", "name")
|
||||
.distinct()
|
||||
)
|
||||
|
||||
@allow_permission(
|
||||
[
|
||||
ROLE.ADMIN,
|
||||
ROLE.MEMBER,
|
||||
]
|
||||
)
|
||||
@allow_permission([ROLE.ADMIN, ROLE.MEMBER])
|
||||
def get(self, request, slug, project_id, pk=None):
|
||||
if pk is None:
|
||||
queryset = (
|
||||
|
|
@ -342,9 +323,7 @@ class CycleArchiveUnarchiveEndpoint(BaseAPIView):
|
|||
return Response(queryset, status=status.HTTP_200_OK)
|
||||
else:
|
||||
queryset = (
|
||||
self.get_queryset()
|
||||
.filter(archived_at__isnull=False)
|
||||
.filter(pk=pk)
|
||||
self.get_queryset().filter(archived_at__isnull=False).filter(pk=pk)
|
||||
)
|
||||
data = (
|
||||
self.get_queryset()
|
||||
|
|
@ -437,9 +416,7 @@ class CycleArchiveUnarchiveEndpoint(BaseAPIView):
|
|||
)
|
||||
.values("display_name", "assignee_id", "avatar_url")
|
||||
.annotate(
|
||||
total_estimates=Sum(
|
||||
Cast("estimate_point__value", FloatField())
|
||||
)
|
||||
total_estimates=Sum(Cast("estimate_point__value", FloatField()))
|
||||
)
|
||||
.annotate(
|
||||
completed_estimates=Sum(
|
||||
|
|
@ -476,9 +453,7 @@ class CycleArchiveUnarchiveEndpoint(BaseAPIView):
|
|||
.annotate(label_id=F("labels__id"))
|
||||
.values("label_name", "color", "label_id")
|
||||
.annotate(
|
||||
total_estimates=Sum(
|
||||
Cast("estimate_point__value", FloatField())
|
||||
)
|
||||
total_estimates=Sum(Cast("estimate_point__value", FloatField()))
|
||||
)
|
||||
.annotate(
|
||||
completed_estimates=Sum(
|
||||
|
|
@ -509,14 +484,12 @@ class CycleArchiveUnarchiveEndpoint(BaseAPIView):
|
|||
}
|
||||
|
||||
if data["start_date"] and data["end_date"]:
|
||||
data["estimate_distribution"]["completion_chart"] = (
|
||||
burndown_plot(
|
||||
queryset=queryset,
|
||||
slug=slug,
|
||||
project_id=project_id,
|
||||
plot_type="points",
|
||||
cycle_id=pk,
|
||||
)
|
||||
data["estimate_distribution"]["completion_chart"] = burndown_plot(
|
||||
queryset=queryset,
|
||||
slug=slug,
|
||||
project_id=project_id,
|
||||
plot_type="points",
|
||||
cycle_id=pk,
|
||||
)
|
||||
|
||||
# Assignee Distribution
|
||||
|
|
@ -560,12 +533,8 @@ class CycleArchiveUnarchiveEndpoint(BaseAPIView):
|
|||
)
|
||||
.annotate(
|
||||
total_issues=Count(
|
||||
"id",
|
||||
filter=Q(
|
||||
archived_at__isnull=True,
|
||||
is_draft=False,
|
||||
),
|
||||
),
|
||||
"id", filter=Q(archived_at__isnull=True, is_draft=False)
|
||||
)
|
||||
)
|
||||
.annotate(
|
||||
completed_issues=Count(
|
||||
|
|
@ -604,12 +573,8 @@ class CycleArchiveUnarchiveEndpoint(BaseAPIView):
|
|||
.values("label_name", "color", "label_id")
|
||||
.annotate(
|
||||
total_issues=Count(
|
||||
"id",
|
||||
filter=Q(
|
||||
archived_at__isnull=True,
|
||||
is_draft=False,
|
||||
),
|
||||
),
|
||||
"id", filter=Q(archived_at__isnull=True, is_draft=False)
|
||||
)
|
||||
)
|
||||
.annotate(
|
||||
completed_issues=Count(
|
||||
|
|
@ -649,10 +614,7 @@ class CycleArchiveUnarchiveEndpoint(BaseAPIView):
|
|||
cycle_id=pk,
|
||||
)
|
||||
|
||||
return Response(
|
||||
data,
|
||||
status=status.HTTP_200_OK,
|
||||
)
|
||||
return Response(data, status=status.HTTP_200_OK)
|
||||
|
||||
@allow_permission([ROLE.ADMIN, ROLE.MEMBER])
|
||||
def post(self, request, slug, project_id, cycle_id):
|
||||
|
|
@ -675,8 +637,7 @@ class CycleArchiveUnarchiveEndpoint(BaseAPIView):
|
|||
workspace__slug=slug,
|
||||
).delete()
|
||||
return Response(
|
||||
{"archived_at": str(cycle.archived_at)},
|
||||
status=status.HTTP_200_OK,
|
||||
{"archived_at": str(cycle.archived_at)}, status=status.HTTP_200_OK
|
||||
)
|
||||
|
||||
@allow_permission([ROLE.ADMIN, ROLE.MEMBER])
|
||||
|
|
|
|||
|
|
@ -89,9 +89,7 @@ class CycleViewSet(BaseViewSet):
|
|||
.prefetch_related(
|
||||
Prefetch(
|
||||
"issue_cycle__issue__labels",
|
||||
queryset=Label.objects.only(
|
||||
"name", "color", "id"
|
||||
).distinct(),
|
||||
queryset=Label.objects.only("name", "color", "id").distinct(),
|
||||
)
|
||||
)
|
||||
.annotate(is_favorite=Exists(favorite_subquery))
|
||||
|
|
@ -125,9 +123,7 @@ class CycleViewSet(BaseViewSet):
|
|||
& Q(end_date__gte=timezone.now()),
|
||||
then=Value("CURRENT"),
|
||||
),
|
||||
When(
|
||||
start_date__gt=timezone.now(), then=Value("UPCOMING")
|
||||
),
|
||||
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),
|
||||
|
|
@ -142,9 +138,7 @@ class CycleViewSet(BaseViewSet):
|
|||
ArrayAgg(
|
||||
"issue_cycle__issue__assignees__id",
|
||||
distinct=True,
|
||||
filter=~Q(
|
||||
issue_cycle__issue__assignees__id__isnull=True
|
||||
)
|
||||
filter=~Q(issue_cycle__issue__assignees__id__isnull=True)
|
||||
& (
|
||||
Q(
|
||||
issue_cycle__issue__issue_assignee__deleted_at__isnull=True
|
||||
|
|
@ -169,8 +163,7 @@ class CycleViewSet(BaseViewSet):
|
|||
# Current Cycle
|
||||
if cycle_view == "current":
|
||||
queryset = queryset.filter(
|
||||
start_date__lte=timezone.now(),
|
||||
end_date__gte=timezone.now(),
|
||||
start_date__lte=timezone.now(), end_date__gte=timezone.now()
|
||||
)
|
||||
|
||||
data = queryset.values(
|
||||
|
|
@ -241,10 +234,7 @@ class CycleViewSet(BaseViewSet):
|
|||
):
|
||||
serializer = CycleWriteSerializer(data=request.data)
|
||||
if serializer.is_valid():
|
||||
serializer.save(
|
||||
project_id=project_id,
|
||||
owned_by=request.user,
|
||||
)
|
||||
serializer.save(project_id=project_id, owned_by=request.user)
|
||||
cycle = (
|
||||
self.get_queryset()
|
||||
.filter(pk=serializer.data["id"])
|
||||
|
|
@ -288,9 +278,7 @@ class CycleViewSet(BaseViewSet):
|
|||
origin=request.META.get("HTTP_ORIGIN"),
|
||||
)
|
||||
return Response(cycle, status=status.HTTP_201_CREATED)
|
||||
return Response(
|
||||
serializer.errors, status=status.HTTP_400_BAD_REQUEST
|
||||
)
|
||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
else:
|
||||
return Response(
|
||||
{
|
||||
|
|
@ -321,9 +309,7 @@ class CycleViewSet(BaseViewSet):
|
|||
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
|
||||
)
|
||||
"sort_order": request_data.get("sort_order", cycle.sort_order)
|
||||
}
|
||||
else:
|
||||
return Response(
|
||||
|
|
@ -333,9 +319,7 @@ class CycleViewSet(BaseViewSet):
|
|||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
||||
serializer = CycleWriteSerializer(
|
||||
cycle, data=request.data, partial=True
|
||||
)
|
||||
serializer = CycleWriteSerializer(cycle, data=request.data, partial=True)
|
||||
if serializer.is_valid():
|
||||
serializer.save()
|
||||
cycle = queryset.values(
|
||||
|
|
@ -379,16 +363,9 @@ class CycleViewSet(BaseViewSet):
|
|||
return Response(cycle, status=status.HTTP_200_OK)
|
||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
@allow_permission(
|
||||
[
|
||||
ROLE.ADMIN,
|
||||
ROLE.MEMBER,
|
||||
]
|
||||
)
|
||||
@allow_permission([ROLE.ADMIN, ROLE.MEMBER])
|
||||
def retrieve(self, request, slug, project_id, pk):
|
||||
queryset = (
|
||||
self.get_queryset().filter(archived_at__isnull=True).filter(pk=pk)
|
||||
)
|
||||
queryset = self.get_queryset().filter(archived_at__isnull=True).filter(pk=pk)
|
||||
data = (
|
||||
self.get_queryset()
|
||||
.filter(pk=pk)
|
||||
|
|
@ -436,8 +413,7 @@ class CycleViewSet(BaseViewSet):
|
|||
|
||||
if data is None:
|
||||
return Response(
|
||||
{"error": "Cycle not found"},
|
||||
status=status.HTTP_404_NOT_FOUND,
|
||||
{"error": "Cycle not found"}, status=status.HTTP_404_NOT_FOUND
|
||||
)
|
||||
|
||||
queryset = queryset.first()
|
||||
|
|
@ -449,16 +425,11 @@ class CycleViewSet(BaseViewSet):
|
|||
user_id=request.user.id,
|
||||
project_id=project_id,
|
||||
)
|
||||
return Response(
|
||||
data,
|
||||
status=status.HTTP_200_OK,
|
||||
)
|
||||
return Response(data, status=status.HTTP_200_OK)
|
||||
|
||||
@allow_permission([ROLE.ADMIN], creator=True, model=Cycle)
|
||||
def destroy(self, request, slug, project_id, pk):
|
||||
cycle = Cycle.objects.get(
|
||||
workspace__slug=slug, project_id=project_id, pk=pk
|
||||
)
|
||||
cycle = Cycle.objects.get(workspace__slug=slug, project_id=project_id, pk=pk)
|
||||
if cycle.owned_by_id != request.user.id and not (
|
||||
ProjectMember.objects.filter(
|
||||
workspace__slug=slug,
|
||||
|
|
@ -474,9 +445,9 @@ class CycleViewSet(BaseViewSet):
|
|||
)
|
||||
|
||||
cycle_issues = list(
|
||||
CycleIssue.objects.filter(
|
||||
cycle_id=self.kwargs.get("pk")
|
||||
).values_list("issue", flat=True)
|
||||
CycleIssue.objects.filter(cycle_id=self.kwargs.get("pk")).values_list(
|
||||
"issue", flat=True
|
||||
)
|
||||
)
|
||||
|
||||
issue_activity.delay(
|
||||
|
|
@ -707,9 +678,7 @@ class TransferCycleIssueEndpoint(BaseAPIView):
|
|||
)
|
||||
.values("display_name", "assignee_id", "avatar_url")
|
||||
.annotate(
|
||||
total_estimates=Sum(
|
||||
Cast("estimate_point__value", FloatField())
|
||||
)
|
||||
total_estimates=Sum(Cast("estimate_point__value", FloatField()))
|
||||
)
|
||||
.annotate(
|
||||
completed_estimates=Sum(
|
||||
|
|
@ -738,9 +707,7 @@ class TransferCycleIssueEndpoint(BaseAPIView):
|
|||
{
|
||||
"display_name": item["display_name"],
|
||||
"assignee_id": (
|
||||
str(item["assignee_id"])
|
||||
if item["assignee_id"]
|
||||
else None
|
||||
str(item["assignee_id"]) if item["assignee_id"] else None
|
||||
),
|
||||
"avatar": item.get("avatar"),
|
||||
"avatar_url": item.get("avatar_url"),
|
||||
|
|
@ -763,9 +730,7 @@ class TransferCycleIssueEndpoint(BaseAPIView):
|
|||
.annotate(label_id=F("labels__id"))
|
||||
.values("label_name", "color", "label_id")
|
||||
.annotate(
|
||||
total_estimates=Sum(
|
||||
Cast("estimate_point__value", FloatField())
|
||||
)
|
||||
total_estimates=Sum(Cast("estimate_point__value", FloatField()))
|
||||
)
|
||||
.annotate(
|
||||
completed_estimates=Sum(
|
||||
|
|
@ -802,9 +767,7 @@ class TransferCycleIssueEndpoint(BaseAPIView):
|
|||
{
|
||||
"label_name": item["label_name"],
|
||||
"color": item["color"],
|
||||
"label_id": (
|
||||
str(item["label_id"]) if item["label_id"] else None
|
||||
),
|
||||
"label_id": (str(item["label_id"]) if item["label_id"] else None),
|
||||
"total_estimates": item["total_estimates"],
|
||||
"completed_estimates": item["completed_estimates"],
|
||||
"pending_estimates": item["pending_estimates"],
|
||||
|
|
@ -835,8 +798,7 @@ class TransferCycleIssueEndpoint(BaseAPIView):
|
|||
),
|
||||
# If `avatar_asset` is None, fall back to using `avatar` field directly
|
||||
When(
|
||||
assignees__avatar_asset__isnull=True,
|
||||
then="assignees__avatar",
|
||||
assignees__avatar_asset__isnull=True, then="assignees__avatar"
|
||||
),
|
||||
default=Value(None),
|
||||
output_field=models.CharField(),
|
||||
|
|
@ -845,12 +807,8 @@ class TransferCycleIssueEndpoint(BaseAPIView):
|
|||
.values("display_name", "assignee_id", "avatar_url")
|
||||
.annotate(
|
||||
total_issues=Count(
|
||||
"id",
|
||||
filter=Q(
|
||||
archived_at__isnull=True,
|
||||
is_draft=False,
|
||||
),
|
||||
),
|
||||
"id", filter=Q(archived_at__isnull=True, is_draft=False)
|
||||
)
|
||||
)
|
||||
.annotate(
|
||||
completed_issues=Count(
|
||||
|
|
@ -904,12 +862,8 @@ class TransferCycleIssueEndpoint(BaseAPIView):
|
|||
.values("label_name", "color", "label_id")
|
||||
.annotate(
|
||||
total_issues=Count(
|
||||
"id",
|
||||
filter=Q(
|
||||
archived_at__isnull=True,
|
||||
is_draft=False,
|
||||
),
|
||||
),
|
||||
"id", filter=Q(archived_at__isnull=True, is_draft=False)
|
||||
)
|
||||
)
|
||||
.annotate(
|
||||
completed_issues=Count(
|
||||
|
|
@ -939,9 +893,7 @@ class TransferCycleIssueEndpoint(BaseAPIView):
|
|||
{
|
||||
"label_name": item["label_name"],
|
||||
"color": item["color"],
|
||||
"label_id": (
|
||||
str(item["label_id"]) if item["label_id"] else None
|
||||
),
|
||||
"label_id": (str(item["label_id"]) if item["label_id"] else None),
|
||||
"total_issues": item["total_issues"],
|
||||
"completed_issues": item["completed_issues"],
|
||||
"pending_issues": item["pending_issues"],
|
||||
|
|
@ -986,10 +938,7 @@ class TransferCycleIssueEndpoint(BaseAPIView):
|
|||
}
|
||||
current_cycle.save(update_fields=["progress_snapshot"])
|
||||
|
||||
if (
|
||||
new_cycle.end_date is not None
|
||||
and new_cycle.end_date < timezone.now()
|
||||
):
|
||||
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"
|
||||
|
|
@ -1052,9 +1001,7 @@ class CycleUserPropertiesEndpoint(BaseAPIView):
|
|||
workspace__slug=slug,
|
||||
)
|
||||
|
||||
cycle_properties.filters = request.data.get(
|
||||
"filters", cycle_properties.filters
|
||||
)
|
||||
cycle_properties.filters = request.data.get("filters", cycle_properties.filters)
|
||||
cycle_properties.display_filters = request.data.get(
|
||||
"display_filters", cycle_properties.display_filters
|
||||
)
|
||||
|
|
@ -1089,9 +1036,7 @@ class CycleProgressEndpoint(BaseAPIView):
|
|||
workspace__slug=slug,
|
||||
project_id=project_id,
|
||||
)
|
||||
.annotate(
|
||||
value_as_float=Cast("estimate_point__value", FloatField())
|
||||
)
|
||||
.annotate(value_as_float=Cast("estimate_point__value", FloatField()))
|
||||
.aggregate(
|
||||
backlog_estimate_point=Sum(
|
||||
Case(
|
||||
|
|
@ -1129,9 +1074,7 @@ class CycleProgressEndpoint(BaseAPIView):
|
|||
)
|
||||
),
|
||||
total_estimate_points=Sum(
|
||||
"value_as_float",
|
||||
default=Value(0),
|
||||
output_field=FloatField(),
|
||||
"value_as_float", default=Value(0), output_field=FloatField()
|
||||
),
|
||||
)
|
||||
)
|
||||
|
|
@ -1185,17 +1128,13 @@ class CycleProgressEndpoint(BaseAPIView):
|
|||
|
||||
return Response(
|
||||
{
|
||||
"backlog_estimate_points": aggregate_estimates[
|
||||
"backlog_estimate_point"
|
||||
]
|
||||
"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"
|
||||
]
|
||||
"started_estimate_points": aggregate_estimates["started_estimate_point"]
|
||||
or 0,
|
||||
"cancelled_estimate_points": aggregate_estimates[
|
||||
"cancelled_estimate_point"
|
||||
|
|
@ -1205,9 +1144,7 @@ class CycleProgressEndpoint(BaseAPIView):
|
|||
"completed_estimate_points"
|
||||
]
|
||||
or 0,
|
||||
"total_estimate_points": aggregate_estimates[
|
||||
"total_estimate_points"
|
||||
],
|
||||
"total_estimate_points": aggregate_estimates["total_estimate_points"],
|
||||
"backlog_issues": backlog_issues,
|
||||
"total_issues": total_issues,
|
||||
"completed_issues": completed_issues,
|
||||
|
|
@ -1220,15 +1157,12 @@ class CycleProgressEndpoint(BaseAPIView):
|
|||
|
||||
|
||||
class CycleAnalyticsEndpoint(BaseAPIView):
|
||||
|
||||
@allow_permission([ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST])
|
||||
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,
|
||||
workspace__slug=slug, project_id=project_id, id=cycle_id
|
||||
)
|
||||
.annotate(
|
||||
total_issues=Count(
|
||||
|
|
@ -1294,9 +1228,7 @@ class CycleAnalyticsEndpoint(BaseAPIView):
|
|||
)
|
||||
.values("display_name", "assignee_id", "avatar_url")
|
||||
.annotate(
|
||||
total_estimates=Sum(
|
||||
Cast("estimate_point__value", FloatField())
|
||||
)
|
||||
total_estimates=Sum(Cast("estimate_point__value", FloatField()))
|
||||
)
|
||||
.annotate(
|
||||
completed_estimates=Sum(
|
||||
|
|
@ -1333,9 +1265,7 @@ class CycleAnalyticsEndpoint(BaseAPIView):
|
|||
.annotate(label_id=F("labels__id"))
|
||||
.values("label_name", "color", "label_id")
|
||||
.annotate(
|
||||
total_estimates=Sum(
|
||||
Cast("estimate_point__value", FloatField())
|
||||
)
|
||||
total_estimates=Sum(Cast("estimate_point__value", FloatField()))
|
||||
)
|
||||
.annotate(
|
||||
completed_estimates=Sum(
|
||||
|
|
@ -1402,7 +1332,7 @@ class CycleAnalyticsEndpoint(BaseAPIView):
|
|||
total_issues=Count(
|
||||
"assignee_id",
|
||||
filter=Q(archived_at__isnull=True, is_draft=False),
|
||||
),
|
||||
)
|
||||
)
|
||||
.annotate(
|
||||
completed_issues=Count(
|
||||
|
|
@ -1440,8 +1370,7 @@ class CycleAnalyticsEndpoint(BaseAPIView):
|
|||
.values("label_name", "color", "label_id")
|
||||
.annotate(
|
||||
total_issues=Count(
|
||||
"label_id",
|
||||
filter=Q(archived_at__isnull=True, is_draft=False),
|
||||
"label_id", filter=Q(archived_at__isnull=True, is_draft=False)
|
||||
)
|
||||
)
|
||||
.annotate(
|
||||
|
|
|
|||
|
|
@ -15,17 +15,9 @@ from rest_framework.response import Response
|
|||
|
||||
# Module imports
|
||||
from .. import BaseViewSet
|
||||
from plane.app.serializers import (
|
||||
CycleIssueSerializer,
|
||||
)
|
||||
from plane.app.serializers import CycleIssueSerializer
|
||||
from plane.bgtasks.issue_activities_task import issue_activity
|
||||
from plane.db.models import (
|
||||
Cycle,
|
||||
CycleIssue,
|
||||
Issue,
|
||||
FileAsset,
|
||||
IssueLink,
|
||||
)
|
||||
from plane.db.models import Cycle, CycleIssue, Issue, FileAsset, IssueLink
|
||||
from plane.utils.grouper import (
|
||||
issue_group_values,
|
||||
issue_on_results,
|
||||
|
|
@ -33,14 +25,10 @@ from plane.utils.grouper import (
|
|||
)
|
||||
from plane.utils.issue_filters import issue_filters
|
||||
from plane.utils.order_queryset import order_issue_queryset
|
||||
from plane.utils.paginator import (
|
||||
GroupedOffsetPaginator,
|
||||
SubGroupedOffsetPaginator,
|
||||
)
|
||||
from plane.utils.paginator import GroupedOffsetPaginator, SubGroupedOffsetPaginator
|
||||
from plane.app.permissions import allow_permission, ROLE
|
||||
|
||||
|
||||
|
||||
class CycleIssueViewSet(BaseViewSet):
|
||||
serializer_class = CycleIssueSerializer
|
||||
model = CycleIssue
|
||||
|
|
@ -48,19 +36,14 @@ class CycleIssueViewSet(BaseViewSet):
|
|||
webhook_event = "cycle_issue"
|
||||
bulk = True
|
||||
|
||||
filterset_fields = [
|
||||
"issue__labels__id",
|
||||
"issue__assignees__id",
|
||||
]
|
||||
filterset_fields = ["issue__labels__id", "issue__assignees__id"]
|
||||
|
||||
def get_queryset(self):
|
||||
return self.filter_queryset(
|
||||
super()
|
||||
.get_queryset()
|
||||
.annotate(
|
||||
sub_issues_count=Issue.issue_objects.filter(
|
||||
parent=OuterRef("issue_id")
|
||||
)
|
||||
sub_issues_count=Issue.issue_objects.filter(parent=OuterRef("issue_id"))
|
||||
.order_by()
|
||||
.annotate(count=Func(F("id"), function="Count"))
|
||||
.values("count")
|
||||
|
|
@ -82,29 +65,20 @@ class CycleIssueViewSet(BaseViewSet):
|
|||
)
|
||||
|
||||
@method_decorator(gzip_page)
|
||||
@allow_permission(
|
||||
[
|
||||
ROLE.ADMIN,
|
||||
ROLE.MEMBER,
|
||||
]
|
||||
)
|
||||
@allow_permission([ROLE.ADMIN, ROLE.MEMBER])
|
||||
def list(self, request, slug, project_id, cycle_id):
|
||||
order_by_param = request.GET.get("order_by", "created_at")
|
||||
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_cycle__cycle_id=cycle_id, issue_cycle__deleted_at__isnull=True
|
||||
)
|
||||
.filter(project_id=project_id)
|
||||
.filter(workspace__slug=slug)
|
||||
.filter(**filters)
|
||||
.select_related("workspace", "project", "state", "parent")
|
||||
.prefetch_related(
|
||||
"assignees",
|
||||
"labels",
|
||||
"issue_module__module",
|
||||
"issue_cycle__cycle",
|
||||
"assignees", "labels", "issue_module__module", "issue_cycle__cycle"
|
||||
)
|
||||
.filter(**filters)
|
||||
.annotate(
|
||||
|
|
@ -130,9 +104,7 @@ class CycleIssueViewSet(BaseViewSet):
|
|||
.values("count")
|
||||
)
|
||||
.annotate(
|
||||
sub_issues_count=Issue.issue_objects.filter(
|
||||
parent=OuterRef("id")
|
||||
)
|
||||
sub_issues_count=Issue.issue_objects.filter(parent=OuterRef("id"))
|
||||
.order_by()
|
||||
.annotate(count=Func(F("id"), function="Count"))
|
||||
.values("count")
|
||||
|
|
@ -144,8 +116,7 @@ class CycleIssueViewSet(BaseViewSet):
|
|||
issue_queryset = issue_queryset.filter(**filters)
|
||||
# Issue queryset
|
||||
issue_queryset, order_by_param = order_issue_queryset(
|
||||
issue_queryset=issue_queryset,
|
||||
order_by_param=order_by_param,
|
||||
issue_queryset=issue_queryset, order_by_param=order_by_param
|
||||
)
|
||||
|
||||
# Group by
|
||||
|
|
@ -154,9 +125,7 @@ class CycleIssueViewSet(BaseViewSet):
|
|||
|
||||
# issue queryset
|
||||
issue_queryset = issue_queryset_grouper(
|
||||
queryset=issue_queryset,
|
||||
group_by=group_by,
|
||||
sub_group_by=sub_group_by,
|
||||
queryset=issue_queryset, group_by=group_by, sub_group_by=sub_group_by
|
||||
)
|
||||
|
||||
if group_by:
|
||||
|
|
@ -176,9 +145,7 @@ class CycleIssueViewSet(BaseViewSet):
|
|||
order_by=order_by_param,
|
||||
queryset=issue_queryset,
|
||||
on_results=lambda issues: issue_on_results(
|
||||
group_by=group_by,
|
||||
issues=issues,
|
||||
sub_group_by=sub_group_by,
|
||||
group_by=group_by, issues=issues, sub_group_by=sub_group_by
|
||||
),
|
||||
paginator_cls=SubGroupedOffsetPaginator,
|
||||
group_by_fields=issue_group_values(
|
||||
|
|
@ -212,9 +179,7 @@ class CycleIssueViewSet(BaseViewSet):
|
|||
order_by=order_by_param,
|
||||
queryset=issue_queryset,
|
||||
on_results=lambda issues: issue_on_results(
|
||||
group_by=group_by,
|
||||
issues=issues,
|
||||
sub_group_by=sub_group_by,
|
||||
group_by=group_by, issues=issues, sub_group_by=sub_group_by
|
||||
),
|
||||
paginator_cls=GroupedOffsetPaginator,
|
||||
group_by_fields=issue_group_values(
|
||||
|
|
@ -250,8 +215,7 @@ class CycleIssueViewSet(BaseViewSet):
|
|||
|
||||
if not issues:
|
||||
return Response(
|
||||
{"error": "Issues are required"},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
{"error": "Issues are required"}, status=status.HTTP_400_BAD_REQUEST
|
||||
)
|
||||
|
||||
cycle = Cycle.objects.get(
|
||||
|
|
@ -268,13 +232,9 @@ class CycleIssueViewSet(BaseViewSet):
|
|||
|
||||
# Get all CycleIssues already created
|
||||
cycle_issues = list(
|
||||
CycleIssue.objects.filter(
|
||||
~Q(cycle_id=cycle_id), issue_id__in=issues
|
||||
)
|
||||
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
|
||||
]
|
||||
existing_issues = [str(cycle_issue.issue_id) for cycle_issue in cycle_issues]
|
||||
new_issues = list(set(issues) - set(existing_issues))
|
||||
|
||||
# New issues to create
|
||||
|
|
@ -313,9 +273,7 @@ class CycleIssueViewSet(BaseViewSet):
|
|||
)
|
||||
|
||||
# Update the cycle issues
|
||||
CycleIssue.objects.bulk_update(
|
||||
updated_records, ["cycle_id"], batch_size=100
|
||||
)
|
||||
CycleIssue.objects.bulk_update(updated_records, ["cycle_id"], batch_size=100)
|
||||
# Capture Issue Activity
|
||||
issue_activity.delay(
|
||||
type="cycle.activity.created",
|
||||
|
|
|
|||
|
|
@ -227,7 +227,7 @@ def dashboard_assigned_issues(self, request, slug):
|
|||
distinct=True,
|
||||
filter=Q(
|
||||
~Q(labels__id__isnull=True)
|
||||
& Q(label_issue__deleted_at__isnull=True),
|
||||
& Q(label_issue__deleted_at__isnull=True)
|
||||
),
|
||||
),
|
||||
Value([], output_field=ArrayField(UUIDField())),
|
||||
|
|
@ -260,10 +260,7 @@ def dashboard_assigned_issues(self, request, slug):
|
|||
)
|
||||
|
||||
if WorkspaceMember.objects.filter(
|
||||
workspace__slug=slug,
|
||||
member=request.user,
|
||||
role=5,
|
||||
is_active=True,
|
||||
workspace__slug=slug, member=request.user, role=5, is_active=True
|
||||
).exists():
|
||||
assigned_issues = assigned_issues.filter(created_by=request.user)
|
||||
|
||||
|
|
@ -271,10 +268,7 @@ def dashboard_assigned_issues(self, request, slug):
|
|||
priority_order = ["urgent", "high", "medium", "low", "none"]
|
||||
assigned_issues = assigned_issues.annotate(
|
||||
priority_order=Case(
|
||||
*[
|
||||
When(priority=p, then=Value(i))
|
||||
for i, p in enumerate(priority_order)
|
||||
],
|
||||
*[When(priority=p, then=Value(i)) for i, p in enumerate(priority_order)],
|
||||
output_field=CharField(),
|
||||
)
|
||||
).order_by("priority_order")
|
||||
|
|
@ -300,9 +294,7 @@ def dashboard_assigned_issues(self, request, slug):
|
|||
completed_issues_count = assigned_issues.filter(
|
||||
state__group__in=["completed"]
|
||||
).count()
|
||||
completed_issues = assigned_issues.filter(
|
||||
state__group__in=["completed"]
|
||||
)[:5]
|
||||
completed_issues = assigned_issues.filter(state__group__in=["completed"])[:5]
|
||||
return Response(
|
||||
{
|
||||
"issues": IssueSerializer(
|
||||
|
|
@ -407,7 +399,7 @@ def dashboard_created_issues(self, request, slug):
|
|||
distinct=True,
|
||||
filter=Q(
|
||||
~Q(labels__id__isnull=True)
|
||||
& Q(label_issue__deleted_at__isnull=True),
|
||||
& Q(label_issue__deleted_at__isnull=True)
|
||||
),
|
||||
),
|
||||
Value([], output_field=ArrayField(UUIDField())),
|
||||
|
|
@ -444,10 +436,7 @@ def dashboard_created_issues(self, request, slug):
|
|||
priority_order = ["urgent", "high", "medium", "low", "none"]
|
||||
created_issues = created_issues.annotate(
|
||||
priority_order=Case(
|
||||
*[
|
||||
When(priority=p, then=Value(i))
|
||||
for i, p in enumerate(priority_order)
|
||||
],
|
||||
*[When(priority=p, then=Value(i)) for i, p in enumerate(priority_order)],
|
||||
output_field=CharField(),
|
||||
)
|
||||
).order_by("priority_order")
|
||||
|
|
@ -473,9 +462,7 @@ def dashboard_created_issues(self, request, slug):
|
|||
completed_issues_count = created_issues.filter(
|
||||
state__group__in=["completed"]
|
||||
).count()
|
||||
completed_issues = created_issues.filter(
|
||||
state__group__in=["completed"]
|
||||
)[:5]
|
||||
completed_issues = created_issues.filter(state__group__in=["completed"])[:5]
|
||||
return Response(
|
||||
{
|
||||
"issues": IssueSerializer(completed_issues, many=True).data,
|
||||
|
|
@ -530,10 +517,7 @@ def dashboard_issues_by_state_groups(self, request, slug):
|
|||
extra_filters = {}
|
||||
|
||||
if WorkspaceMember.objects.filter(
|
||||
workspace__slug=slug,
|
||||
member=request.user,
|
||||
role=5,
|
||||
is_active=True,
|
||||
workspace__slug=slug, member=request.user, role=5, is_active=True
|
||||
).exists():
|
||||
extra_filters = {"created_by": request.user}
|
||||
|
||||
|
|
@ -570,10 +554,7 @@ def dashboard_issues_by_priority(self, request, slug):
|
|||
extra_filters = {}
|
||||
|
||||
if WorkspaceMember.objects.filter(
|
||||
workspace__slug=slug,
|
||||
member=request.user,
|
||||
role=5,
|
||||
is_active=True,
|
||||
workspace__slug=slug, member=request.user, role=5, is_active=True
|
||||
).exists():
|
||||
extra_filters = {"created_by": request.user}
|
||||
|
||||
|
|
@ -598,8 +579,7 @@ def dashboard_issues_by_priority(self, request, slug):
|
|||
|
||||
# Prepare output including all groups with their counts
|
||||
output_data = [
|
||||
{"priority": group, "count": count}
|
||||
for group, count in all_groups.items()
|
||||
{"priority": group, "count": count} for group, count in all_groups.items()
|
||||
]
|
||||
|
||||
return Response(output_data, status=status.HTTP_200_OK)
|
||||
|
|
@ -616,8 +596,7 @@ def dashboard_recent_activity(self, request, slug):
|
|||
).select_related("actor", "workspace", "issue", "project")[:8]
|
||||
|
||||
return Response(
|
||||
IssueActivitySerializer(queryset, many=True).data,
|
||||
status=status.HTTP_200_OK,
|
||||
IssueActivitySerializer(queryset, many=True).data, status=status.HTTP_200_OK
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -647,22 +626,14 @@ def dashboard_recent_projects(self, request, slug):
|
|||
).exclude(id__in=unique_project_ids)
|
||||
|
||||
# Append additional project IDs to the existing list
|
||||
unique_project_ids.update(
|
||||
additional_projects.values_list("id", flat=True)
|
||||
)
|
||||
unique_project_ids.update(additional_projects.values_list("id", flat=True))
|
||||
|
||||
return Response(
|
||||
list(unique_project_ids)[:4],
|
||||
status=status.HTTP_200_OK,
|
||||
)
|
||||
return Response(list(unique_project_ids)[:4], status=status.HTTP_200_OK)
|
||||
|
||||
|
||||
def dashboard_recent_collaborators(self, request, slug):
|
||||
project_members_with_activities = (
|
||||
WorkspaceMember.objects.filter(
|
||||
workspace__slug=slug,
|
||||
is_active=True,
|
||||
)
|
||||
WorkspaceMember.objects.filter(workspace__slug=slug, is_active=True)
|
||||
.annotate(
|
||||
active_issue_count=Count(
|
||||
Case(
|
||||
|
|
@ -687,10 +658,7 @@ def dashboard_recent_collaborators(self, request, slug):
|
|||
.order_by("-active_issue_count")
|
||||
.distinct()
|
||||
)
|
||||
return Response(
|
||||
(project_members_with_activities),
|
||||
status=status.HTTP_200_OK,
|
||||
)
|
||||
return Response((project_members_with_activities), status=status.HTTP_200_OK)
|
||||
|
||||
|
||||
class DashboardEndpoint(BaseAPIView):
|
||||
|
|
@ -739,14 +707,13 @@ class DashboardEndpoint(BaseAPIView):
|
|||
|
||||
updated_dashboard_widgets = []
|
||||
for widget_key in widgets_to_fetch:
|
||||
widget = Widget.objects.filter(
|
||||
key=widget_key
|
||||
).values_list("id", flat=True)
|
||||
widget = Widget.objects.filter(key=widget_key).values_list(
|
||||
"id", flat=True
|
||||
)
|
||||
if widget:
|
||||
updated_dashboard_widgets.append(
|
||||
DashboardWidget(
|
||||
widget_id=widget,
|
||||
dashboard_id=dashboard.id,
|
||||
widget_id=widget, dashboard_id=dashboard.id
|
||||
)
|
||||
)
|
||||
|
||||
|
|
@ -813,11 +780,7 @@ class DashboardEndpoint(BaseAPIView):
|
|||
|
||||
func = WIDGETS_MAPPER.get(widget_key)
|
||||
if func is not None:
|
||||
response = func(
|
||||
self,
|
||||
request=request,
|
||||
slug=slug,
|
||||
)
|
||||
response = func(self, request=request, slug=slug)
|
||||
if isinstance(response, Response):
|
||||
return response
|
||||
|
||||
|
|
@ -830,8 +793,7 @@ class DashboardEndpoint(BaseAPIView):
|
|||
class WidgetsEndpoint(BaseAPIView):
|
||||
def patch(self, request, dashboard_id, widget_id):
|
||||
dashboard_widget = DashboardWidget.objects.filter(
|
||||
widget_id=widget_id,
|
||||
dashboard_id=dashboard_id,
|
||||
widget_id=widget_id, dashboard_id=dashboard_id
|
||||
).first()
|
||||
dashboard_widget.is_visible = request.data.get(
|
||||
"is_visible", dashboard_widget.is_visible
|
||||
|
|
@ -839,10 +801,6 @@ class WidgetsEndpoint(BaseAPIView):
|
|||
dashboard_widget.sort_order = request.data.get(
|
||||
"sort_order", dashboard_widget.sort_order
|
||||
)
|
||||
dashboard_widget.filters = request.data.get(
|
||||
"filters", dashboard_widget.filters
|
||||
)
|
||||
dashboard_widget.filters = request.data.get("filters", dashboard_widget.filters)
|
||||
dashboard_widget.save()
|
||||
return Response(
|
||||
{"message": "successfully updated"}, status=status.HTTP_200_OK
|
||||
)
|
||||
return Response({"message": "successfully updated"}, status=status.HTTP_200_OK)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
# views.py
|
||||
from django.http import JsonResponse
|
||||
|
||||
|
||||
def custom_404_view(request, exception=None):
|
||||
return JsonResponse({"error": "Page not found."}, status=404)
|
||||
|
|
|
|||
|
|
@ -11,11 +11,7 @@ from rest_framework import status
|
|||
|
||||
# Module imports
|
||||
from ..base import BaseViewSet, BaseAPIView
|
||||
from plane.app.permissions import (
|
||||
ProjectEntityPermission,
|
||||
allow_permission,
|
||||
ROLE,
|
||||
)
|
||||
from plane.app.permissions import ProjectEntityPermission, allow_permission, ROLE
|
||||
from plane.db.models import Project, Estimate, EstimatePoint, Issue
|
||||
from plane.app.serializers import (
|
||||
EstimateSerializer,
|
||||
|
|
@ -32,13 +28,7 @@ def generate_random_name(length=10):
|
|||
|
||||
|
||||
class ProjectEstimatePointEndpoint(BaseAPIView):
|
||||
|
||||
@allow_permission(
|
||||
[
|
||||
ROLE.ADMIN,
|
||||
ROLE.MEMBER,
|
||||
]
|
||||
)
|
||||
@allow_permission([ROLE.ADMIN, ROLE.MEMBER])
|
||||
def get(self, request, slug, project_id):
|
||||
project = Project.objects.get(workspace__slug=slug, pk=project_id)
|
||||
if project.estimate_id is not None:
|
||||
|
|
@ -53,17 +43,13 @@ class ProjectEstimatePointEndpoint(BaseAPIView):
|
|||
|
||||
|
||||
class BulkEstimatePointEndpoint(BaseViewSet):
|
||||
permission_classes = [
|
||||
ProjectEntityPermission,
|
||||
]
|
||||
permission_classes = [ProjectEntityPermission]
|
||||
model = Estimate
|
||||
serializer_class = EstimateSerializer
|
||||
|
||||
def list(self, request, slug, project_id):
|
||||
estimates = (
|
||||
Estimate.objects.filter(
|
||||
workspace__slug=slug, project_id=project_id
|
||||
)
|
||||
Estimate.objects.filter(workspace__slug=slug, project_id=project_id)
|
||||
.prefetch_related("points")
|
||||
.select_related("workspace", "project")
|
||||
)
|
||||
|
|
@ -91,9 +77,7 @@ class BulkEstimatePointEndpoint(BaseViewSet):
|
|||
data=request.data.get("estimate_points"), many=True
|
||||
)
|
||||
if not serializer.is_valid():
|
||||
return Response(
|
||||
serializer.errors, status=status.HTTP_400_BAD_REQUEST
|
||||
)
|
||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
estimate_points = EstimatePoint.objects.bulk_create(
|
||||
[
|
||||
|
|
@ -121,16 +105,12 @@ class BulkEstimatePointEndpoint(BaseViewSet):
|
|||
pk=estimate_id, workspace__slug=slug, project_id=project_id
|
||||
)
|
||||
serializer = EstimateReadSerializer(estimate)
|
||||
return Response(
|
||||
serializer.data,
|
||||
status=status.HTTP_200_OK,
|
||||
)
|
||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||
|
||||
@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(
|
||||
{"error": "Estimate points are required"},
|
||||
|
|
@ -140,20 +120,15 @@ class BulkEstimatePointEndpoint(BaseViewSet):
|
|||
estimate = Estimate.objects.get(pk=estimate_id)
|
||||
|
||||
if request.data.get("estimate"):
|
||||
estimate.name = request.data.get("estimate").get(
|
||||
"name", estimate.name
|
||||
)
|
||||
estimate.type = request.data.get("estimate").get(
|
||||
"type", estimate.type
|
||||
)
|
||||
estimate.name = request.data.get("estimate").get("name", estimate.name)
|
||||
estimate.type = request.data.get("estimate").get("type", estimate.type)
|
||||
estimate.save()
|
||||
|
||||
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
|
||||
estimate_point.get("id") for estimate_point in estimate_points_data
|
||||
],
|
||||
workspace__slug=slug,
|
||||
project_id=project_id,
|
||||
|
|
@ -178,16 +153,11 @@ class BulkEstimatePointEndpoint(BaseViewSet):
|
|||
updated_estimate_points.append(estimate_point)
|
||||
|
||||
EstimatePoint.objects.bulk_update(
|
||||
updated_estimate_points,
|
||||
["key", "value"],
|
||||
batch_size=10,
|
||||
updated_estimate_points, ["key", "value"], batch_size=10
|
||||
)
|
||||
|
||||
estimate_serializer = EstimateReadSerializer(estimate)
|
||||
return Response(
|
||||
estimate_serializer.data,
|
||||
status=status.HTTP_200_OK,
|
||||
)
|
||||
return Response(estimate_serializer.data, status=status.HTTP_200_OK)
|
||||
|
||||
@invalidate_cache(
|
||||
path="/api/workspaces/:slug/estimates/", url_params=True, user=False
|
||||
|
|
@ -201,7 +171,6 @@ class BulkEstimatePointEndpoint(BaseViewSet):
|
|||
|
||||
|
||||
class EstimatePointEndpoint(BaseViewSet):
|
||||
|
||||
@allow_permission([ROLE.ADMIN, ROLE.MEMBER])
|
||||
def create(self, request, slug, project_id, estimate_id):
|
||||
# TODO: add a key validation if the same key already exists
|
||||
|
|
@ -213,18 +182,13 @@ class EstimatePointEndpoint(BaseViewSet):
|
|||
key = request.data.get("key", 0)
|
||||
value = request.data.get("value", "")
|
||||
estimate_point = EstimatePoint.objects.create(
|
||||
estimate_id=estimate_id,
|
||||
project_id=project_id,
|
||||
key=key,
|
||||
value=value,
|
||||
estimate_id=estimate_id, project_id=project_id, key=key, value=value
|
||||
)
|
||||
serializer = EstimatePointSerializer(estimate_point).data
|
||||
return Response(serializer, status=status.HTTP_200_OK)
|
||||
|
||||
@allow_permission([ROLE.ADMIN, ROLE.MEMBER])
|
||||
def partial_update(
|
||||
self, request, slug, project_id, estimate_id, estimate_point_id
|
||||
):
|
||||
def partial_update(self, request, slug, project_id, estimate_id, estimate_point_id):
|
||||
# TODO: add a key validation if the same key already exists
|
||||
estimate_point = EstimatePoint.objects.get(
|
||||
pk=estimate_point_id,
|
||||
|
|
@ -236,21 +200,15 @@ class EstimatePointEndpoint(BaseViewSet):
|
|||
estimate_point, data=request.data, partial=True
|
||||
)
|
||||
if not serializer.is_valid():
|
||||
return Response(
|
||||
serializer.errors, status=status.HTTP_400_BAD_REQUEST
|
||||
)
|
||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
serializer.save()
|
||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||
|
||||
@allow_permission([ROLE.ADMIN, ROLE.MEMBER])
|
||||
def destroy(
|
||||
self, request, slug, project_id, estimate_id, estimate_point_id
|
||||
):
|
||||
def destroy(self, request, slug, project_id, estimate_id, estimate_point_id):
|
||||
new_estimate_id = request.data.get("new_estimate_id", None)
|
||||
estimate_points = EstimatePoint.objects.filter(
|
||||
estimate_id=estimate_id,
|
||||
project_id=project_id,
|
||||
workspace__slug=slug,
|
||||
estimate_id=estimate_id, project_id=project_id, workspace__slug=slug
|
||||
)
|
||||
# update all the issues with the new estimate
|
||||
if new_estimate_id:
|
||||
|
|
@ -265,10 +223,8 @@ class EstimatePointEndpoint(BaseViewSet):
|
|||
requested_data=json.dumps(
|
||||
{
|
||||
"estimate_point": (
|
||||
str(new_estimate_id)
|
||||
if new_estimate_id
|
||||
else None
|
||||
),
|
||||
str(new_estimate_id) if new_estimate_id else None
|
||||
)
|
||||
}
|
||||
),
|
||||
actor_id=str(request.user.id),
|
||||
|
|
@ -280,7 +236,7 @@ class EstimatePointEndpoint(BaseViewSet):
|
|||
str(issue.estimate_point_id)
|
||||
if issue.estimate_point_id
|
||||
else None
|
||||
),
|
||||
)
|
||||
}
|
||||
),
|
||||
epoch=int(timezone.now().timestamp()),
|
||||
|
|
@ -295,11 +251,7 @@ class EstimatePointEndpoint(BaseViewSet):
|
|||
for issue in issues:
|
||||
issue_activity.delay(
|
||||
type="issue.activity.updated",
|
||||
requested_data=json.dumps(
|
||||
{
|
||||
"estimate_point": None,
|
||||
}
|
||||
),
|
||||
requested_data=json.dumps({"estimate_point": None}),
|
||||
actor_id=str(request.user.id),
|
||||
issue_id=issue.id,
|
||||
project_id=str(project_id),
|
||||
|
|
@ -309,16 +261,14 @@ class EstimatePointEndpoint(BaseViewSet):
|
|||
str(issue.estimate_point_id)
|
||||
if issue.estimate_point_id
|
||||
else None
|
||||
),
|
||||
)
|
||||
}
|
||||
),
|
||||
epoch=int(timezone.now().timestamp()),
|
||||
)
|
||||
|
||||
# delete the estimate point
|
||||
old_estimate_point = EstimatePoint.objects.filter(
|
||||
pk=estimate_point_id
|
||||
).first()
|
||||
old_estimate_point = EstimatePoint.objects.filter(pk=estimate_point_id).first()
|
||||
|
||||
# rearrange the estimate points
|
||||
updated_estimate_points = []
|
||||
|
|
@ -328,9 +278,7 @@ class EstimatePointEndpoint(BaseViewSet):
|
|||
updated_estimate_points.append(estimate_point)
|
||||
|
||||
EstimatePoint.objects.bulk_update(
|
||||
updated_estimate_points,
|
||||
["key"],
|
||||
batch_size=10,
|
||||
updated_estimate_points, ["key"], batch_size=10
|
||||
)
|
||||
|
||||
old_estimate_point.delete()
|
||||
|
|
|
|||
|
|
@ -51,9 +51,7 @@ class ExportIssuesEndpoint(BaseAPIView):
|
|||
slug=slug,
|
||||
)
|
||||
return Response(
|
||||
{
|
||||
"message": "Once the export is ready you will be able to download it"
|
||||
},
|
||||
{"message": "Once the export is ready you will be able to download it"},
|
||||
status=status.HTTP_200_OK,
|
||||
)
|
||||
else:
|
||||
|
|
@ -62,18 +60,13 @@ class ExportIssuesEndpoint(BaseAPIView):
|
|||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
||||
@allow_permission(
|
||||
allowed_roles=[ROLE.ADMIN, ROLE.MEMBER], level="WORKSPACE"
|
||||
)
|
||||
@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",
|
||||
workspace__slug=slug, type="issue_exports"
|
||||
).select_related("workspace", "initiated_by")
|
||||
|
||||
if request.GET.get("per_page", False) and request.GET.get(
|
||||
"cursor", False
|
||||
):
|
||||
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,
|
||||
|
|
|
|||
41
apiserver/plane/app/views/external/base.py
vendored
41
apiserver/plane/app/views/external/base.py
vendored
|
|
@ -13,15 +13,11 @@ from rest_framework import status
|
|||
from ..base import BaseAPIView
|
||||
from plane.app.permissions import allow_permission, ROLE
|
||||
from plane.db.models import Workspace, Project
|
||||
from plane.app.serializers import (
|
||||
ProjectLiteSerializer,
|
||||
WorkspaceLiteSerializer,
|
||||
)
|
||||
from plane.app.serializers import ProjectLiteSerializer, WorkspaceLiteSerializer
|
||||
from plane.license.utils.instance_value import get_configuration_value
|
||||
|
||||
|
||||
class GPTIntegrationEndpoint(BaseAPIView):
|
||||
|
||||
@allow_permission([ROLE.ADMIN, ROLE.MEMBER])
|
||||
def post(self, request, slug, project_id):
|
||||
OPENAI_API_KEY, GPT_ENGINE = get_configuration_value(
|
||||
|
|
@ -50,19 +46,15 @@ class GPTIntegrationEndpoint(BaseAPIView):
|
|||
|
||||
if not task:
|
||||
return Response(
|
||||
{"error": "Task is required"},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
{"error": "Task is required"}, status=status.HTTP_400_BAD_REQUEST
|
||||
)
|
||||
|
||||
final_text = task + "\n" + prompt
|
||||
|
||||
client = OpenAI(
|
||||
api_key=OPENAI_API_KEY,
|
||||
)
|
||||
client = OpenAI(api_key=OPENAI_API_KEY)
|
||||
|
||||
response = client.chat.completions.create(
|
||||
model=GPT_ENGINE,
|
||||
messages=[{"role": "user", "content": final_text}],
|
||||
model=GPT_ENGINE, messages=[{"role": "user", "content": final_text}]
|
||||
)
|
||||
|
||||
workspace = Workspace.objects.get(slug=slug)
|
||||
|
|
@ -82,10 +74,7 @@ class GPTIntegrationEndpoint(BaseAPIView):
|
|||
|
||||
|
||||
class WorkspaceGPTIntegrationEndpoint(BaseAPIView):
|
||||
|
||||
@allow_permission(
|
||||
allowed_roles=[ROLE.ADMIN, ROLE.MEMBER], level="WORKSPACE"
|
||||
)
|
||||
@allow_permission(allowed_roles=[ROLE.ADMIN, ROLE.MEMBER], level="WORKSPACE")
|
||||
def post(self, request, slug):
|
||||
OPENAI_API_KEY, GPT_ENGINE = get_configuration_value(
|
||||
[
|
||||
|
|
@ -113,29 +102,21 @@ class WorkspaceGPTIntegrationEndpoint(BaseAPIView):
|
|||
|
||||
if not task:
|
||||
return Response(
|
||||
{"error": "Task is required"},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
{"error": "Task is required"}, status=status.HTTP_400_BAD_REQUEST
|
||||
)
|
||||
|
||||
final_text = task + "\n" + prompt
|
||||
|
||||
client = OpenAI(
|
||||
api_key=OPENAI_API_KEY,
|
||||
)
|
||||
client = OpenAI(api_key=OPENAI_API_KEY)
|
||||
|
||||
response = client.chat.completions.create(
|
||||
model=GPT_ENGINE,
|
||||
messages=[{"role": "user", "content": final_text}],
|
||||
model=GPT_ENGINE, messages=[{"role": "user", "content": final_text}]
|
||||
)
|
||||
|
||||
text = response.choices[0].message.content.strip()
|
||||
text_html = text.replace("\n", "<br/>")
|
||||
return Response(
|
||||
{
|
||||
"response": text,
|
||||
"response_html": text_html,
|
||||
},
|
||||
status=status.HTTP_200_OK,
|
||||
{"response": text, "response_html": text_html}, status=status.HTTP_200_OK
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -164,9 +145,7 @@ class UnsplashEndpoint(BaseAPIView):
|
|||
else f"https://api.unsplash.com/photos/?client_id={UNSPLASH_ACCESS_KEY}&page={page}&per_page={per_page}"
|
||||
)
|
||||
|
||||
headers = {
|
||||
"Content-Type": "application/json",
|
||||
}
|
||||
headers = {"Content-Type": "application/json"}
|
||||
|
||||
resp = requests.get(url=url, headers=headers)
|
||||
return Response(resp.json(), status=resp.status_code)
|
||||
|
|
|
|||
|
|
@ -40,7 +40,6 @@ from plane.bgtasks.issue_activities_task import issue_activity
|
|||
|
||||
|
||||
class IntakeViewSet(BaseViewSet):
|
||||
|
||||
serializer_class = IntakeSerializer
|
||||
model = Intake
|
||||
|
||||
|
|
@ -54,8 +53,7 @@ class IntakeViewSet(BaseViewSet):
|
|||
)
|
||||
.annotate(
|
||||
pending_issue_count=Count(
|
||||
"issue_intake",
|
||||
filter=Q(issue_intake__status=-2),
|
||||
"issue_intake", filter=Q(issue_intake__status=-2)
|
||||
)
|
||||
)
|
||||
.select_related("workspace", "project")
|
||||
|
|
@ -64,10 +62,7 @@ class IntakeViewSet(BaseViewSet):
|
|||
@allow_permission([ROLE.ADMIN, ROLE.MEMBER])
|
||||
def list(self, request, slug, project_id):
|
||||
intake = self.get_queryset().first()
|
||||
return Response(
|
||||
IntakeSerializer(intake).data,
|
||||
status=status.HTTP_200_OK,
|
||||
)
|
||||
return Response(IntakeSerializer(intake).data, status=status.HTTP_200_OK)
|
||||
|
||||
@allow_permission([ROLE.ADMIN, ROLE.MEMBER])
|
||||
def perform_create(self, serializer):
|
||||
|
|
@ -89,13 +84,10 @@ class IntakeViewSet(BaseViewSet):
|
|||
|
||||
|
||||
class IntakeIssueViewSet(BaseViewSet):
|
||||
|
||||
serializer_class = IntakeIssueSerializer
|
||||
model = IntakeIssue
|
||||
|
||||
filterset_fields = [
|
||||
"statulls",
|
||||
]
|
||||
filterset_fields = ["statulls"]
|
||||
|
||||
def get_queryset(self):
|
||||
return (
|
||||
|
|
@ -136,9 +128,7 @@ class IntakeIssueViewSet(BaseViewSet):
|
|||
.values("count")
|
||||
)
|
||||
.annotate(
|
||||
sub_issues_count=Issue.issue_objects.filter(
|
||||
parent=OuterRef("id")
|
||||
)
|
||||
sub_issues_count=Issue.issue_objects.filter(parent=OuterRef("id"))
|
||||
.order_by()
|
||||
.annotate(count=Func(F("id"), function="Count"))
|
||||
.values("count")
|
||||
|
|
@ -150,7 +140,7 @@ class IntakeIssueViewSet(BaseViewSet):
|
|||
distinct=True,
|
||||
filter=Q(
|
||||
~Q(labels__id__isnull=True)
|
||||
& Q(label_issue__deleted_at__isnull=True),
|
||||
& Q(label_issue__deleted_at__isnull=True)
|
||||
),
|
||||
),
|
||||
Value([], output_field=ArrayField(UUIDField())),
|
||||
|
|
@ -194,9 +184,7 @@ class IntakeIssueViewSet(BaseViewSet):
|
|||
intake_id=intake_id.id, project_id=project_id, **filters
|
||||
)
|
||||
.select_related("issue")
|
||||
.prefetch_related(
|
||||
"issue__labels",
|
||||
)
|
||||
.prefetch_related("issue__labels")
|
||||
.annotate(
|
||||
label_ids=Coalesce(
|
||||
ArrayAgg(
|
||||
|
|
@ -235,8 +223,7 @@ class IntakeIssueViewSet(BaseViewSet):
|
|||
request=request,
|
||||
queryset=(intake_issue),
|
||||
on_results=lambda intake_issues: IntakeIssueSerializer(
|
||||
intake_issues,
|
||||
many=True,
|
||||
intake_issues, many=True
|
||||
).data,
|
||||
)
|
||||
|
||||
|
|
@ -244,8 +231,7 @@ class IntakeIssueViewSet(BaseViewSet):
|
|||
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,
|
||||
{"error": "Name is required"}, status=status.HTTP_400_BAD_REQUEST
|
||||
)
|
||||
|
||||
# Check for valid priority
|
||||
|
|
@ -257,8 +243,7 @@ class IntakeIssueViewSet(BaseViewSet):
|
|||
"none",
|
||||
]:
|
||||
return Response(
|
||||
{"error": "Invalid priority"},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
{"error": "Invalid priority"}, status=status.HTTP_400_BAD_REQUEST
|
||||
)
|
||||
|
||||
# create an issue
|
||||
|
|
@ -298,10 +283,7 @@ class IntakeIssueViewSet(BaseViewSet):
|
|||
)
|
||||
intake_issue = (
|
||||
IntakeIssue.objects.select_related("issue")
|
||||
.prefetch_related(
|
||||
"issue__labels",
|
||||
"issue__assignees",
|
||||
)
|
||||
.prefetch_related("issue__labels", "issue__assignees")
|
||||
.annotate(
|
||||
label_ids=Coalesce(
|
||||
ArrayAgg(
|
||||
|
|
@ -309,9 +291,7 @@ class IntakeIssueViewSet(BaseViewSet):
|
|||
distinct=True,
|
||||
filter=Q(
|
||||
~Q(issue__labels__id__isnull=True)
|
||||
& Q(
|
||||
issue__label_issue__deleted_at__isnull=True
|
||||
)
|
||||
& Q(issue__label_issue__deleted_at__isnull=True)
|
||||
),
|
||||
),
|
||||
Value([], output_field=ArrayField(UUIDField())),
|
||||
|
|
@ -321,9 +301,7 @@ class IntakeIssueViewSet(BaseViewSet):
|
|||
"issue__assignees__id",
|
||||
distinct=True,
|
||||
filter=~Q(issue__assignees__id__isnull=True)
|
||||
& Q(
|
||||
issue__assignees__member_project__is_active=True
|
||||
),
|
||||
& Q(issue__assignees__member_project__is_active=True),
|
||||
),
|
||||
Value([], output_field=ArrayField(UUIDField())),
|
||||
),
|
||||
|
|
@ -337,9 +315,7 @@ class IntakeIssueViewSet(BaseViewSet):
|
|||
serializer = IntakeIssueDetailSerializer(intake_issue)
|
||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||
else:
|
||||
return Response(
|
||||
serializer.errors, status=status.HTTP_400_BAD_REQUEST
|
||||
)
|
||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
@allow_permission(allowed_roles=[ROLE.ADMIN], creator=True, model=Issue)
|
||||
def partial_update(self, request, slug, project_id, pk):
|
||||
|
|
@ -394,11 +370,7 @@ class IntakeIssueViewSet(BaseViewSet):
|
|||
),
|
||||
Value([], output_field=ArrayField(UUIDField())),
|
||||
),
|
||||
).get(
|
||||
pk=intake_issue.issue_id,
|
||||
workspace__slug=slug,
|
||||
project_id=project_id,
|
||||
)
|
||||
).get(pk=intake_issue.issue_id, workspace__slug=slug, project_id=project_id)
|
||||
# Only allow guests to edit name and description
|
||||
if project_member.role <= 5:
|
||||
issue_data = {
|
||||
|
|
@ -406,9 +378,7 @@ class IntakeIssueViewSet(BaseViewSet):
|
|||
"description_html": issue_data.get(
|
||||
"description_html", issue.description_html
|
||||
),
|
||||
"description": issue_data.get(
|
||||
"description", issue.description
|
||||
),
|
||||
"description": issue_data.get("description", issue.description),
|
||||
}
|
||||
|
||||
issue_serializer = IssueCreateSerializer(
|
||||
|
|
@ -459,9 +429,7 @@ class IntakeIssueViewSet(BaseViewSet):
|
|||
project_id=project_id,
|
||||
)
|
||||
state = State.objects.filter(
|
||||
group="cancelled",
|
||||
workspace__slug=slug,
|
||||
project_id=project_id,
|
||||
group="cancelled", workspace__slug=slug, project_id=project_id
|
||||
).first()
|
||||
if state is not None:
|
||||
issue.state = state
|
||||
|
|
@ -479,9 +447,7 @@ class IntakeIssueViewSet(BaseViewSet):
|
|||
if issue.state.is_triage:
|
||||
# Move to default state
|
||||
state = State.objects.filter(
|
||||
workspace__slug=slug,
|
||||
project_id=project_id,
|
||||
default=True,
|
||||
workspace__slug=slug, project_id=project_id, default=True
|
||||
).first()
|
||||
if state is not None:
|
||||
issue.state = state
|
||||
|
|
@ -489,9 +455,7 @@ class IntakeIssueViewSet(BaseViewSet):
|
|||
# create a activity for status change
|
||||
issue_activity.delay(
|
||||
type="intake.activity.created",
|
||||
requested_data=json.dumps(
|
||||
request.data, cls=DjangoJSONEncoder
|
||||
),
|
||||
requested_data=json.dumps(request.data, cls=DjangoJSONEncoder),
|
||||
actor_id=str(request.user.id),
|
||||
issue_id=str(pk),
|
||||
project_id=str(project_id),
|
||||
|
|
@ -504,10 +468,7 @@ class IntakeIssueViewSet(BaseViewSet):
|
|||
|
||||
intake_issue = (
|
||||
IntakeIssue.objects.select_related("issue")
|
||||
.prefetch_related(
|
||||
"issue__labels",
|
||||
"issue__assignees",
|
||||
)
|
||||
.prefetch_related("issue__labels", "issue__assignees")
|
||||
.annotate(
|
||||
label_ids=Coalesce(
|
||||
ArrayAgg(
|
||||
|
|
@ -515,9 +476,7 @@ class IntakeIssueViewSet(BaseViewSet):
|
|||
distinct=True,
|
||||
filter=Q(
|
||||
~Q(issue__labels__id__isnull=True)
|
||||
& Q(
|
||||
issue__label_issue__deleted_at__isnull=True
|
||||
)
|
||||
& Q(issue__label_issue__deleted_at__isnull=True)
|
||||
),
|
||||
),
|
||||
Value([], output_field=ArrayField(UUIDField())),
|
||||
|
|
@ -528,37 +487,23 @@ class IntakeIssueViewSet(BaseViewSet):
|
|||
distinct=True,
|
||||
filter=Q(
|
||||
~Q(issue__assignees__id__isnull=True)
|
||||
& Q(
|
||||
issue__issue_assignee__deleted_at__isnull=True
|
||||
)
|
||||
& Q(issue__issue_assignee__deleted_at__isnull=True)
|
||||
),
|
||||
),
|
||||
Value([], output_field=ArrayField(UUIDField())),
|
||||
),
|
||||
)
|
||||
.get(
|
||||
intake_id=intake_id.id,
|
||||
issue_id=pk,
|
||||
project_id=project_id,
|
||||
)
|
||||
.get(intake_id=intake_id.id, issue_id=pk, project_id=project_id)
|
||||
)
|
||||
serializer = IntakeIssueDetailSerializer(intake_issue).data
|
||||
return Response(serializer, status=status.HTTP_200_OK)
|
||||
return Response(
|
||||
serializer.errors, status=status.HTTP_400_BAD_REQUEST
|
||||
)
|
||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
else:
|
||||
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,
|
||||
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(
|
||||
|
|
@ -567,10 +512,7 @@ class IntakeIssueViewSet(BaseViewSet):
|
|||
project = Project.objects.get(pk=project_id)
|
||||
intake_issue = (
|
||||
IntakeIssue.objects.select_related("issue")
|
||||
.prefetch_related(
|
||||
"issue__labels",
|
||||
"issue__assignees",
|
||||
)
|
||||
.prefetch_related("issue__labels", "issue__assignees")
|
||||
.annotate(
|
||||
label_ids=Coalesce(
|
||||
ArrayAgg(
|
||||
|
|
@ -613,10 +555,7 @@ class IntakeIssueViewSet(BaseViewSet):
|
|||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
issue = IntakeIssueDetailSerializer(intake_issue).data
|
||||
return Response(
|
||||
issue,
|
||||
status=status.HTTP_200_OK,
|
||||
)
|
||||
return Response(issue, status=status.HTTP_200_OK)
|
||||
|
||||
@allow_permission(allowed_roles=[ROLE.ADMIN], creator=True, model=Issue)
|
||||
def destroy(self, request, slug, project_id, pk):
|
||||
|
|
|
|||
|
|
@ -2,10 +2,7 @@
|
|||
from itertools import chain
|
||||
|
||||
# Django imports
|
||||
from django.db.models import (
|
||||
Prefetch,
|
||||
Q,
|
||||
)
|
||||
from django.db.models import Prefetch, Q
|
||||
from django.utils.decorators import method_decorator
|
||||
from django.views.decorators.gzip import gzip_page
|
||||
|
||||
|
|
@ -15,35 +12,16 @@ from rest_framework import status
|
|||
|
||||
# Module imports
|
||||
from .. import BaseAPIView
|
||||
from plane.app.serializers import (
|
||||
IssueActivitySerializer,
|
||||
IssueCommentSerializer,
|
||||
)
|
||||
from plane.app.permissions import (
|
||||
ProjectEntityPermission,
|
||||
allow_permission,
|
||||
ROLE,
|
||||
)
|
||||
from plane.db.models import (
|
||||
IssueActivity,
|
||||
IssueComment,
|
||||
CommentReaction,
|
||||
)
|
||||
from plane.app.serializers import IssueActivitySerializer, IssueCommentSerializer
|
||||
from plane.app.permissions import ProjectEntityPermission, allow_permission, ROLE
|
||||
from plane.db.models import IssueActivity, IssueComment, CommentReaction
|
||||
|
||||
|
||||
class IssueActivityEndpoint(BaseAPIView):
|
||||
permission_classes = [
|
||||
ProjectEntityPermission,
|
||||
]
|
||||
permission_classes = [ProjectEntityPermission]
|
||||
|
||||
@method_decorator(gzip_page)
|
||||
@allow_permission(
|
||||
[
|
||||
ROLE.ADMIN,
|
||||
ROLE.MEMBER,
|
||||
ROLE.GUEST,
|
||||
]
|
||||
)
|
||||
@allow_permission([ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST])
|
||||
def get(self, request, slug, project_id, issue_id):
|
||||
filters = {}
|
||||
if request.GET.get("created_at__gt", None) is not None:
|
||||
|
|
@ -79,9 +57,7 @@ class IssueActivityEndpoint(BaseAPIView):
|
|||
)
|
||||
)
|
||||
)
|
||||
issue_activities = IssueActivitySerializer(
|
||||
issue_activities, many=True
|
||||
).data
|
||||
issue_activities = IssueActivitySerializer(issue_activities, many=True).data
|
||||
issue_comments = IssueCommentSerializer(issue_comments, many=True).data
|
||||
|
||||
if request.GET.get("activity_type", None) == "issue-property":
|
||||
|
|
|
|||
|
|
@ -12,9 +12,7 @@ from django.views.decorators.gzip import gzip_page
|
|||
from rest_framework import status
|
||||
from rest_framework.response import Response
|
||||
|
||||
from plane.app.permissions import (
|
||||
ProjectEntityPermission,
|
||||
)
|
||||
from plane.app.permissions import ProjectEntityPermission
|
||||
from plane.app.serializers import (
|
||||
IssueFlatSerializer,
|
||||
IssueSerializer,
|
||||
|
|
@ -27,7 +25,7 @@ from plane.db.models import (
|
|||
IssueLink,
|
||||
IssueSubscriber,
|
||||
IssueReaction,
|
||||
CycleIssue
|
||||
CycleIssue,
|
||||
)
|
||||
from plane.utils.grouper import (
|
||||
issue_group_values,
|
||||
|
|
@ -36,10 +34,7 @@ from plane.utils.grouper import (
|
|||
)
|
||||
from plane.utils.issue_filters import issue_filters
|
||||
from plane.utils.order_queryset import order_issue_queryset
|
||||
from plane.utils.paginator import (
|
||||
GroupedOffsetPaginator,
|
||||
SubGroupedOffsetPaginator,
|
||||
)
|
||||
from plane.utils.paginator import GroupedOffsetPaginator, SubGroupedOffsetPaginator
|
||||
from plane.app.permissions import allow_permission, ROLE
|
||||
from plane.utils.error_codes import ERROR_CODES
|
||||
|
||||
|
|
@ -88,9 +83,7 @@ class IssueArchiveViewSet(BaseViewSet):
|
|||
.values("count")
|
||||
)
|
||||
.annotate(
|
||||
sub_issues_count=Issue.issue_objects.filter(
|
||||
parent=OuterRef("id")
|
||||
)
|
||||
sub_issues_count=Issue.issue_objects.filter(parent=OuterRef("id"))
|
||||
.order_by()
|
||||
.annotate(count=Func(F("id"), function="Count"))
|
||||
.values("count")
|
||||
|
|
@ -98,12 +91,7 @@ class IssueArchiveViewSet(BaseViewSet):
|
|||
)
|
||||
|
||||
@method_decorator(gzip_page)
|
||||
@allow_permission(
|
||||
[
|
||||
ROLE.ADMIN,
|
||||
ROLE.MEMBER,
|
||||
]
|
||||
)
|
||||
@allow_permission([ROLE.ADMIN, ROLE.MEMBER])
|
||||
def list(self, request, slug, project_id):
|
||||
filters = issue_filters(request.query_params, "GET")
|
||||
show_sub_issues = request.GET.get("show_sub_issues", "true")
|
||||
|
|
@ -119,8 +107,7 @@ class IssueArchiveViewSet(BaseViewSet):
|
|||
)
|
||||
# Issue queryset
|
||||
issue_queryset, order_by_param = order_issue_queryset(
|
||||
issue_queryset=issue_queryset,
|
||||
order_by_param=order_by_param,
|
||||
issue_queryset=issue_queryset, order_by_param=order_by_param
|
||||
)
|
||||
|
||||
# Group by
|
||||
|
|
@ -129,9 +116,7 @@ class IssueArchiveViewSet(BaseViewSet):
|
|||
|
||||
# issue queryset
|
||||
issue_queryset = issue_queryset_grouper(
|
||||
queryset=issue_queryset,
|
||||
group_by=group_by,
|
||||
sub_group_by=sub_group_by,
|
||||
queryset=issue_queryset, group_by=group_by, sub_group_by=sub_group_by
|
||||
)
|
||||
|
||||
if group_by:
|
||||
|
|
@ -151,9 +136,7 @@ class IssueArchiveViewSet(BaseViewSet):
|
|||
order_by=order_by_param,
|
||||
queryset=issue_queryset,
|
||||
on_results=lambda issues: issue_on_results(
|
||||
group_by=group_by,
|
||||
issues=issues,
|
||||
sub_group_by=sub_group_by,
|
||||
group_by=group_by, issues=issues, sub_group_by=sub_group_by
|
||||
),
|
||||
paginator_cls=SubGroupedOffsetPaginator,
|
||||
group_by_fields=issue_group_values(
|
||||
|
|
@ -187,9 +170,7 @@ class IssueArchiveViewSet(BaseViewSet):
|
|||
order_by=order_by_param,
|
||||
queryset=issue_queryset,
|
||||
on_results=lambda issues: issue_on_results(
|
||||
group_by=group_by,
|
||||
issues=issues,
|
||||
sub_group_by=sub_group_by,
|
||||
group_by=group_by, issues=issues, sub_group_by=sub_group_by
|
||||
),
|
||||
paginator_cls=GroupedOffsetPaginator,
|
||||
group_by_fields=issue_group_values(
|
||||
|
|
@ -219,12 +200,7 @@ class IssueArchiveViewSet(BaseViewSet):
|
|||
),
|
||||
)
|
||||
|
||||
@allow_permission(
|
||||
[
|
||||
ROLE.ADMIN,
|
||||
ROLE.MEMBER,
|
||||
]
|
||||
)
|
||||
@allow_permission([ROLE.ADMIN, ROLE.MEMBER])
|
||||
def retrieve(self, request, slug, project_id, pk=None):
|
||||
issue = (
|
||||
self.get_queryset()
|
||||
|
|
@ -232,9 +208,7 @@ class IssueArchiveViewSet(BaseViewSet):
|
|||
.prefetch_related(
|
||||
Prefetch(
|
||||
"issue_reactions",
|
||||
queryset=IssueReaction.objects.select_related(
|
||||
"issue", "actor"
|
||||
),
|
||||
queryset=IssueReaction.objects.select_related("issue", "actor"),
|
||||
)
|
||||
)
|
||||
.prefetch_related(
|
||||
|
|
@ -265,24 +239,17 @@ 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,
|
||||
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"
|
||||
},
|
||||
{"error": "Can only archive completed or cancelled state group issue"},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
issue_activity.delay(
|
||||
type="issue.activity.updated",
|
||||
requested_data=json.dumps(
|
||||
{
|
||||
"archived_at": str(timezone.now().date()),
|
||||
"automation": False,
|
||||
}
|
||||
{"archived_at": str(timezone.now().date()), "automation": False}
|
||||
),
|
||||
actor_id=str(request.user.id),
|
||||
issue_id=str(issue.id),
|
||||
|
|
@ -329,9 +296,7 @@ class IssueArchiveViewSet(BaseViewSet):
|
|||
|
||||
|
||||
class BulkArchiveIssuesEndpoint(BaseAPIView):
|
||||
permission_classes = [
|
||||
ProjectEntityPermission,
|
||||
]
|
||||
permission_classes = [ProjectEntityPermission]
|
||||
|
||||
@allow_permission([ROLE.ADMIN, ROLE.MEMBER])
|
||||
def post(self, request, slug, project_id):
|
||||
|
|
@ -339,8 +304,7 @@ class BulkArchiveIssuesEndpoint(BaseAPIView):
|
|||
|
||||
if not len(issue_ids):
|
||||
return Response(
|
||||
{"error": "Issue IDs are required"},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
{"error": "Issue IDs are required"}, status=status.HTTP_400_BAD_REQUEST
|
||||
)
|
||||
|
||||
issues = Issue.objects.filter(
|
||||
|
|
@ -351,9 +315,7 @@ class BulkArchiveIssuesEndpoint(BaseAPIView):
|
|||
if issue.state.group not in ["completed", "cancelled"]:
|
||||
return Response(
|
||||
{
|
||||
"error_code": ERROR_CODES[
|
||||
"INVALID_ARCHIVE_STATE_GROUP"
|
||||
],
|
||||
"error_code": ERROR_CODES["INVALID_ARCHIVE_STATE_GROUP"],
|
||||
"error_message": "INVALID_ARCHIVE_STATE_GROUP",
|
||||
},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
|
|
@ -361,10 +323,7 @@ class BulkArchiveIssuesEndpoint(BaseAPIView):
|
|||
issue_activity.delay(
|
||||
type="issue.activity.updated",
|
||||
requested_data=json.dumps(
|
||||
{
|
||||
"archived_at": str(timezone.now().date()),
|
||||
"automation": False,
|
||||
}
|
||||
{"archived_at": str(timezone.now().date()), "automation": False}
|
||||
),
|
||||
actor_id=str(request.user.id),
|
||||
issue_id=str(issue.id),
|
||||
|
|
@ -381,6 +340,5 @@ class BulkArchiveIssuesEndpoint(BaseAPIView):
|
|||
Issue.objects.bulk_update(bulk_archive_issues, ["archived_at"])
|
||||
|
||||
return Response(
|
||||
{"archived_at": str(timezone.now().date())},
|
||||
status=status.HTTP_200_OK,
|
||||
{"archived_at": str(timezone.now().date())}, status=status.HTTP_200_OK
|
||||
)
|
||||
|
|
|
|||
|
|
@ -45,10 +45,7 @@ class IssueAttachmentEndpoint(BaseAPIView):
|
|||
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(
|
||||
serializer.data,
|
||||
cls=DjangoJSONEncoder,
|
||||
),
|
||||
current_instance=json.dumps(serializer.data, cls=DjangoJSONEncoder),
|
||||
epoch=int(timezone.now().timestamp()),
|
||||
notification=True,
|
||||
origin=request.META.get("HTTP_ORIGIN"),
|
||||
|
|
@ -75,13 +72,7 @@ class IssueAttachmentEndpoint(BaseAPIView):
|
|||
|
||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
@allow_permission(
|
||||
[
|
||||
ROLE.ADMIN,
|
||||
ROLE.MEMBER,
|
||||
ROLE.GUEST,
|
||||
]
|
||||
)
|
||||
@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
|
||||
|
|
@ -91,7 +82,6 @@ class IssueAttachmentEndpoint(BaseAPIView):
|
|||
|
||||
|
||||
class IssueAttachmentV2Endpoint(BaseAPIView):
|
||||
|
||||
serializer_class = IssueAttachmentSerializer
|
||||
model = FileAsset
|
||||
|
||||
|
|
@ -103,10 +93,7 @@ class IssueAttachmentV2Endpoint(BaseAPIView):
|
|||
|
||||
if not type or type not in settings.ATTACHMENT_MIME_TYPES:
|
||||
return Response(
|
||||
{
|
||||
"error": "Invalid file type.",
|
||||
"status": False,
|
||||
},
|
||||
{"error": "Invalid file type.", "status": False},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
||||
|
|
@ -121,11 +108,7 @@ class IssueAttachmentV2Endpoint(BaseAPIView):
|
|||
|
||||
# Create a File Asset
|
||||
asset = FileAsset.objects.create(
|
||||
attributes={
|
||||
"name": name,
|
||||
"type": type,
|
||||
"size": size_limit,
|
||||
},
|
||||
attributes={"name": name, "type": type, "size": size_limit},
|
||||
asset=asset_key,
|
||||
size=size_limit,
|
||||
workspace_id=workspace.id,
|
||||
|
|
@ -139,9 +122,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,
|
||||
object_name=asset_key, file_type=type, file_size=size_limit
|
||||
)
|
||||
# Return the presigned URL
|
||||
return Response(
|
||||
|
|
@ -177,13 +158,7 @@ class IssueAttachmentV2Endpoint(BaseAPIView):
|
|||
|
||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
@allow_permission(
|
||||
[
|
||||
ROLE.ADMIN,
|
||||
ROLE.MEMBER,
|
||||
ROLE.GUEST,
|
||||
]
|
||||
)
|
||||
@allow_permission([ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST])
|
||||
def get(self, request, slug, project_id, issue_id, pk=None):
|
||||
if pk:
|
||||
# Get the asset
|
||||
|
|
@ -194,10 +169,7 @@ class IssueAttachmentV2Endpoint(BaseAPIView):
|
|||
# Check if the asset is uploaded
|
||||
if not asset.is_uploaded:
|
||||
return Response(
|
||||
{
|
||||
"error": "The asset is not uploaded.",
|
||||
"status": False,
|
||||
},
|
||||
{"error": "The asset is not uploaded.", "status": False},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
||||
|
|
@ -221,13 +193,7 @@ class IssueAttachmentV2Endpoint(BaseAPIView):
|
|||
serializer = IssueAttachmentSerializer(issue_attachments, many=True)
|
||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||
|
||||
@allow_permission(
|
||||
[
|
||||
ROLE.ADMIN,
|
||||
ROLE.MEMBER,
|
||||
ROLE.GUEST,
|
||||
]
|
||||
)
|
||||
@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
|
||||
|
|
@ -242,10 +208,7 @@ class IssueAttachmentV2Endpoint(BaseAPIView):
|
|||
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(
|
||||
serializer.data,
|
||||
cls=DjangoJSONEncoder,
|
||||
),
|
||||
current_instance=json.dumps(serializer.data, cls=DjangoJSONEncoder),
|
||||
epoch=int(timezone.now().timestamp()),
|
||||
notification=True,
|
||||
origin=request.META.get("HTTP_ORIGIN"),
|
||||
|
|
|
|||
|
|
@ -54,10 +54,7 @@ from plane.utils.grouper import (
|
|||
)
|
||||
from plane.utils.issue_filters import issue_filters
|
||||
from plane.utils.order_queryset import order_issue_queryset
|
||||
from plane.utils.paginator import (
|
||||
GroupedOffsetPaginator,
|
||||
SubGroupedOffsetPaginator,
|
||||
)
|
||||
from plane.utils.paginator import GroupedOffsetPaginator, SubGroupedOffsetPaginator
|
||||
from .. import BaseAPIView, BaseViewSet
|
||||
from plane.utils.user_timezone_converter import user_timezone_converter
|
||||
from plane.bgtasks.recent_visited_task import recent_visited_task
|
||||
|
|
@ -72,13 +69,10 @@ class IssueListEndpoint(BaseAPIView):
|
|||
|
||||
if not issue_ids:
|
||||
return Response(
|
||||
{"error": "Issues are required"},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
{"error": "Issues are required"}, status=status.HTTP_400_BAD_REQUEST
|
||||
)
|
||||
|
||||
issue_ids = [
|
||||
issue_id for issue_id in issue_ids.split(",") if issue_id != ""
|
||||
]
|
||||
issue_ids = [issue_id for issue_id in issue_ids.split(",") if issue_id != ""]
|
||||
|
||||
queryset = (
|
||||
Issue.issue_objects.filter(
|
||||
|
|
@ -110,9 +104,7 @@ class IssueListEndpoint(BaseAPIView):
|
|||
.values("count")
|
||||
)
|
||||
.annotate(
|
||||
sub_issues_count=Issue.issue_objects.filter(
|
||||
parent=OuterRef("id")
|
||||
)
|
||||
sub_issues_count=Issue.issue_objects.filter(parent=OuterRef("id"))
|
||||
.order_by()
|
||||
.annotate(count=Func(F("id"), function="Count"))
|
||||
.values("count")
|
||||
|
|
@ -125,8 +117,7 @@ class IssueListEndpoint(BaseAPIView):
|
|||
issue_queryset = queryset.filter(**filters)
|
||||
# Issue queryset
|
||||
issue_queryset, _ = order_issue_queryset(
|
||||
issue_queryset=issue_queryset,
|
||||
order_by_param=order_by_param,
|
||||
issue_queryset=issue_queryset, order_by_param=order_by_param
|
||||
)
|
||||
|
||||
# Group by
|
||||
|
|
@ -135,9 +126,7 @@ class IssueListEndpoint(BaseAPIView):
|
|||
|
||||
# issue queryset
|
||||
issue_queryset = issue_queryset_grouper(
|
||||
queryset=issue_queryset,
|
||||
group_by=group_by,
|
||||
sub_group_by=sub_group_by,
|
||||
queryset=issue_queryset, group_by=group_by, sub_group_by=sub_group_by
|
||||
)
|
||||
|
||||
recent_visited_task.delay(
|
||||
|
|
@ -199,21 +188,13 @@ class IssueViewSet(BaseViewSet):
|
|||
model = Issue
|
||||
webhook_event = "issue"
|
||||
|
||||
search_fields = [
|
||||
"name",
|
||||
]
|
||||
search_fields = ["name"]
|
||||
|
||||
filterset_fields = [
|
||||
"state__name",
|
||||
"assignees__id",
|
||||
"workspace__id",
|
||||
]
|
||||
filterset_fields = ["state__name", "assignees__id", "workspace__id"]
|
||||
|
||||
def get_queryset(self):
|
||||
return (
|
||||
Issue.issue_objects.filter(
|
||||
project_id=self.kwargs.get("project_id")
|
||||
)
|
||||
Issue.issue_objects.filter(project_id=self.kwargs.get("project_id"))
|
||||
.filter(workspace__slug=self.kwargs.get("slug"))
|
||||
.select_related("workspace", "project", "state", "parent")
|
||||
.prefetch_related("assignees", "labels", "issue_module__module")
|
||||
|
|
@ -240,9 +221,7 @@ class IssueViewSet(BaseViewSet):
|
|||
.values("count")
|
||||
)
|
||||
.annotate(
|
||||
sub_issues_count=Issue.issue_objects.filter(
|
||||
parent=OuterRef("id")
|
||||
)
|
||||
sub_issues_count=Issue.issue_objects.filter(parent=OuterRef("id"))
|
||||
.order_by()
|
||||
.annotate(count=Func(F("id"), function="Count"))
|
||||
.values("count")
|
||||
|
|
@ -254,9 +233,7 @@ class IssueViewSet(BaseViewSet):
|
|||
def list(self, request, slug, project_id):
|
||||
extra_filters = {}
|
||||
if request.GET.get("updated_at__gt", None) is not None:
|
||||
extra_filters = {
|
||||
"updated_at__gt": request.GET.get("updated_at__gt")
|
||||
}
|
||||
extra_filters = {"updated_at__gt": request.GET.get("updated_at__gt")}
|
||||
|
||||
project = Project.objects.get(pk=project_id, workspace__slug=slug)
|
||||
filters = issue_filters(request.query_params, "GET")
|
||||
|
|
@ -267,8 +244,7 @@ class IssueViewSet(BaseViewSet):
|
|||
|
||||
# Issue queryset
|
||||
issue_queryset, order_by_param = order_issue_queryset(
|
||||
issue_queryset=issue_queryset,
|
||||
order_by_param=order_by_param,
|
||||
issue_queryset=issue_queryset, order_by_param=order_by_param
|
||||
)
|
||||
|
||||
# Group by
|
||||
|
|
@ -277,9 +253,7 @@ class IssueViewSet(BaseViewSet):
|
|||
|
||||
# issue queryset
|
||||
issue_queryset = issue_queryset_grouper(
|
||||
queryset=issue_queryset,
|
||||
group_by=group_by,
|
||||
sub_group_by=sub_group_by,
|
||||
queryset=issue_queryset, group_by=group_by, sub_group_by=sub_group_by
|
||||
)
|
||||
|
||||
recent_visited_task.delay(
|
||||
|
|
@ -316,9 +290,7 @@ class IssueViewSet(BaseViewSet):
|
|||
order_by=order_by_param,
|
||||
queryset=issue_queryset,
|
||||
on_results=lambda issues: issue_on_results(
|
||||
group_by=group_by,
|
||||
issues=issues,
|
||||
sub_group_by=sub_group_by,
|
||||
group_by=group_by, issues=issues, sub_group_by=sub_group_by
|
||||
),
|
||||
paginator_cls=SubGroupedOffsetPaginator,
|
||||
group_by_fields=issue_group_values(
|
||||
|
|
@ -351,9 +323,7 @@ class IssueViewSet(BaseViewSet):
|
|||
order_by=order_by_param,
|
||||
queryset=issue_queryset,
|
||||
on_results=lambda issues: issue_on_results(
|
||||
group_by=group_by,
|
||||
issues=issues,
|
||||
sub_group_by=sub_group_by,
|
||||
group_by=group_by, issues=issues, sub_group_by=sub_group_by
|
||||
),
|
||||
paginator_cls=GroupedOffsetPaginator,
|
||||
group_by_fields=issue_group_values(
|
||||
|
|
@ -401,9 +371,7 @@ class IssueViewSet(BaseViewSet):
|
|||
# Track the issue
|
||||
issue_activity.delay(
|
||||
type="issue.activity.created",
|
||||
requested_data=json.dumps(
|
||||
self.request.data, cls=DjangoJSONEncoder
|
||||
),
|
||||
requested_data=json.dumps(self.request.data, cls=DjangoJSONEncoder),
|
||||
actor_id=str(request.user.id),
|
||||
issue_id=str(serializer.data.get("id", None)),
|
||||
project_id=str(project_id),
|
||||
|
|
@ -414,9 +382,7 @@ class IssueViewSet(BaseViewSet):
|
|||
)
|
||||
issue = (
|
||||
issue_queryset_grouper(
|
||||
queryset=self.get_queryset().filter(
|
||||
pk=serializer.data["id"]
|
||||
),
|
||||
queryset=self.get_queryset().filter(pk=serializer.data["id"]),
|
||||
group_by=None,
|
||||
sub_group_by=None,
|
||||
)
|
||||
|
|
@ -468,21 +434,13 @@ class IssueViewSet(BaseViewSet):
|
|||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
@allow_permission(
|
||||
allowed_roles=[
|
||||
ROLE.ADMIN,
|
||||
ROLE.MEMBER,
|
||||
ROLE.GUEST,
|
||||
],
|
||||
creator=True,
|
||||
model=Issue,
|
||||
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)
|
||||
|
||||
issue = (
|
||||
Issue.objects.filter(
|
||||
project_id=self.kwargs.get("project_id")
|
||||
)
|
||||
Issue.objects.filter(project_id=self.kwargs.get("project_id"))
|
||||
.filter(workspace__slug=self.kwargs.get("slug"))
|
||||
.select_related("workspace", "project", "state", "parent")
|
||||
.prefetch_related("assignees", "labels", "issue_module__module")
|
||||
|
|
@ -511,13 +469,11 @@ class IssueViewSet(BaseViewSet):
|
|||
.values("count")
|
||||
)
|
||||
.annotate(
|
||||
sub_issues_count=Issue.issue_objects.filter(
|
||||
parent=OuterRef("id")
|
||||
)
|
||||
sub_issues_count=Issue.issue_objects.filter(parent=OuterRef("id"))
|
||||
.order_by()
|
||||
.annotate(count=Func(F("id"), function="Count"))
|
||||
.values("count")
|
||||
)
|
||||
)
|
||||
.filter(pk=pk)
|
||||
.annotate(
|
||||
label_ids=Coalesce(
|
||||
|
|
@ -526,7 +482,7 @@ class IssueViewSet(BaseViewSet):
|
|||
distinct=True,
|
||||
filter=Q(
|
||||
~Q(labels__id__isnull=True)
|
||||
& Q(label_issue__deleted_at__isnull=True),
|
||||
& Q(label_issue__deleted_at__isnull=True)
|
||||
),
|
||||
),
|
||||
Value([], output_field=ArrayField(UUIDField())),
|
||||
|
|
@ -559,9 +515,7 @@ class IssueViewSet(BaseViewSet):
|
|||
.prefetch_related(
|
||||
Prefetch(
|
||||
"issue_reactions",
|
||||
queryset=IssueReaction.objects.select_related(
|
||||
"issue", "actor"
|
||||
),
|
||||
queryset=IssueReaction.objects.select_related("issue", "actor"),
|
||||
)
|
||||
)
|
||||
.prefetch_related(
|
||||
|
|
@ -632,7 +586,7 @@ class IssueViewSet(BaseViewSet):
|
|||
distinct=True,
|
||||
filter=Q(
|
||||
~Q(labels__id__isnull=True)
|
||||
& Q(label_issue__deleted_at__isnull=True),
|
||||
& Q(label_issue__deleted_at__isnull=True)
|
||||
),
|
||||
),
|
||||
Value([], output_field=ArrayField(UUIDField())),
|
||||
|
|
@ -668,8 +622,7 @@ class IssueViewSet(BaseViewSet):
|
|||
|
||||
if not issue:
|
||||
return Response(
|
||||
{"error": "Issue not found"},
|
||||
status=status.HTTP_404_NOT_FOUND,
|
||||
{"error": "Issue not found"}, status=status.HTTP_404_NOT_FOUND
|
||||
)
|
||||
|
||||
current_instance = json.dumps(
|
||||
|
|
@ -677,9 +630,7 @@ class IssueViewSet(BaseViewSet):
|
|||
)
|
||||
|
||||
requested_data = json.dumps(self.request.data, cls=DjangoJSONEncoder)
|
||||
serializer = IssueCreateSerializer(
|
||||
issue, data=request.data, partial=True
|
||||
)
|
||||
serializer = IssueCreateSerializer(issue, data=request.data, partial=True)
|
||||
if serializer.is_valid():
|
||||
serializer.save()
|
||||
issue_activity.delay(
|
||||
|
|
@ -707,9 +658,7 @@ class IssueViewSet(BaseViewSet):
|
|||
|
||||
@allow_permission([ROLE.ADMIN], creator=True, model=Issue)
|
||||
def destroy(self, request, slug, project_id, pk=None):
|
||||
issue = Issue.objects.get(
|
||||
workspace__slug=slug, project_id=project_id, pk=pk
|
||||
)
|
||||
issue = Issue.objects.get(workspace__slug=slug, project_id=project_id, pk=pk)
|
||||
|
||||
issue.delete()
|
||||
issue_activity.delay(
|
||||
|
|
@ -730,13 +679,10 @@ 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,
|
||||
user=request.user, project_id=project_id
|
||||
)
|
||||
|
||||
issue_property.filters = request.data.get(
|
||||
"filters", issue_property.filters
|
||||
)
|
||||
issue_property.filters = request.data.get("filters", issue_property.filters)
|
||||
issue_property.display_filters = request.data.get(
|
||||
"display_filters", issue_property.display_filters
|
||||
)
|
||||
|
|
@ -747,13 +693,7 @@ class IssueUserDisplayPropertyEndpoint(BaseAPIView):
|
|||
serializer = IssueUserPropertySerializer(issue_property)
|
||||
return Response(serializer.data, status=status.HTTP_201_CREATED)
|
||||
|
||||
@allow_permission(
|
||||
[
|
||||
ROLE.ADMIN,
|
||||
ROLE.MEMBER,
|
||||
ROLE.GUEST,
|
||||
]
|
||||
)
|
||||
@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
|
||||
|
|
@ -769,8 +709,7 @@ class BulkDeleteIssuesEndpoint(BaseAPIView):
|
|||
|
||||
if not len(issue_ids):
|
||||
return Response(
|
||||
{"error": "Issue IDs are required"},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
{"error": "Issue IDs are required"}, status=status.HTTP_400_BAD_REQUEST
|
||||
)
|
||||
|
||||
issues = Issue.issue_objects.filter(
|
||||
|
|
@ -794,10 +733,7 @@ class DeletedIssuesListViewSet(BaseAPIView):
|
|||
if request.GET.get("updated_at__gt", None) is not None:
|
||||
filters = {"updated_at__gt": request.GET.get("updated_at__gt")}
|
||||
deleted_issues = (
|
||||
Issue.all_objects.filter(
|
||||
workspace__slug=slug,
|
||||
project_id=project_id,
|
||||
)
|
||||
Issue.all_objects.filter(workspace__slug=slug, project_id=project_id)
|
||||
.filter(Q(archived_at__isnull=False) | Q(deleted_at__isnull=False))
|
||||
.filter(**filters)
|
||||
.values_list("id", flat=True)
|
||||
|
|
@ -816,9 +752,7 @@ class IssuePaginatedViewSet(BaseViewSet):
|
|||
)
|
||||
|
||||
return (
|
||||
issue_queryset.select_related(
|
||||
"workspace", "project", "state", "parent"
|
||||
)
|
||||
issue_queryset.select_related("workspace", "project", "state", "parent")
|
||||
.prefetch_related("assignees", "labels", "issue_module__module")
|
||||
.annotate(
|
||||
cycle_id=Subquery(
|
||||
|
|
@ -843,9 +777,7 @@ class IssuePaginatedViewSet(BaseViewSet):
|
|||
.values("count")
|
||||
)
|
||||
.annotate(
|
||||
sub_issues_count=Issue.issue_objects.filter(
|
||||
parent=OuterRef("id")
|
||||
)
|
||||
sub_issues_count=Issue.issue_objects.filter(parent=OuterRef("id"))
|
||||
.order_by()
|
||||
.annotate(count=Func(F("id"), function="Count"))
|
||||
.values("count")
|
||||
|
|
@ -935,7 +867,7 @@ class IssuePaginatedViewSet(BaseViewSet):
|
|||
distinct=True,
|
||||
filter=Q(
|
||||
~Q(labels__id__isnull=True)
|
||||
& Q(label_issue__deleted_at__isnull=True),
|
||||
& Q(label_issue__deleted_at__isnull=True)
|
||||
),
|
||||
),
|
||||
Value([], output_field=ArrayField(UUIDField())),
|
||||
|
|
@ -983,9 +915,7 @@ class IssueDetailEndpoint(BaseAPIView):
|
|||
def get(self, request, slug, project_id):
|
||||
filters = issue_filters(request.query_params, "GET")
|
||||
issue = (
|
||||
Issue.issue_objects.filter(
|
||||
workspace__slug=slug, project_id=project_id
|
||||
)
|
||||
Issue.issue_objects.filter(workspace__slug=slug, project_id=project_id)
|
||||
.select_related("workspace", "project", "state", "parent")
|
||||
.prefetch_related("assignees", "labels", "issue_module__module")
|
||||
.annotate(
|
||||
|
|
@ -1002,7 +932,7 @@ class IssueDetailEndpoint(BaseAPIView):
|
|||
distinct=True,
|
||||
filter=Q(
|
||||
~Q(labels__id__isnull=True)
|
||||
& Q(label_issue__deleted_at__isnull=True),
|
||||
& Q(label_issue__deleted_at__isnull=True)
|
||||
),
|
||||
),
|
||||
Value([], output_field=ArrayField(UUIDField())),
|
||||
|
|
@ -1048,9 +978,7 @@ class IssueDetailEndpoint(BaseAPIView):
|
|||
.values("count")
|
||||
)
|
||||
.annotate(
|
||||
sub_issues_count=Issue.issue_objects.filter(
|
||||
parent=OuterRef("id")
|
||||
)
|
||||
sub_issues_count=Issue.issue_objects.filter(parent=OuterRef("id"))
|
||||
.order_by()
|
||||
.annotate(count=Func(F("id"), function="Count"))
|
||||
.values("count")
|
||||
|
|
@ -1060,27 +988,20 @@ 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_queryset=issue, order_by_param=order_by_param
|
||||
)
|
||||
return self.paginate(
|
||||
request=request,
|
||||
order_by=order_by_param,
|
||||
queryset=(issue),
|
||||
on_results=lambda issue: IssueSerializer(
|
||||
issue,
|
||||
many=True,
|
||||
fields=self.fields,
|
||||
expand=self.expand,
|
||||
issue, many=True, fields=self.fields, expand=self.expand
|
||||
).data,
|
||||
)
|
||||
|
||||
|
||||
class IssueBulkUpdateDateEndpoint(BaseAPIView):
|
||||
|
||||
def validate_dates(
|
||||
self, current_start, current_target, new_start, new_target
|
||||
):
|
||||
def validate_dates(self, current_start, current_target, new_start, new_target):
|
||||
"""
|
||||
Validate that start date is before target date.
|
||||
"""
|
||||
|
|
@ -1093,7 +1014,6 @@ class IssueBulkUpdateDateEndpoint(BaseAPIView):
|
|||
|
||||
@allow_permission([ROLE.ADMIN, ROLE.MEMBER])
|
||||
def post(self, request, slug, project_id):
|
||||
|
||||
updates = request.data.get("updates", [])
|
||||
|
||||
issue_ids = [update["id"] for update in updates]
|
||||
|
|
@ -1118,21 +1038,15 @@ class IssueBulkUpdateDateEndpoint(BaseAPIView):
|
|||
)
|
||||
if not validate_dates:
|
||||
return Response(
|
||||
{
|
||||
"message": "Start date cannot exceed target date",
|
||||
},
|
||||
{"message": "Start date cannot exceed target date"},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
||||
if start_date:
|
||||
issue_activity.delay(
|
||||
type="issue.activity.updated",
|
||||
requested_data=json.dumps(
|
||||
{"start_date": update.get("start_date")}
|
||||
),
|
||||
current_instance=json.dumps(
|
||||
{"start_date": str(issue.start_date)}
|
||||
),
|
||||
requested_data=json.dumps({"start_date": update.get("start_date")}),
|
||||
current_instance=json.dumps({"start_date": str(issue.start_date)}),
|
||||
issue_id=str(issue_id),
|
||||
actor_id=str(request.user.id),
|
||||
project_id=str(project_id),
|
||||
|
|
@ -1159,11 +1073,8 @@ class IssueBulkUpdateDateEndpoint(BaseAPIView):
|
|||
issues_to_update.append(issue)
|
||||
|
||||
# Bulk update issues
|
||||
Issue.objects.bulk_update(
|
||||
issues_to_update, ["start_date", "target_date"]
|
||||
)
|
||||
Issue.objects.bulk_update(issues_to_update, ["start_date", "target_date"])
|
||||
|
||||
return Response(
|
||||
{"message": "Issues updated successfully"},
|
||||
status=status.HTTP_200_OK,
|
||||
{"message": "Issues updated successfully"}, status=status.HTTP_200_OK
|
||||
)
|
||||
|
|
|
|||
|
|
@ -12,18 +12,9 @@ from rest_framework import status
|
|||
|
||||
# Module imports
|
||||
from .. import BaseViewSet
|
||||
from plane.app.serializers import (
|
||||
IssueCommentSerializer,
|
||||
CommentReactionSerializer,
|
||||
)
|
||||
from plane.app.serializers import IssueCommentSerializer, CommentReactionSerializer
|
||||
from plane.app.permissions import allow_permission, ROLE
|
||||
from plane.db.models import (
|
||||
IssueComment,
|
||||
ProjectMember,
|
||||
CommentReaction,
|
||||
Project,
|
||||
Issue,
|
||||
)
|
||||
from plane.db.models import IssueComment, ProjectMember, CommentReaction, Project, Issue
|
||||
from plane.bgtasks.issue_activities_task import issue_activity
|
||||
|
||||
|
||||
|
|
@ -32,10 +23,7 @@ class IssueCommentViewSet(BaseViewSet):
|
|||
model = IssueComment
|
||||
webhook_event = "issue_comment"
|
||||
|
||||
filterset_fields = [
|
||||
"issue__id",
|
||||
"workspace__id",
|
||||
]
|
||||
filterset_fields = ["issue__id", "workspace__id"]
|
||||
|
||||
def get_queryset(self):
|
||||
return self.filter_queryset(
|
||||
|
|
@ -65,13 +53,7 @@ class IssueCommentViewSet(BaseViewSet):
|
|||
.distinct()
|
||||
)
|
||||
|
||||
@allow_permission(
|
||||
[
|
||||
ROLE.ADMIN,
|
||||
ROLE.MEMBER,
|
||||
ROLE.GUEST,
|
||||
]
|
||||
)
|
||||
@allow_permission([ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST])
|
||||
def create(self, request, slug, project_id, issue_id):
|
||||
project = Project.objects.get(pk=project_id)
|
||||
issue = Issue.objects.get(pk=issue_id)
|
||||
|
|
@ -93,15 +75,11 @@ 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,
|
||||
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
|
||||
),
|
||||
requested_data=json.dumps(serializer.data, cls=DjangoJSONEncoder),
|
||||
actor_id=str(self.request.user.id),
|
||||
issue_id=str(self.kwargs.get("issue_id")),
|
||||
project_id=str(self.kwargs.get("project_id")),
|
||||
|
|
@ -113,22 +91,14 @@ class IssueCommentViewSet(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],
|
||||
creator=True,
|
||||
model=IssueComment,
|
||||
)
|
||||
@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,
|
||||
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,
|
||||
IssueCommentSerializer(issue_comment).data, cls=DjangoJSONEncoder
|
||||
)
|
||||
serializer = IssueCommentSerializer(
|
||||
issue_comment, data=request.data, partial=True
|
||||
|
|
@ -149,19 +119,13 @@ class IssueCommentViewSet(BaseViewSet):
|
|||
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], creator=True, model=IssueComment
|
||||
)
|
||||
@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,
|
||||
workspace__slug=slug, project_id=project_id, issue_id=issue_id, pk=pk
|
||||
)
|
||||
current_instance = json.dumps(
|
||||
IssueCommentSerializer(issue_comment).data,
|
||||
cls=DjangoJSONEncoder,
|
||||
IssueCommentSerializer(issue_comment).data, cls=DjangoJSONEncoder
|
||||
)
|
||||
issue_comment.delete()
|
||||
issue_activity.delay(
|
||||
|
|
@ -198,20 +162,12 @@ class CommentReactionViewSet(BaseViewSet):
|
|||
.distinct()
|
||||
)
|
||||
|
||||
@allow_permission(
|
||||
[
|
||||
ROLE.ADMIN,
|
||||
ROLE.MEMBER,
|
||||
ROLE.GUEST,
|
||||
]
|
||||
)
|
||||
@allow_permission([ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST])
|
||||
def create(self, request, slug, project_id, comment_id):
|
||||
serializer = CommentReactionSerializer(data=request.data)
|
||||
if serializer.is_valid():
|
||||
serializer.save(
|
||||
project_id=project_id,
|
||||
actor_id=request.user.id,
|
||||
comment_id=comment_id,
|
||||
project_id=project_id, actor_id=request.user.id, comment_id=comment_id
|
||||
)
|
||||
issue_activity.delay(
|
||||
type="comment_reaction.activity.created",
|
||||
|
|
@ -227,13 +183,7 @@ class CommentReactionViewSet(BaseViewSet):
|
|||
return Response(serializer.data, status=status.HTTP_201_CREATED)
|
||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
@allow_permission(
|
||||
[
|
||||
ROLE.ADMIN,
|
||||
ROLE.MEMBER,
|
||||
ROLE.GUEST,
|
||||
]
|
||||
)
|
||||
@allow_permission([ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST])
|
||||
def destroy(self, request, slug, project_id, comment_id, reaction_code):
|
||||
comment_reaction = CommentReaction.objects.get(
|
||||
workspace__slug=slug,
|
||||
|
|
|
|||
|
|
@ -12,19 +12,14 @@ from rest_framework import status
|
|||
from .. import BaseViewSet, BaseAPIView
|
||||
from plane.app.serializers import LabelSerializer
|
||||
from plane.app.permissions import allow_permission, ProjectBasePermission, ROLE
|
||||
from plane.db.models import (
|
||||
Project,
|
||||
Label,
|
||||
)
|
||||
from plane.db.models import Project, Label
|
||||
from plane.utils.cache import invalidate_cache
|
||||
|
||||
|
||||
class LabelViewSet(BaseViewSet):
|
||||
serializer_class = LabelSerializer
|
||||
model = Label
|
||||
permission_classes = [
|
||||
ProjectBasePermission,
|
||||
]
|
||||
permission_classes = [ProjectBasePermission]
|
||||
|
||||
def get_queryset(self):
|
||||
return self.filter_queryset(
|
||||
|
|
@ -40,39 +35,27 @@ class LabelViewSet(BaseViewSet):
|
|||
.order_by("sort_order")
|
||||
)
|
||||
|
||||
@invalidate_cache(
|
||||
path="/api/workspaces/:slug/labels/", url_params=True, user=False
|
||||
)
|
||||
@invalidate_cache(path="/api/workspaces/:slug/labels/", url_params=True, user=False)
|
||||
@allow_permission([ROLE.ADMIN])
|
||||
def create(self, request, slug, project_id):
|
||||
try:
|
||||
serializer = LabelSerializer(data=request.data)
|
||||
if serializer.is_valid():
|
||||
serializer.save(project_id=project_id)
|
||||
return Response(
|
||||
serializer.data, status=status.HTTP_201_CREATED
|
||||
)
|
||||
return Response(
|
||||
serializer.errors, status=status.HTTP_400_BAD_REQUEST
|
||||
)
|
||||
return Response(serializer.data, status=status.HTTP_201_CREATED)
|
||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
except IntegrityError:
|
||||
return Response(
|
||||
{
|
||||
"error": "Label with the same name already exists in the project"
|
||||
},
|
||||
{"error": "Label with the same name already exists in the project"},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
||||
@invalidate_cache(
|
||||
path="/api/workspaces/:slug/labels/", url_params=True, user=False
|
||||
)
|
||||
@invalidate_cache(path="/api/workspaces/:slug/labels/", url_params=True, user=False)
|
||||
@allow_permission([ROLE.ADMIN])
|
||||
def partial_update(self, request, *args, **kwargs):
|
||||
return super().partial_update(request, *args, **kwargs)
|
||||
|
||||
@invalidate_cache(
|
||||
path="/api/workspaces/:slug/labels/", url_params=True, user=False
|
||||
)
|
||||
@invalidate_cache(path="/api/workspaces/:slug/labels/", url_params=True, user=False)
|
||||
@allow_permission([ROLE.ADMIN])
|
||||
def destroy(self, request, *args, **kwargs):
|
||||
return super().destroy(request, *args, **kwargs)
|
||||
|
|
|
|||
|
|
@ -18,9 +18,7 @@ from plane.bgtasks.issue_activities_task import issue_activity
|
|||
|
||||
|
||||
class IssueLinkViewSet(BaseViewSet):
|
||||
permission_classes = [
|
||||
ProjectEntityPermission,
|
||||
]
|
||||
permission_classes = [ProjectEntityPermission]
|
||||
|
||||
model = IssueLink
|
||||
serializer_class = IssueLinkSerializer
|
||||
|
|
@ -44,15 +42,10 @@ class IssueLinkViewSet(BaseViewSet):
|
|||
def create(self, request, slug, project_id, issue_id):
|
||||
serializer = IssueLinkSerializer(data=request.data)
|
||||
if serializer.is_valid():
|
||||
serializer.save(
|
||||
project_id=project_id,
|
||||
issue_id=issue_id,
|
||||
)
|
||||
serializer.save(project_id=project_id, issue_id=issue_id)
|
||||
issue_activity.delay(
|
||||
type="link.activity.created",
|
||||
requested_data=json.dumps(
|
||||
serializer.data, cls=DjangoJSONEncoder
|
||||
),
|
||||
requested_data=json.dumps(serializer.data, cls=DjangoJSONEncoder),
|
||||
actor_id=str(self.request.user.id),
|
||||
issue_id=str(self.kwargs.get("issue_id")),
|
||||
project_id=str(self.kwargs.get("project_id")),
|
||||
|
|
@ -66,19 +59,13 @@ class IssueLinkViewSet(BaseViewSet):
|
|||
|
||||
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,
|
||||
workspace__slug=slug, project_id=project_id, issue_id=issue_id, pk=pk
|
||||
)
|
||||
requested_data = json.dumps(request.data, cls=DjangoJSONEncoder)
|
||||
current_instance = json.dumps(
|
||||
IssueLinkSerializer(issue_link).data,
|
||||
cls=DjangoJSONEncoder,
|
||||
)
|
||||
serializer = IssueLinkSerializer(
|
||||
issue_link, data=request.data, partial=True
|
||||
IssueLinkSerializer(issue_link).data, cls=DjangoJSONEncoder
|
||||
)
|
||||
serializer = IssueLinkSerializer(issue_link, data=request.data, partial=True)
|
||||
if serializer.is_valid():
|
||||
serializer.save()
|
||||
issue_activity.delay(
|
||||
|
|
@ -97,14 +84,10 @@ class IssueLinkViewSet(BaseViewSet):
|
|||
|
||||
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,
|
||||
workspace__slug=slug, project_id=project_id, issue_id=issue_id, pk=pk
|
||||
)
|
||||
current_instance = json.dumps(
|
||||
IssueLinkSerializer(issue_link).data,
|
||||
cls=DjangoJSONEncoder,
|
||||
IssueLinkSerializer(issue_link).data, cls=DjangoJSONEncoder
|
||||
)
|
||||
issue_activity.delay(
|
||||
type="link.activity.deleted",
|
||||
|
|
|
|||
|
|
@ -42,9 +42,7 @@ class IssueReactionViewSet(BaseViewSet):
|
|||
serializer = IssueReactionSerializer(data=request.data)
|
||||
if serializer.is_valid():
|
||||
serializer.save(
|
||||
issue_id=issue_id,
|
||||
project_id=project_id,
|
||||
actor=request.user,
|
||||
issue_id=issue_id, project_id=project_id, actor=request.user
|
||||
)
|
||||
issue_activity.delay(
|
||||
type="issue_reaction.activity.created",
|
||||
|
|
@ -76,10 +74,7 @@ class IssueReactionViewSet(BaseViewSet):
|
|||
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),
|
||||
}
|
||||
{"reaction": str(reaction_code), "identifier": str(issue_reaction.id)}
|
||||
),
|
||||
epoch=int(timezone.now().timestamp()),
|
||||
notification=True,
|
||||
|
|
|
|||
|
|
@ -3,16 +3,7 @@ import json
|
|||
|
||||
# Django imports
|
||||
from django.utils import timezone
|
||||
from django.db.models import (
|
||||
Q,
|
||||
OuterRef,
|
||||
F,
|
||||
Func,
|
||||
UUIDField,
|
||||
Value,
|
||||
CharField,
|
||||
Subquery,
|
||||
)
|
||||
from django.db.models import Q, OuterRef, F, Func, UUIDField, Value, CharField, Subquery
|
||||
from django.core.serializers.json import DjangoJSONEncoder
|
||||
from django.db.models.functions import Coalesce
|
||||
from django.contrib.postgres.aggregates import ArrayAgg
|
||||
|
|
@ -24,10 +15,7 @@ from rest_framework import status
|
|||
|
||||
# Module imports
|
||||
from .. import BaseViewSet
|
||||
from plane.app.serializers import (
|
||||
IssueRelationSerializer,
|
||||
RelatedIssueSerializer,
|
||||
)
|
||||
from plane.app.serializers import IssueRelationSerializer, RelatedIssueSerializer
|
||||
from plane.app.permissions import ProjectEntityPermission
|
||||
from plane.db.models import (
|
||||
Project,
|
||||
|
|
@ -44,9 +32,7 @@ from plane.utils.issue_relation_mapper import get_actual_relation
|
|||
class IssueRelationViewSet(BaseViewSet):
|
||||
serializer_class = IssueRelationSerializer
|
||||
model = IssueRelation
|
||||
permission_classes = [
|
||||
ProjectEntityPermission,
|
||||
]
|
||||
permission_classes = [ProjectEntityPermission]
|
||||
|
||||
def list(self, request, slug, project_id, issue_id):
|
||||
issue_relations = (
|
||||
|
|
@ -137,9 +123,7 @@ class IssueRelationViewSet(BaseViewSet):
|
|||
.values("count")
|
||||
)
|
||||
.annotate(
|
||||
sub_issues_count=Issue.issue_objects.filter(
|
||||
parent=OuterRef("id")
|
||||
)
|
||||
sub_issues_count=Issue.issue_objects.filter(parent=OuterRef("id"))
|
||||
.order_by()
|
||||
.annotate(count=Func(F("id"), function="Count"))
|
||||
.values("count")
|
||||
|
|
@ -191,78 +175,34 @@ class IssueRelationViewSet(BaseViewSet):
|
|||
|
||||
response_data = {
|
||||
"blocking": queryset.filter(pk__in=blocking_issues)
|
||||
.annotate(
|
||||
relation_type=Value("blocking", output_field=CharField())
|
||||
)
|
||||
.annotate(relation_type=Value("blocking", output_field=CharField()))
|
||||
.values(*fields),
|
||||
"blocked_by": queryset.filter(pk__in=blocked_by_issues)
|
||||
.annotate(
|
||||
relation_type=Value("blocked_by", output_field=CharField())
|
||||
)
|
||||
.annotate(relation_type=Value("blocked_by", output_field=CharField()))
|
||||
.values(*fields),
|
||||
"duplicate": queryset.filter(pk__in=duplicate_issues)
|
||||
.annotate(
|
||||
relation_type=Value(
|
||||
"duplicate",
|
||||
output_field=CharField(),
|
||||
)
|
||||
)
|
||||
.annotate(relation_type=Value("duplicate", output_field=CharField()))
|
||||
.values(*fields)
|
||||
| queryset.filter(pk__in=duplicate_issues_related)
|
||||
.annotate(
|
||||
relation_type=Value(
|
||||
"duplicate",
|
||||
output_field=CharField(),
|
||||
)
|
||||
)
|
||||
.annotate(relation_type=Value("duplicate", output_field=CharField()))
|
||||
.values(*fields),
|
||||
"relates_to": queryset.filter(pk__in=relates_to_issues)
|
||||
.annotate(
|
||||
relation_type=Value(
|
||||
"relates_to",
|
||||
output_field=CharField(),
|
||||
)
|
||||
)
|
||||
.annotate(relation_type=Value("relates_to", output_field=CharField()))
|
||||
.values(*fields)
|
||||
| queryset.filter(pk__in=relates_to_issues_related)
|
||||
.annotate(
|
||||
relation_type=Value(
|
||||
"relates_to",
|
||||
output_field=CharField(),
|
||||
)
|
||||
)
|
||||
.annotate(relation_type=Value("relates_to", output_field=CharField()))
|
||||
.values(*fields),
|
||||
"start_after": queryset.filter(pk__in=start_after_issues)
|
||||
.annotate(
|
||||
relation_type=Value(
|
||||
"start_after",
|
||||
output_field=CharField(),
|
||||
)
|
||||
)
|
||||
.annotate(relation_type=Value("start_after", output_field=CharField()))
|
||||
.values(*fields),
|
||||
"start_before": queryset.filter(pk__in=start_before_issues)
|
||||
.annotate(
|
||||
relation_type=Value(
|
||||
"start_before",
|
||||
output_field=CharField(),
|
||||
)
|
||||
)
|
||||
.annotate(relation_type=Value("start_before", output_field=CharField()))
|
||||
.values(*fields),
|
||||
"finish_after": queryset.filter(pk__in=finish_after_issues)
|
||||
.annotate(
|
||||
relation_type=Value(
|
||||
"finish_after",
|
||||
output_field=CharField(),
|
||||
)
|
||||
)
|
||||
.annotate(relation_type=Value("finish_after", output_field=CharField()))
|
||||
.values(*fields),
|
||||
"finish_before": queryset.filter(pk__in=finish_before_issues)
|
||||
.annotate(
|
||||
relation_type=Value(
|
||||
"finish_before",
|
||||
output_field=CharField(),
|
||||
)
|
||||
)
|
||||
.annotate(relation_type=Value("finish_before", output_field=CharField()))
|
||||
.values(*fields),
|
||||
}
|
||||
|
||||
|
|
@ -284,14 +224,12 @@ class IssueRelationViewSet(BaseViewSet):
|
|||
IssueRelation(
|
||||
issue_id=(
|
||||
issue
|
||||
if relation_type
|
||||
in ["blocking", "start_after", "finish_after"]
|
||||
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"]
|
||||
if relation_type in ["blocking", "start_after", "finish_after"]
|
||||
else issue
|
||||
),
|
||||
relation_type=(get_actual_relation(relation_type)),
|
||||
|
|
@ -348,8 +286,7 @@ class IssueRelationViewSet(BaseViewSet):
|
|||
related_issue_id=related_issue,
|
||||
)
|
||||
current_instance = json.dumps(
|
||||
IssueRelationSerializer(issue_relation).data,
|
||||
cls=DjangoJSONEncoder,
|
||||
IssueRelationSerializer(issue_relation).data, cls=DjangoJSONEncoder
|
||||
)
|
||||
issue_relation.delete()
|
||||
issue_activity.delay(
|
||||
|
|
|
|||
|
|
@ -18,28 +18,19 @@ from rest_framework import status
|
|||
from .. import BaseAPIView
|
||||
from plane.app.serializers import IssueSerializer
|
||||
from plane.app.permissions import ProjectEntityPermission
|
||||
from plane.db.models import (
|
||||
Issue,
|
||||
IssueLink,
|
||||
FileAsset,
|
||||
CycleIssue,
|
||||
)
|
||||
from plane.db.models import Issue, IssueLink, FileAsset, CycleIssue
|
||||
from plane.bgtasks.issue_activities_task import issue_activity
|
||||
from plane.utils.user_timezone_converter import user_timezone_converter
|
||||
from collections import defaultdict
|
||||
|
||||
|
||||
class SubIssuesEndpoint(BaseAPIView):
|
||||
permission_classes = [
|
||||
ProjectEntityPermission,
|
||||
]
|
||||
permission_classes = [ProjectEntityPermission]
|
||||
|
||||
@method_decorator(gzip_page)
|
||||
def get(self, request, slug, project_id, issue_id):
|
||||
sub_issues = (
|
||||
Issue.issue_objects.filter(
|
||||
parent_id=issue_id, workspace__slug=slug
|
||||
)
|
||||
Issue.issue_objects.filter(parent_id=issue_id, workspace__slug=slug)
|
||||
.select_related("workspace", "project", "state", "parent")
|
||||
.prefetch_related("assignees", "labels", "issue_module__module")
|
||||
.annotate(
|
||||
|
|
@ -65,9 +56,7 @@ class SubIssuesEndpoint(BaseAPIView):
|
|||
.values("count")
|
||||
)
|
||||
.annotate(
|
||||
sub_issues_count=Issue.issue_objects.filter(
|
||||
parent=OuterRef("id")
|
||||
)
|
||||
sub_issues_count=Issue.issue_objects.filter(parent=OuterRef("id"))
|
||||
.order_by()
|
||||
.annotate(count=Func(F("id"), function="Count"))
|
||||
.values("count")
|
||||
|
|
@ -79,7 +68,7 @@ class SubIssuesEndpoint(BaseAPIView):
|
|||
distinct=True,
|
||||
filter=Q(
|
||||
~Q(labels__id__isnull=True)
|
||||
& Q(label_issue__deleted_at__isnull=True),
|
||||
& Q(label_issue__deleted_at__isnull=True)
|
||||
),
|
||||
),
|
||||
Value([], output_field=ArrayField(UUIDField())),
|
||||
|
|
@ -150,10 +139,7 @@ class SubIssuesEndpoint(BaseAPIView):
|
|||
sub_issues, datetime_fields, request.user.user_timezone
|
||||
)
|
||||
return Response(
|
||||
{
|
||||
"sub_issues": sub_issues,
|
||||
"state_distribution": result,
|
||||
},
|
||||
{"sub_issues": sub_issues, "state_distribution": result},
|
||||
status=status.HTTP_200_OK,
|
||||
)
|
||||
|
||||
|
|
@ -175,9 +161,9 @@ 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
|
||||
_ = [
|
||||
|
|
@ -200,14 +186,8 @@ class SubIssuesEndpoint(BaseAPIView):
|
|||
for sub_issue in updated_sub_issues:
|
||||
result[sub_issue.state_group].append(str(sub_issue.id))
|
||||
|
||||
serializer = IssueSerializer(
|
||||
updated_sub_issues,
|
||||
many=True,
|
||||
)
|
||||
serializer = IssueSerializer(updated_sub_issues, many=True)
|
||||
return Response(
|
||||
{
|
||||
"sub_issues": serializer.data,
|
||||
"state_distribution": result,
|
||||
},
|
||||
{"sub_issues": serializer.data, "state_distribution": result},
|
||||
status=status.HTTP_200_OK,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -4,37 +4,22 @@ from rest_framework import status
|
|||
|
||||
# Module imports
|
||||
from .. import BaseViewSet
|
||||
from plane.app.serializers import (
|
||||
IssueSubscriberSerializer,
|
||||
ProjectMemberLiteSerializer,
|
||||
)
|
||||
from plane.app.permissions import (
|
||||
ProjectEntityPermission,
|
||||
ProjectLitePermission,
|
||||
)
|
||||
from plane.db.models import (
|
||||
IssueSubscriber,
|
||||
ProjectMember,
|
||||
)
|
||||
from plane.app.serializers import IssueSubscriberSerializer, ProjectMemberLiteSerializer
|
||||
from plane.app.permissions import ProjectEntityPermission, ProjectLitePermission
|
||||
from plane.db.models import IssueSubscriber, ProjectMember
|
||||
|
||||
|
||||
class IssueSubscriberViewSet(BaseViewSet):
|
||||
serializer_class = IssueSubscriberSerializer
|
||||
model = IssueSubscriber
|
||||
|
||||
permission_classes = [
|
||||
ProjectEntityPermission,
|
||||
]
|
||||
permission_classes = [ProjectEntityPermission]
|
||||
|
||||
def get_permissions(self):
|
||||
if self.action in ["subscribe", "unsubscribe", "subscription_status"]:
|
||||
self.permission_classes = [
|
||||
ProjectLitePermission,
|
||||
]
|
||||
self.permission_classes = [ProjectLitePermission]
|
||||
else:
|
||||
self.permission_classes = [
|
||||
ProjectEntityPermission,
|
||||
]
|
||||
self.permission_classes = [ProjectEntityPermission]
|
||||
|
||||
return super(IssueSubscriberViewSet, self).get_permissions()
|
||||
|
||||
|
|
@ -62,9 +47,7 @@ class IssueSubscriberViewSet(BaseViewSet):
|
|||
|
||||
def list(self, request, slug, project_id, issue_id):
|
||||
members = ProjectMember.objects.filter(
|
||||
workspace__slug=slug,
|
||||
project_id=project_id,
|
||||
is_active=True,
|
||||
workspace__slug=slug, project_id=project_id, is_active=True
|
||||
).select_related("member")
|
||||
serializer = ProjectMemberLiteSerializer(members, many=True)
|
||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||
|
|
@ -77,9 +60,7 @@ class IssueSubscriberViewSet(BaseViewSet):
|
|||
issue=issue_id,
|
||||
)
|
||||
issue_subscriber.delete()
|
||||
return Response(
|
||||
status=status.HTTP_204_NO_CONTENT,
|
||||
)
|
||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
def subscribe(self, request, slug, project_id, issue_id):
|
||||
if IssueSubscriber.objects.filter(
|
||||
|
|
@ -94,9 +75,7 @@ class IssueSubscriberViewSet(BaseViewSet):
|
|||
)
|
||||
|
||||
subscriber = IssueSubscriber.objects.create(
|
||||
issue_id=issue_id,
|
||||
subscriber_id=request.user.id,
|
||||
project_id=project_id,
|
||||
issue_id=issue_id, subscriber_id=request.user.id, project_id=project_id
|
||||
)
|
||||
serializer = IssueSubscriberSerializer(subscriber)
|
||||
return Response(serializer.data, status=status.HTTP_201_CREATED)
|
||||
|
|
@ -109,9 +88,7 @@ class IssueSubscriberViewSet(BaseViewSet):
|
|||
issue=issue_id,
|
||||
)
|
||||
issue_subscriber.delete()
|
||||
return Response(
|
||||
status=status.HTTP_204_NO_CONTENT,
|
||||
)
|
||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
def subscription_status(self, request, slug, project_id, issue_id):
|
||||
issue_subscriber = IssueSubscriber.objects.filter(
|
||||
|
|
@ -120,6 +97,4 @@ class IssueSubscriberViewSet(BaseViewSet):
|
|||
workspace__slug=slug,
|
||||
project=project_id,
|
||||
).exists()
|
||||
return Response(
|
||||
{"subscribed": issue_subscriber}, status=status.HTTP_200_OK
|
||||
)
|
||||
return Response({"subscribed": issue_subscriber}, status=status.HTTP_200_OK)
|
||||
|
|
|
|||
|
|
@ -24,12 +24,8 @@ from django.db import models
|
|||
# Third party imports
|
||||
from rest_framework import status
|
||||
from rest_framework.response import Response
|
||||
from plane.app.permissions import (
|
||||
ProjectEntityPermission,
|
||||
)
|
||||
from plane.app.serializers import (
|
||||
ModuleDetailSerializer,
|
||||
)
|
||||
from plane.app.permissions import ProjectEntityPermission
|
||||
from plane.app.serializers import ModuleDetailSerializer
|
||||
from plane.db.models import Issue, Module, ModuleLink, UserFavorite, Project
|
||||
from plane.utils.analytics_plot import burndown_plot
|
||||
from plane.utils.user_timezone_converter import user_timezone_converter
|
||||
|
|
@ -40,10 +36,7 @@ from .. import BaseAPIView
|
|||
|
||||
|
||||
class ModuleArchiveUnarchiveEndpoint(BaseAPIView):
|
||||
|
||||
permission_classes = [
|
||||
ProjectEntityPermission,
|
||||
]
|
||||
permission_classes = [ProjectEntityPermission]
|
||||
|
||||
def get_queryset(self):
|
||||
favorite_subquery = UserFavorite.objects.filter(
|
||||
|
|
@ -136,9 +129,7 @@ class ModuleArchiveUnarchiveEndpoint(BaseAPIView):
|
|||
)
|
||||
.values("issue_module__module_id")
|
||||
.annotate(
|
||||
total_estimate_points=Sum(
|
||||
Cast("estimate_point__value", FloatField())
|
||||
)
|
||||
total_estimate_points=Sum(Cast("estimate_point__value", FloatField()))
|
||||
)
|
||||
.values("total_estimate_points")[:1]
|
||||
)
|
||||
|
|
@ -151,9 +142,7 @@ class ModuleArchiveUnarchiveEndpoint(BaseAPIView):
|
|||
)
|
||||
.values("issue_module__module_id")
|
||||
.annotate(
|
||||
backlog_estimate_point=Sum(
|
||||
Cast("estimate_point__value", FloatField())
|
||||
)
|
||||
backlog_estimate_point=Sum(Cast("estimate_point__value", FloatField()))
|
||||
)
|
||||
.values("backlog_estimate_point")[:1]
|
||||
)
|
||||
|
|
@ -181,9 +170,7 @@ class ModuleArchiveUnarchiveEndpoint(BaseAPIView):
|
|||
)
|
||||
.values("issue_module__module_id")
|
||||
.annotate(
|
||||
started_estimate_point=Sum(
|
||||
Cast("estimate_point__value", FloatField())
|
||||
)
|
||||
started_estimate_point=Sum(Cast("estimate_point__value", FloatField()))
|
||||
)
|
||||
.values("started_estimate_point")[:1]
|
||||
)
|
||||
|
|
@ -212,9 +199,7 @@ class ModuleArchiveUnarchiveEndpoint(BaseAPIView):
|
|||
.prefetch_related(
|
||||
Prefetch(
|
||||
"link_module",
|
||||
queryset=ModuleLink.objects.select_related(
|
||||
"module", "created_by"
|
||||
),
|
||||
queryset=ModuleLink.objects.select_related("module", "created_by"),
|
||||
)
|
||||
)
|
||||
.annotate(
|
||||
|
|
@ -231,8 +216,7 @@ class ModuleArchiveUnarchiveEndpoint(BaseAPIView):
|
|||
)
|
||||
.annotate(
|
||||
started_issues=Coalesce(
|
||||
Subquery(started_issues[:1]),
|
||||
Value(0, output_field=IntegerField()),
|
||||
Subquery(started_issues[:1]), Value(0, output_field=IntegerField())
|
||||
)
|
||||
)
|
||||
.annotate(
|
||||
|
|
@ -243,51 +227,48 @@ class ModuleArchiveUnarchiveEndpoint(BaseAPIView):
|
|||
)
|
||||
.annotate(
|
||||
backlog_issues=Coalesce(
|
||||
Subquery(backlog_issues[:1]),
|
||||
Value(0, output_field=IntegerField()),
|
||||
Subquery(backlog_issues[:1]), Value(0, output_field=IntegerField())
|
||||
)
|
||||
)
|
||||
.annotate(
|
||||
total_issues=Coalesce(
|
||||
Subquery(total_issues[:1]),
|
||||
Value(0, output_field=IntegerField()),
|
||||
Subquery(total_issues[:1]), Value(0, output_field=IntegerField())
|
||||
)
|
||||
)
|
||||
.annotate(
|
||||
backlog_estimate_points=Coalesce(
|
||||
Subquery(backlog_estimate_point),
|
||||
Value(0, output_field=FloatField()),
|
||||
),
|
||||
)
|
||||
)
|
||||
.annotate(
|
||||
unstarted_estimate_points=Coalesce(
|
||||
Subquery(unstarted_estimate_point),
|
||||
Value(0, output_field=FloatField()),
|
||||
),
|
||||
)
|
||||
)
|
||||
.annotate(
|
||||
started_estimate_points=Coalesce(
|
||||
Subquery(started_estimate_point),
|
||||
Value(0, output_field=FloatField()),
|
||||
),
|
||||
)
|
||||
)
|
||||
.annotate(
|
||||
cancelled_estimate_points=Coalesce(
|
||||
Subquery(cancelled_estimate_point),
|
||||
Value(0, output_field=FloatField()),
|
||||
),
|
||||
)
|
||||
)
|
||||
.annotate(
|
||||
completed_estimate_points=Coalesce(
|
||||
Subquery(completed_estimate_point),
|
||||
Value(0, output_field=FloatField()),
|
||||
),
|
||||
)
|
||||
)
|
||||
.annotate(
|
||||
total_estimate_points=Coalesce(
|
||||
Subquery(total_estimate_point),
|
||||
Value(0, output_field=FloatField()),
|
||||
),
|
||||
Subquery(total_estimate_point), Value(0, output_field=FloatField())
|
||||
)
|
||||
)
|
||||
.annotate(
|
||||
member_ids=Coalesce(
|
||||
|
|
@ -409,9 +390,7 @@ class ModuleArchiveUnarchiveEndpoint(BaseAPIView):
|
|||
"display_name",
|
||||
)
|
||||
.annotate(
|
||||
total_estimates=Sum(
|
||||
Cast("estimate_point__value", FloatField())
|
||||
),
|
||||
total_estimates=Sum(Cast("estimate_point__value", FloatField()))
|
||||
)
|
||||
.annotate(
|
||||
completed_estimates=Sum(
|
||||
|
|
@ -447,9 +426,7 @@ class ModuleArchiveUnarchiveEndpoint(BaseAPIView):
|
|||
.annotate(label_id=F("labels__id"))
|
||||
.values("label_name", "color", "label_id")
|
||||
.annotate(
|
||||
total_estimates=Sum(
|
||||
Cast("estimate_point__value", FloatField())
|
||||
),
|
||||
total_estimates=Sum(Cast("estimate_point__value", FloatField()))
|
||||
)
|
||||
.annotate(
|
||||
completed_estimates=Sum(
|
||||
|
|
@ -473,20 +450,16 @@ class ModuleArchiveUnarchiveEndpoint(BaseAPIView):
|
|||
)
|
||||
.order_by("label_name")
|
||||
)
|
||||
data["estimate_distribution"][
|
||||
"assignees"
|
||||
] = assignee_distribution
|
||||
data["estimate_distribution"]["assignees"] = assignee_distribution
|
||||
data["estimate_distribution"]["labels"] = label_distribution
|
||||
|
||||
if modules and modules.start_date and modules.target_date:
|
||||
data["estimate_distribution"]["completion_chart"] = (
|
||||
burndown_plot(
|
||||
queryset=modules,
|
||||
slug=slug,
|
||||
project_id=project_id,
|
||||
plot_type="points",
|
||||
module_id=pk,
|
||||
)
|
||||
data["estimate_distribution"]["completion_chart"] = burndown_plot(
|
||||
queryset=modules,
|
||||
slug=slug,
|
||||
project_id=project_id,
|
||||
plot_type="points",
|
||||
module_id=pk,
|
||||
)
|
||||
|
||||
assignee_distribution = (
|
||||
|
|
@ -529,12 +502,8 @@ class ModuleArchiveUnarchiveEndpoint(BaseAPIView):
|
|||
)
|
||||
.annotate(
|
||||
total_issues=Count(
|
||||
"id",
|
||||
filter=Q(
|
||||
archived_at__isnull=True,
|
||||
is_draft=False,
|
||||
),
|
||||
),
|
||||
"id", filter=Q(archived_at__isnull=True, is_draft=False)
|
||||
)
|
||||
)
|
||||
.annotate(
|
||||
completed_issues=Count(
|
||||
|
|
@ -572,12 +541,8 @@ class ModuleArchiveUnarchiveEndpoint(BaseAPIView):
|
|||
.values("label_name", "color", "label_id")
|
||||
.annotate(
|
||||
total_issues=Count(
|
||||
"id",
|
||||
filter=Q(
|
||||
archived_at__isnull=True,
|
||||
is_draft=False,
|
||||
),
|
||||
),
|
||||
"id", filter=Q(archived_at__isnull=True, is_draft=False)
|
||||
)
|
||||
)
|
||||
.annotate(
|
||||
completed_issues=Count(
|
||||
|
|
@ -616,10 +581,7 @@ class ModuleArchiveUnarchiveEndpoint(BaseAPIView):
|
|||
module_id=pk,
|
||||
)
|
||||
|
||||
return Response(
|
||||
data,
|
||||
status=status.HTTP_200_OK,
|
||||
)
|
||||
return Response(data, status=status.HTTP_200_OK)
|
||||
|
||||
def post(self, request, slug, project_id, module_id):
|
||||
module = Module.objects.get(
|
||||
|
|
@ -627,9 +589,7 @@ class ModuleArchiveUnarchiveEndpoint(BaseAPIView):
|
|||
)
|
||||
if module.status not in ["completed", "cancelled"]:
|
||||
return Response(
|
||||
{
|
||||
"error": "Only completed or cancelled modules can be archived"
|
||||
},
|
||||
{"error": "Only completed or cancelled modules can be archived"},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
module.archived_at = timezone.now()
|
||||
|
|
@ -641,8 +601,7 @@ class ModuleArchiveUnarchiveEndpoint(BaseAPIView):
|
|||
workspace__slug=slug,
|
||||
).delete()
|
||||
return Response(
|
||||
{"archived_at": str(module.archived_at)},
|
||||
status=status.HTTP_200_OK,
|
||||
{"archived_at": str(module.archived_at)}, status=status.HTTP_200_OK
|
||||
)
|
||||
|
||||
def delete(self, request, slug, project_id, module_id):
|
||||
|
|
|
|||
|
|
@ -164,9 +164,7 @@ class ModuleViewSet(BaseViewSet):
|
|||
)
|
||||
.values("issue_module__module_id")
|
||||
.annotate(
|
||||
total_estimate_points=Sum(
|
||||
Cast("estimate_point__value", FloatField())
|
||||
)
|
||||
total_estimate_points=Sum(Cast("estimate_point__value", FloatField()))
|
||||
)
|
||||
.values("total_estimate_points")[:1]
|
||||
)
|
||||
|
|
@ -179,9 +177,7 @@ class ModuleViewSet(BaseViewSet):
|
|||
)
|
||||
.values("issue_module__module_id")
|
||||
.annotate(
|
||||
backlog_estimate_point=Sum(
|
||||
Cast("estimate_point__value", FloatField())
|
||||
)
|
||||
backlog_estimate_point=Sum(Cast("estimate_point__value", FloatField()))
|
||||
)
|
||||
.values("backlog_estimate_point")[:1]
|
||||
)
|
||||
|
|
@ -209,9 +205,7 @@ class ModuleViewSet(BaseViewSet):
|
|||
)
|
||||
.values("issue_module__module_id")
|
||||
.annotate(
|
||||
started_estimate_point=Sum(
|
||||
Cast("estimate_point__value", FloatField())
|
||||
)
|
||||
started_estimate_point=Sum(Cast("estimate_point__value", FloatField()))
|
||||
)
|
||||
.values("started_estimate_point")[:1]
|
||||
)
|
||||
|
|
@ -243,9 +237,7 @@ class ModuleViewSet(BaseViewSet):
|
|||
.prefetch_related(
|
||||
Prefetch(
|
||||
"link_module",
|
||||
queryset=ModuleLink.objects.select_related(
|
||||
"module", "created_by"
|
||||
),
|
||||
queryset=ModuleLink.objects.select_related("module", "created_by"),
|
||||
)
|
||||
)
|
||||
.annotate(
|
||||
|
|
@ -262,8 +254,7 @@ class ModuleViewSet(BaseViewSet):
|
|||
)
|
||||
.annotate(
|
||||
started_issues=Coalesce(
|
||||
Subquery(started_issues[:1]),
|
||||
Value(0, output_field=IntegerField()),
|
||||
Subquery(started_issues[:1]), Value(0, output_field=IntegerField())
|
||||
)
|
||||
)
|
||||
.annotate(
|
||||
|
|
@ -274,51 +265,48 @@ class ModuleViewSet(BaseViewSet):
|
|||
)
|
||||
.annotate(
|
||||
backlog_issues=Coalesce(
|
||||
Subquery(backlog_issues[:1]),
|
||||
Value(0, output_field=IntegerField()),
|
||||
Subquery(backlog_issues[:1]), Value(0, output_field=IntegerField())
|
||||
)
|
||||
)
|
||||
.annotate(
|
||||
total_issues=Coalesce(
|
||||
Subquery(total_issues[:1]),
|
||||
Value(0, output_field=IntegerField()),
|
||||
Subquery(total_issues[:1]), Value(0, output_field=IntegerField())
|
||||
)
|
||||
)
|
||||
.annotate(
|
||||
backlog_estimate_points=Coalesce(
|
||||
Subquery(backlog_estimate_point),
|
||||
Value(0, output_field=FloatField()),
|
||||
),
|
||||
)
|
||||
)
|
||||
.annotate(
|
||||
unstarted_estimate_points=Coalesce(
|
||||
Subquery(unstarted_estimate_point),
|
||||
Value(0, output_field=FloatField()),
|
||||
),
|
||||
)
|
||||
)
|
||||
.annotate(
|
||||
started_estimate_points=Coalesce(
|
||||
Subquery(started_estimate_point),
|
||||
Value(0, output_field=FloatField()),
|
||||
),
|
||||
)
|
||||
)
|
||||
.annotate(
|
||||
cancelled_estimate_points=Coalesce(
|
||||
Subquery(cancelled_estimate_point),
|
||||
Value(0, output_field=FloatField()),
|
||||
),
|
||||
)
|
||||
)
|
||||
.annotate(
|
||||
completed_estimate_points=Coalesce(
|
||||
Subquery(completed_estimate_point),
|
||||
Value(0, output_field=FloatField()),
|
||||
),
|
||||
)
|
||||
)
|
||||
.annotate(
|
||||
total_estimate_points=Coalesce(
|
||||
Subquery(total_estimate_point),
|
||||
Value(0, output_field=FloatField()),
|
||||
),
|
||||
Subquery(total_estimate_point), Value(0, output_field=FloatField())
|
||||
)
|
||||
)
|
||||
.annotate(
|
||||
member_ids=Coalesce(
|
||||
|
|
@ -333,12 +321,7 @@ class ModuleViewSet(BaseViewSet):
|
|||
.order_by("-is_favorite", "-created_at")
|
||||
)
|
||||
|
||||
@allow_permission(
|
||||
[
|
||||
ROLE.ADMIN,
|
||||
ROLE.MEMBER,
|
||||
]
|
||||
)
|
||||
@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(
|
||||
|
|
@ -405,11 +388,7 @@ class ModuleViewSet(BaseViewSet):
|
|||
def list(self, request, slug, project_id):
|
||||
queryset = self.get_queryset().filter(archived_at__isnull=True)
|
||||
if self.fields:
|
||||
modules = ModuleSerializer(
|
||||
queryset,
|
||||
many=True,
|
||||
fields=self.fields,
|
||||
).data
|
||||
modules = ModuleSerializer(queryset, many=True, fields=self.fields).data
|
||||
else:
|
||||
modules = queryset.values( # Required fields
|
||||
"id",
|
||||
|
|
@ -470,8 +449,7 @@ class ModuleViewSet(BaseViewSet):
|
|||
|
||||
if not queryset.exists():
|
||||
return Response(
|
||||
{"error": "Module not found"},
|
||||
status=status.HTTP_404_NOT_FOUND,
|
||||
{"error": "Module not found"}, status=status.HTTP_404_NOT_FOUND
|
||||
)
|
||||
|
||||
estimate_type = Project.objects.filter(
|
||||
|
|
@ -526,9 +504,7 @@ class ModuleViewSet(BaseViewSet):
|
|||
"display_name",
|
||||
)
|
||||
.annotate(
|
||||
total_estimates=Sum(
|
||||
Cast("estimate_point__value", FloatField())
|
||||
),
|
||||
total_estimates=Sum(Cast("estimate_point__value", FloatField()))
|
||||
)
|
||||
.annotate(
|
||||
completed_estimates=Sum(
|
||||
|
|
@ -565,9 +541,7 @@ class ModuleViewSet(BaseViewSet):
|
|||
.annotate(label_id=F("labels__id"))
|
||||
.values("label_name", "color", "label_id")
|
||||
.annotate(
|
||||
total_estimates=Sum(
|
||||
Cast("estimate_point__value", FloatField())
|
||||
),
|
||||
total_estimates=Sum(Cast("estimate_point__value", FloatField()))
|
||||
)
|
||||
.annotate(
|
||||
completed_estimates=Sum(
|
||||
|
|
@ -595,14 +569,12 @@ class ModuleViewSet(BaseViewSet):
|
|||
data["estimate_distribution"]["labels"] = label_distribution
|
||||
|
||||
if modules and modules.start_date and modules.target_date:
|
||||
data["estimate_distribution"]["completion_chart"] = (
|
||||
burndown_plot(
|
||||
queryset=modules,
|
||||
slug=slug,
|
||||
project_id=project_id,
|
||||
plot_type="points",
|
||||
module_id=pk,
|
||||
)
|
||||
data["estimate_distribution"]["completion_chart"] = burndown_plot(
|
||||
queryset=modules,
|
||||
slug=slug,
|
||||
project_id=project_id,
|
||||
plot_type="points",
|
||||
module_id=pk,
|
||||
)
|
||||
|
||||
assignee_distribution = (
|
||||
|
|
@ -629,28 +601,19 @@ class ModuleViewSet(BaseViewSet):
|
|||
),
|
||||
# If `avatar_asset` is None, fall back to using `avatar` field directly
|
||||
When(
|
||||
assignees__avatar_asset__isnull=True,
|
||||
then="assignees__avatar",
|
||||
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",
|
||||
"first_name", "last_name", "assignee_id", "avatar_url", "display_name"
|
||||
)
|
||||
.annotate(
|
||||
total_issues=Count(
|
||||
"id",
|
||||
filter=Q(
|
||||
archived_at__isnull=True,
|
||||
is_draft=False,
|
||||
),
|
||||
),
|
||||
"id", filter=Q(archived_at__isnull=True, is_draft=False)
|
||||
)
|
||||
)
|
||||
.annotate(
|
||||
completed_issues=Count(
|
||||
|
|
@ -688,12 +651,8 @@ class ModuleViewSet(BaseViewSet):
|
|||
.values("label_name", "color", "label_id")
|
||||
.annotate(
|
||||
total_issues=Count(
|
||||
"id",
|
||||
filter=Q(
|
||||
archived_at__isnull=True,
|
||||
is_draft=False,
|
||||
),
|
||||
),
|
||||
"id", filter=Q(archived_at__isnull=True, is_draft=False)
|
||||
)
|
||||
)
|
||||
.annotate(
|
||||
completed_issues=Count(
|
||||
|
|
@ -746,10 +705,7 @@ class ModuleViewSet(BaseViewSet):
|
|||
project_id=project_id,
|
||||
)
|
||||
|
||||
return Response(
|
||||
data,
|
||||
status=status.HTTP_200_OK,
|
||||
)
|
||||
return Response(data, status=status.HTTP_200_OK)
|
||||
|
||||
@allow_permission([ROLE.ADMIN, ROLE.MEMBER])
|
||||
def partial_update(self, request, slug, project_id, pk):
|
||||
|
|
@ -823,14 +779,10 @@ class ModuleViewSet(BaseViewSet):
|
|||
|
||||
@allow_permission([ROLE.ADMIN], creator=True, model=Module)
|
||||
def destroy(self, request, slug, project_id, pk):
|
||||
module = Module.objects.get(
|
||||
workspace__slug=slug, project_id=project_id, pk=pk
|
||||
)
|
||||
module = Module.objects.get(workspace__slug=slug, project_id=project_id, pk=pk)
|
||||
|
||||
module_issues = list(
|
||||
ModuleIssue.objects.filter(module_id=pk).values_list(
|
||||
"issue", flat=True
|
||||
)
|
||||
ModuleIssue.objects.filter(module_id=pk).values_list("issue", flat=True)
|
||||
)
|
||||
_ = [
|
||||
issue_activity.delay(
|
||||
|
|
@ -848,10 +800,7 @@ class ModuleViewSet(BaseViewSet):
|
|||
]
|
||||
module.delete()
|
||||
# Delete the module issues
|
||||
ModuleIssue.objects.filter(
|
||||
module=pk,
|
||||
project_id=project_id,
|
||||
).delete()
|
||||
ModuleIssue.objects.filter(module=pk, project_id=project_id).delete()
|
||||
# Delete the user favorite module
|
||||
UserFavorite.objects.filter(
|
||||
user=request.user,
|
||||
|
|
@ -863,9 +812,7 @@ class ModuleViewSet(BaseViewSet):
|
|||
|
||||
|
||||
class ModuleLinkViewSet(BaseViewSet):
|
||||
permission_classes = [
|
||||
ProjectEntityPermission,
|
||||
]
|
||||
permission_classes = [ProjectEntityPermission]
|
||||
|
||||
model = ModuleLink
|
||||
serializer_class = ModuleLinkSerializer
|
||||
|
|
@ -895,9 +842,7 @@ class ModuleLinkViewSet(BaseViewSet):
|
|||
|
||||
class ModuleFavoriteViewSet(BaseViewSet):
|
||||
model = UserFavorite
|
||||
permission_classes = [
|
||||
ProjectLitePermission,
|
||||
]
|
||||
permission_classes = [ProjectLitePermission]
|
||||
|
||||
def get_queryset(self):
|
||||
return self.filter_queryset(
|
||||
|
|
@ -930,7 +875,6 @@ class ModuleFavoriteViewSet(BaseViewSet):
|
|||
|
||||
|
||||
class ModuleUserPropertiesEndpoint(BaseAPIView):
|
||||
|
||||
@allow_permission([ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST])
|
||||
def patch(self, request, slug, project_id, module_id):
|
||||
module_properties = ModuleUserProperties.objects.get(
|
||||
|
|
|
|||
|
|
@ -13,9 +13,7 @@ from rest_framework import status
|
|||
from rest_framework.response import Response
|
||||
|
||||
from plane.app.permissions import allow_permission, ROLE
|
||||
from plane.app.serializers import (
|
||||
ModuleIssueSerializer,
|
||||
)
|
||||
from plane.app.serializers import ModuleIssueSerializer
|
||||
from plane.bgtasks.issue_activities_task import issue_activity
|
||||
from plane.db.models import (
|
||||
Issue,
|
||||
|
|
@ -32,10 +30,7 @@ from plane.utils.grouper import (
|
|||
)
|
||||
from plane.utils.issue_filters import issue_filters
|
||||
from plane.utils.order_queryset import order_issue_queryset
|
||||
from plane.utils.paginator import (
|
||||
GroupedOffsetPaginator,
|
||||
SubGroupedOffsetPaginator,
|
||||
)
|
||||
from plane.utils.paginator import GroupedOffsetPaginator, SubGroupedOffsetPaginator
|
||||
|
||||
# Module imports
|
||||
from .. import BaseViewSet
|
||||
|
|
@ -47,10 +42,7 @@ class ModuleIssueViewSet(BaseViewSet):
|
|||
webhook_event = "module_issue"
|
||||
bulk = True
|
||||
|
||||
filterset_fields = [
|
||||
"issue__labels__id",
|
||||
"issue__assignees__id",
|
||||
]
|
||||
filterset_fields = ["issue__labels__id", "issue__assignees__id"]
|
||||
|
||||
def get_queryset(self):
|
||||
return (
|
||||
|
|
@ -85,9 +77,7 @@ class ModuleIssueViewSet(BaseViewSet):
|
|||
.values("count")
|
||||
)
|
||||
.annotate(
|
||||
sub_issues_count=Issue.issue_objects.filter(
|
||||
parent=OuterRef("id")
|
||||
)
|
||||
sub_issues_count=Issue.issue_objects.filter(parent=OuterRef("id"))
|
||||
.order_by()
|
||||
.annotate(count=Func(F("id"), function="Count"))
|
||||
.values("count")
|
||||
|
|
@ -95,12 +85,7 @@ class ModuleIssueViewSet(BaseViewSet):
|
|||
).distinct()
|
||||
|
||||
@method_decorator(gzip_page)
|
||||
@allow_permission(
|
||||
[
|
||||
ROLE.ADMIN,
|
||||
ROLE.MEMBER,
|
||||
]
|
||||
)
|
||||
@allow_permission([ROLE.ADMIN, ROLE.MEMBER])
|
||||
def list(self, request, slug, project_id, module_id):
|
||||
filters = issue_filters(request.query_params, "GET")
|
||||
issue_queryset = self.get_queryset().filter(**filters)
|
||||
|
|
@ -108,8 +93,7 @@ class ModuleIssueViewSet(BaseViewSet):
|
|||
|
||||
# Issue queryset
|
||||
issue_queryset, order_by_param = order_issue_queryset(
|
||||
issue_queryset=issue_queryset,
|
||||
order_by_param=order_by_param,
|
||||
issue_queryset=issue_queryset, order_by_param=order_by_param
|
||||
)
|
||||
|
||||
# Group by
|
||||
|
|
@ -118,9 +102,7 @@ class ModuleIssueViewSet(BaseViewSet):
|
|||
|
||||
# issue queryset
|
||||
issue_queryset = issue_queryset_grouper(
|
||||
queryset=issue_queryset,
|
||||
group_by=group_by,
|
||||
sub_group_by=sub_group_by,
|
||||
queryset=issue_queryset, group_by=group_by, sub_group_by=sub_group_by
|
||||
)
|
||||
|
||||
if group_by:
|
||||
|
|
@ -140,9 +122,7 @@ class ModuleIssueViewSet(BaseViewSet):
|
|||
order_by=order_by_param,
|
||||
queryset=issue_queryset,
|
||||
on_results=lambda issues: issue_on_results(
|
||||
group_by=group_by,
|
||||
issues=issues,
|
||||
sub_group_by=sub_group_by,
|
||||
group_by=group_by, issues=issues, sub_group_by=sub_group_by
|
||||
),
|
||||
paginator_cls=SubGroupedOffsetPaginator,
|
||||
group_by_fields=issue_group_values(
|
||||
|
|
@ -176,9 +156,7 @@ class ModuleIssueViewSet(BaseViewSet):
|
|||
order_by=order_by_param,
|
||||
queryset=issue_queryset,
|
||||
on_results=lambda issues: issue_on_results(
|
||||
group_by=group_by,
|
||||
issues=issues,
|
||||
sub_group_by=sub_group_by,
|
||||
group_by=group_by, issues=issues, sub_group_by=sub_group_by
|
||||
),
|
||||
paginator_cls=GroupedOffsetPaginator,
|
||||
group_by_fields=issue_group_values(
|
||||
|
|
@ -214,8 +192,7 @@ class ModuleIssueViewSet(BaseViewSet):
|
|||
issues = request.data.get("issues", [])
|
||||
if not issues:
|
||||
return Response(
|
||||
{"error": "Issues are required"},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
{"error": "Issues are required"}, status=status.HTTP_400_BAD_REQUEST
|
||||
)
|
||||
project = Project.objects.get(pk=project_id)
|
||||
_ = ModuleIssue.objects.bulk_create(
|
||||
|
|
|
|||
|
|
@ -41,8 +41,7 @@ class NotificationViewSet(BaseViewSet, BasePaginator):
|
|||
)
|
||||
|
||||
@allow_permission(
|
||||
allowed_roles=[ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST],
|
||||
level="WORKSPACE",
|
||||
allowed_roles=[ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST], level="WORKSPACE"
|
||||
)
|
||||
def list(self, request, slug):
|
||||
# Get query parameters
|
||||
|
|
@ -79,10 +78,8 @@ class NotificationViewSet(BaseViewSet, BasePaginator):
|
|||
|
||||
# Filters based on query parameters
|
||||
snoozed_filters = {
|
||||
"true": Q(snoozed_till__lt=timezone.now())
|
||||
| Q(snoozed_till__isnull=False),
|
||||
"false": Q(snoozed_till__gte=timezone.now())
|
||||
| Q(snoozed_till__isnull=True),
|
||||
"true": Q(snoozed_till__lt=timezone.now()) | Q(snoozed_till__isnull=False),
|
||||
"false": Q(snoozed_till__gte=timezone.now()) | Q(snoozed_till__isnull=True),
|
||||
}
|
||||
|
||||
notifications = notifications.filter(snoozed_filters[snoozed])
|
||||
|
|
@ -103,9 +100,7 @@ class NotificationViewSet(BaseViewSet, BasePaginator):
|
|||
if mentioned:
|
||||
notifications = notifications.filter(sender__icontains="mentioned")
|
||||
else:
|
||||
notifications = notifications.exclude(
|
||||
sender__icontains="mentioned"
|
||||
)
|
||||
notifications = notifications.exclude(sender__icontains="mentioned")
|
||||
|
||||
type = type.split(",")
|
||||
# Subscribed issues
|
||||
|
|
@ -143,10 +138,7 @@ class NotificationViewSet(BaseViewSet, BasePaginator):
|
|||
# Created issues
|
||||
if "created" in type:
|
||||
if WorkspaceMember.objects.filter(
|
||||
workspace__slug=slug,
|
||||
member=request.user,
|
||||
role__lt=15,
|
||||
is_active=True,
|
||||
workspace__slug=slug, member=request.user, role__lt=15, is_active=True
|
||||
).exists():
|
||||
notifications = notifications.none()
|
||||
else:
|
||||
|
|
@ -159,9 +151,7 @@ class NotificationViewSet(BaseViewSet, BasePaginator):
|
|||
notifications = notifications.filter(q_filters)
|
||||
|
||||
# Pagination
|
||||
if request.GET.get("per_page", False) and request.GET.get(
|
||||
"cursor", False
|
||||
):
|
||||
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,
|
||||
|
|
@ -175,17 +165,14 @@ class NotificationViewSet(BaseViewSet, BasePaginator):
|
|||
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||
|
||||
@allow_permission(
|
||||
allowed_roles=[ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST],
|
||||
level="WORKSPACE",
|
||||
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
|
||||
)
|
||||
# Only read_at and snoozed_till can be updated
|
||||
notification_data = {
|
||||
"snoozed_till": request.data.get("snoozed_till", None),
|
||||
}
|
||||
notification_data = {"snoozed_till": request.data.get("snoozed_till", None)}
|
||||
serializer = NotificationSerializer(
|
||||
notification, data=notification_data, partial=True
|
||||
)
|
||||
|
|
@ -246,8 +233,7 @@ class NotificationViewSet(BaseViewSet, BasePaginator):
|
|||
|
||||
class UnreadNotificationEndpoint(BaseAPIView):
|
||||
@allow_permission(
|
||||
allowed_roles=[ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST],
|
||||
level="WORKSPACE",
|
||||
allowed_roles=[ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST], level="WORKSPACE"
|
||||
)
|
||||
def get(self, request, slug):
|
||||
# Watching Issues Count
|
||||
|
|
@ -274,12 +260,8 @@ class UnreadNotificationEndpoint(BaseAPIView):
|
|||
|
||||
return Response(
|
||||
{
|
||||
"total_unread_notifications_count": int(
|
||||
unread_notifications_count
|
||||
),
|
||||
"mention_unread_notifications_count": int(
|
||||
mention_notifications_count
|
||||
),
|
||||
"total_unread_notifications_count": int(unread_notifications_count),
|
||||
"mention_unread_notifications_count": int(mention_notifications_count),
|
||||
},
|
||||
status=status.HTTP_200_OK,
|
||||
)
|
||||
|
|
@ -296,9 +278,7 @@ class MarkAllReadNotificationViewSet(BaseViewSet):
|
|||
|
||||
notifications = (
|
||||
Notification.objects.filter(
|
||||
workspace__slug=slug,
|
||||
receiver_id=request.user.id,
|
||||
read_at__isnull=True,
|
||||
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")
|
||||
|
|
@ -307,13 +287,11 @@ class MarkAllReadNotificationViewSet(BaseViewSet):
|
|||
# Filter for snoozed notifications
|
||||
if snoozed:
|
||||
notifications = notifications.filter(
|
||||
Q(snoozed_till__lt=timezone.now())
|
||||
| Q(snoozed_till__isnull=False)
|
||||
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),
|
||||
Q(snoozed_till__gte=timezone.now()) | Q(snoozed_till__isnull=True)
|
||||
)
|
||||
|
||||
# Filter for archived or unarchive
|
||||
|
|
@ -327,35 +305,26 @@ class MarkAllReadNotificationViewSet(BaseViewSet):
|
|||
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
|
||||
)
|
||||
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)
|
||||
notifications = notifications.filter(
|
||||
entity_identifier__in=issue_ids
|
||||
)
|
||||
notifications = notifications.filter(entity_identifier__in=issue_ids)
|
||||
|
||||
# Created issues
|
||||
if type == "created":
|
||||
if WorkspaceMember.objects.filter(
|
||||
workspace__slug=slug,
|
||||
member=request.user,
|
||||
role__lt=15,
|
||||
is_active=True,
|
||||
workspace__slug=slug, member=request.user, role__lt=15, is_active=True
|
||||
).exists():
|
||||
notifications = Notification.objects.none()
|
||||
else:
|
||||
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
|
||||
)
|
||||
notifications = notifications.filter(entity_identifier__in=issue_ids)
|
||||
|
||||
updated_notifications = []
|
||||
for notification in notifications:
|
||||
|
|
@ -376,9 +345,7 @@ class UserNotificationPreferenceEndpoint(BaseAPIView):
|
|||
user_notification_preference = UserNotificationPreference.objects.get(
|
||||
user=request.user
|
||||
)
|
||||
serializer = UserNotificationPreferenceSerializer(
|
||||
user_notification_preference
|
||||
)
|
||||
serializer = UserNotificationPreferenceSerializer(user_notification_preference)
|
||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||
|
||||
# update the object
|
||||
|
|
|
|||
|
|
@ -60,9 +60,7 @@ def unarchive_archive_page_and_descendants(page_id, archived_at):
|
|||
class PageViewSet(BaseViewSet):
|
||||
serializer_class = PageSerializer
|
||||
model = Page
|
||||
search_fields = [
|
||||
"name",
|
||||
]
|
||||
search_fields = ["name"]
|
||||
|
||||
def get_queryset(self):
|
||||
subquery = UserFavorite.objects.filter(
|
||||
|
|
@ -92,8 +90,7 @@ class PageViewSet(BaseViewSet):
|
|||
.annotate(
|
||||
project=Exists(
|
||||
ProjectPage.objects.filter(
|
||||
page_id=OuterRef("id"),
|
||||
project_id=self.kwargs.get("project_id"),
|
||||
page_id=OuterRef("id"), project_id=self.kwargs.get("project_id")
|
||||
)
|
||||
)
|
||||
)
|
||||
|
|
@ -108,9 +105,7 @@ class PageViewSet(BaseViewSet):
|
|||
),
|
||||
project_ids=Coalesce(
|
||||
ArrayAgg(
|
||||
"projects__id",
|
||||
distinct=True,
|
||||
filter=~Q(projects__id=True),
|
||||
"projects__id", distinct=True, filter=~Q(projects__id=True)
|
||||
),
|
||||
Value([], output_field=ArrayField(UUIDField())),
|
||||
),
|
||||
|
|
@ -126,9 +121,7 @@ class PageViewSet(BaseViewSet):
|
|||
context={
|
||||
"project_id": project_id,
|
||||
"owned_by_id": request.user.id,
|
||||
"description_html": request.data.get(
|
||||
"description_html", "<p></p>"
|
||||
),
|
||||
"description_html": request.data.get("description_html", "<p></p>"),
|
||||
},
|
||||
)
|
||||
|
||||
|
|
@ -145,23 +138,18 @@ class PageViewSet(BaseViewSet):
|
|||
def partial_update(self, request, slug, project_id, pk):
|
||||
try:
|
||||
page = Page.objects.get(
|
||||
pk=pk,
|
||||
workspace__slug=slug,
|
||||
projects__id=project_id,
|
||||
pk=pk, workspace__slug=slug, projects__id=project_id
|
||||
)
|
||||
|
||||
if page.is_locked:
|
||||
return Response(
|
||||
{"error": "Page is locked"},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
{"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,
|
||||
pk=parent, workspace__slug=slug, projects__id=project_id
|
||||
)
|
||||
|
||||
# Only update access if the page owner is the requesting user
|
||||
|
|
@ -176,9 +164,7 @@ class PageViewSet(BaseViewSet):
|
|||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
||||
serializer = PageDetailSerializer(
|
||||
page, data=request.data, partial=True
|
||||
)
|
||||
serializer = PageDetailSerializer(page, data=request.data, partial=True)
|
||||
page_description = page.description_html
|
||||
if serializer.is_valid():
|
||||
serializer.save()
|
||||
|
|
@ -187,18 +173,14 @@ class PageViewSet(BaseViewSet):
|
|||
page_transaction.delay(
|
||||
new_value=request.data,
|
||||
old_value=json.dumps(
|
||||
{
|
||||
"description_html": page_description,
|
||||
},
|
||||
{"description_html": page_description},
|
||||
cls=DjangoJSONEncoder,
|
||||
),
|
||||
page_id=pk,
|
||||
)
|
||||
|
||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||
return Response(
|
||||
serializer.errors, status=status.HTTP_400_BAD_REQUEST
|
||||
)
|
||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
except Page.DoesNotExist:
|
||||
return Response(
|
||||
{
|
||||
|
|
@ -207,13 +189,7 @@ class PageViewSet(BaseViewSet):
|
|||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
||||
@allow_permission(
|
||||
[
|
||||
ROLE.ADMIN,
|
||||
ROLE.MEMBER,
|
||||
ROLE.GUEST,
|
||||
]
|
||||
)
|
||||
@allow_permission([ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST])
|
||||
def retrieve(self, request, slug, project_id, pk=None):
|
||||
page = self.get_queryset().filter(pk=pk).first()
|
||||
project = Project.objects.get(pk=project_id)
|
||||
|
|
@ -241,8 +217,7 @@ class PageViewSet(BaseViewSet):
|
|||
|
||||
if page is None:
|
||||
return Response(
|
||||
{"error": "Page not found"},
|
||||
status=status.HTTP_404_NOT_FOUND,
|
||||
{"error": "Page not found"}, status=status.HTTP_404_NOT_FOUND
|
||||
)
|
||||
else:
|
||||
issue_ids = PageLog.objects.filter(
|
||||
|
|
@ -257,10 +232,7 @@ class PageViewSet(BaseViewSet):
|
|||
user_id=request.user.id,
|
||||
project_id=project_id,
|
||||
)
|
||||
return Response(
|
||||
data,
|
||||
status=status.HTTP_200_OK,
|
||||
)
|
||||
return Response(data, status=status.HTTP_200_OK)
|
||||
|
||||
@allow_permission([ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST])
|
||||
def lock(self, request, slug, project_id, pk):
|
||||
|
|
@ -306,13 +278,7 @@ class PageViewSet(BaseViewSet):
|
|||
page.save()
|
||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
@allow_permission(
|
||||
[
|
||||
ROLE.ADMIN,
|
||||
ROLE.MEMBER,
|
||||
ROLE.GUEST,
|
||||
]
|
||||
)
|
||||
@allow_permission([ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST])
|
||||
def list(self, request, slug, project_id):
|
||||
queryset = self.get_queryset()
|
||||
project = Project.objects.get(pk=project_id)
|
||||
|
|
@ -332,17 +298,12 @@ class PageViewSet(BaseViewSet):
|
|||
|
||||
@allow_permission([ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST])
|
||||
def archive(self, request, slug, project_id, pk):
|
||||
page = Page.objects.get(
|
||||
pk=pk, workspace__slug=slug, projects__id=project_id
|
||||
)
|
||||
page = Page.objects.get(pk=pk, workspace__slug=slug, projects__id=project_id)
|
||||
|
||||
# only the owner or admin can archive the page
|
||||
if (
|
||||
ProjectMember.objects.filter(
|
||||
project_id=project_id,
|
||||
member=request.user,
|
||||
is_active=True,
|
||||
role__lte=15,
|
||||
project_id=project_id, member=request.user, is_active=True, role__lte=15
|
||||
).exists()
|
||||
and request.user.id != page.owned_by_id
|
||||
):
|
||||
|
|
@ -360,24 +321,16 @@ class PageViewSet(BaseViewSet):
|
|||
|
||||
unarchive_archive_page_and_descendants(pk, datetime.now())
|
||||
|
||||
return Response(
|
||||
{"archived_at": str(datetime.now())},
|
||||
status=status.HTTP_200_OK,
|
||||
)
|
||||
return Response({"archived_at": str(datetime.now())}, status=status.HTTP_200_OK)
|
||||
|
||||
@allow_permission([ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST])
|
||||
def unarchive(self, request, slug, project_id, pk):
|
||||
page = Page.objects.get(
|
||||
pk=pk, workspace__slug=slug, projects__id=project_id
|
||||
)
|
||||
page = Page.objects.get(pk=pk, workspace__slug=slug, projects__id=project_id)
|
||||
|
||||
# only the owner or admin can un archive the page
|
||||
if (
|
||||
ProjectMember.objects.filter(
|
||||
project_id=project_id,
|
||||
member=request.user,
|
||||
is_active=True,
|
||||
role__lte=15,
|
||||
project_id=project_id, member=request.user, is_active=True, role__lte=15
|
||||
).exists()
|
||||
and request.user.id != page.owned_by_id
|
||||
):
|
||||
|
|
@ -397,9 +350,7 @@ class PageViewSet(BaseViewSet):
|
|||
|
||||
@allow_permission([ROLE.ADMIN], creator=True, model=Page)
|
||||
def destroy(self, request, slug, project_id, pk):
|
||||
page = Page.objects.get(
|
||||
pk=pk, workspace__slug=slug, projects__id=project_id
|
||||
)
|
||||
page = Page.objects.get(pk=pk, workspace__slug=slug, projects__id=project_id)
|
||||
|
||||
if page.archived_at is None:
|
||||
return Response(
|
||||
|
|
@ -438,7 +389,6 @@ class PageViewSet(BaseViewSet):
|
|||
|
||||
|
||||
class PageFavoriteViewSet(BaseViewSet):
|
||||
|
||||
model = UserFavorite
|
||||
|
||||
@allow_permission([ROLE.ADMIN, ROLE.MEMBER])
|
||||
|
|
@ -465,7 +415,6 @@ class PageFavoriteViewSet(BaseViewSet):
|
|||
|
||||
|
||||
class PageLogEndpoint(BaseAPIView):
|
||||
|
||||
serializer_class = PageLogSerializer
|
||||
model = PageLog
|
||||
|
||||
|
|
@ -504,7 +453,6 @@ class PageLogEndpoint(BaseAPIView):
|
|||
|
||||
|
||||
class SubPagesEndpoint(BaseAPIView):
|
||||
|
||||
@method_decorator(gzip_page)
|
||||
def get(self, request, slug, project_id, page_id):
|
||||
pages = (
|
||||
|
|
@ -522,27 +470,15 @@ class SubPagesEndpoint(BaseAPIView):
|
|||
|
||||
|
||||
class PagesDescriptionViewSet(BaseViewSet):
|
||||
|
||||
@allow_permission(
|
||||
[
|
||||
ROLE.ADMIN,
|
||||
ROLE.MEMBER,
|
||||
ROLE.GUEST,
|
||||
]
|
||||
)
|
||||
@allow_permission([ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST])
|
||||
def retrieve(self, request, slug, project_id, pk):
|
||||
page = (
|
||||
Page.objects.filter(
|
||||
pk=pk, workspace__slug=slug, projects__id=project_id
|
||||
)
|
||||
Page.objects.filter(pk=pk, workspace__slug=slug, projects__id=project_id)
|
||||
.filter(Q(owned_by=self.request.user) | Q(access=0))
|
||||
.first()
|
||||
)
|
||||
if page is None:
|
||||
return Response(
|
||||
{"error": "Page not found"},
|
||||
status=404,
|
||||
)
|
||||
return Response({"error": "Page not found"}, status=404)
|
||||
binary_data = page.description_binary
|
||||
|
||||
def stream_data():
|
||||
|
|
@ -554,26 +490,19 @@ class PagesDescriptionViewSet(BaseViewSet):
|
|||
response = StreamingHttpResponse(
|
||||
stream_data(), content_type="application/octet-stream"
|
||||
)
|
||||
response["Content-Disposition"] = (
|
||||
'attachment; filename="page_description.bin"'
|
||||
)
|
||||
response["Content-Disposition"] = 'attachment; filename="page_description.bin"'
|
||||
return response
|
||||
|
||||
@allow_permission([ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST])
|
||||
def partial_update(self, request, slug, project_id, pk):
|
||||
page = (
|
||||
Page.objects.filter(
|
||||
pk=pk, workspace__slug=slug, projects__id=project_id
|
||||
)
|
||||
Page.objects.filter(pk=pk, workspace__slug=slug, projects__id=project_id)
|
||||
.filter(Q(owned_by=self.request.user) | Q(access=0))
|
||||
.first()
|
||||
)
|
||||
|
||||
if page is None:
|
||||
return Response(
|
||||
{"error": "Page not found"},
|
||||
status=404,
|
||||
)
|
||||
return Response({"error": "Page not found"}, status=404)
|
||||
|
||||
if page.is_locked:
|
||||
return Response(
|
||||
|
|
@ -595,10 +524,7 @@ class PagesDescriptionViewSet(BaseViewSet):
|
|||
|
||||
# Serialize the existing instance
|
||||
existing_instance = json.dumps(
|
||||
{
|
||||
"description_html": page.description_html,
|
||||
},
|
||||
cls=DjangoJSONEncoder,
|
||||
{"description_html": page.description_html}, cls=DjangoJSONEncoder
|
||||
)
|
||||
|
||||
# Get the base64 data from the request
|
||||
|
|
@ -611,9 +537,7 @@ class PagesDescriptionViewSet(BaseViewSet):
|
|||
# capture the page transaction
|
||||
if request.data.get("description_html"):
|
||||
page_transaction.delay(
|
||||
new_value=request.data,
|
||||
old_value=existing_instance,
|
||||
page_id=pk,
|
||||
new_value=request.data, old_value=existing_instance, page_id=pk
|
||||
)
|
||||
# Store the updated binary data
|
||||
page.description_binary = new_binary_data
|
||||
|
|
|
|||
|
|
@ -5,34 +5,25 @@ from rest_framework.response import Response
|
|||
# Module imports
|
||||
from plane.db.models import PageVersion
|
||||
from ..base import BaseAPIView
|
||||
from plane.app.serializers import (
|
||||
PageVersionSerializer,
|
||||
PageVersionDetailSerializer,
|
||||
)
|
||||
from plane.app.serializers import PageVersionSerializer, PageVersionDetailSerializer
|
||||
from plane.app.permissions import allow_permission, ROLE
|
||||
|
||||
|
||||
class PageVersionEndpoint(BaseAPIView):
|
||||
|
||||
@allow_permission(
|
||||
allowed_roles=[ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST]
|
||||
)
|
||||
@allow_permission(allowed_roles=[ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST])
|
||||
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,
|
||||
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,
|
||||
workspace__slug=slug, page_id=page_id
|
||||
)
|
||||
# Serialize the page versions
|
||||
serializer = PageVersionSerializer(page_versions, many=True)
|
||||
|
|
|
|||
|
|
@ -6,15 +6,7 @@ import json
|
|||
|
||||
# Django imports
|
||||
from django.db import IntegrityError
|
||||
from django.db.models import (
|
||||
Exists,
|
||||
F,
|
||||
Func,
|
||||
OuterRef,
|
||||
Prefetch,
|
||||
Q,
|
||||
Subquery,
|
||||
)
|
||||
from django.db.models import Exists, F, Func, OuterRef, Prefetch, Q, Subquery
|
||||
from django.core.serializers.json import DjangoJSONEncoder
|
||||
|
||||
# Third Party imports
|
||||
|
|
@ -30,11 +22,7 @@ from plane.app.serializers import (
|
|||
DeployBoardSerializer,
|
||||
)
|
||||
|
||||
from plane.app.permissions import (
|
||||
ProjectMemberPermission,
|
||||
allow_permission,
|
||||
ROLE,
|
||||
)
|
||||
from plane.app.permissions import ProjectMemberPermission, allow_permission, ROLE
|
||||
from plane.db.models import (
|
||||
UserFavorite,
|
||||
Cycle,
|
||||
|
|
@ -73,10 +61,7 @@ class ProjectViewSet(BaseViewSet):
|
|||
.get_queryset()
|
||||
.filter(workspace__slug=self.kwargs.get("slug"))
|
||||
.select_related(
|
||||
"workspace",
|
||||
"workspace__owner",
|
||||
"default_assignee",
|
||||
"project_lead",
|
||||
"workspace", "workspace__owner", "default_assignee", "project_lead"
|
||||
)
|
||||
.annotate(
|
||||
is_favorite=Exists(
|
||||
|
|
@ -100,9 +85,7 @@ class ProjectViewSet(BaseViewSet):
|
|||
)
|
||||
.annotate(
|
||||
total_members=ProjectMember.objects.filter(
|
||||
project_id=OuterRef("id"),
|
||||
member__is_bot=False,
|
||||
is_active=True,
|
||||
project_id=OuterRef("id"), member__is_bot=False, is_active=True
|
||||
)
|
||||
.order_by()
|
||||
.annotate(count=Func(F("id"), function="Count"))
|
||||
|
|
@ -139,8 +122,7 @@ class ProjectViewSet(BaseViewSet):
|
|||
Prefetch(
|
||||
"project_projectmember",
|
||||
queryset=ProjectMember.objects.filter(
|
||||
workspace__slug=self.kwargs.get("slug"),
|
||||
is_active=True,
|
||||
workspace__slug=self.kwargs.get("slug"), is_active=True
|
||||
).select_related("member"),
|
||||
to_attr="members_list",
|
||||
)
|
||||
|
|
@ -149,21 +131,13 @@ class ProjectViewSet(BaseViewSet):
|
|||
)
|
||||
|
||||
@allow_permission(
|
||||
allowed_roles=[ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST],
|
||||
level="WORKSPACE",
|
||||
allowed_roles=[ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST], level="WORKSPACE"
|
||||
)
|
||||
def list(self, request, slug):
|
||||
fields = [
|
||||
field
|
||||
for field in request.GET.get("fields", "").split(",")
|
||||
if field
|
||||
]
|
||||
fields = [field for field in request.GET.get("fields", "").split(",") if field]
|
||||
projects = self.get_queryset().order_by("sort_order", "name")
|
||||
if WorkspaceMember.objects.filter(
|
||||
member=request.user,
|
||||
workspace__slug=slug,
|
||||
is_active=True,
|
||||
role=5,
|
||||
member=request.user, workspace__slug=slug, is_active=True, role=5
|
||||
).exists():
|
||||
projects = projects.filter(
|
||||
project_projectmember__member=self.request.user,
|
||||
|
|
@ -171,10 +145,7 @@ class ProjectViewSet(BaseViewSet):
|
|||
)
|
||||
|
||||
if WorkspaceMember.objects.filter(
|
||||
member=request.user,
|
||||
workspace__slug=slug,
|
||||
is_active=True,
|
||||
role=15,
|
||||
member=request.user, workspace__slug=slug, is_active=True, role=15
|
||||
).exists():
|
||||
projects = projects.filter(
|
||||
Q(
|
||||
|
|
@ -184,9 +155,7 @@ class ProjectViewSet(BaseViewSet):
|
|||
| Q(network=2)
|
||||
)
|
||||
|
||||
if request.GET.get("per_page", False) and request.GET.get(
|
||||
"cursor", False
|
||||
):
|
||||
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,
|
||||
|
|
@ -202,8 +171,7 @@ class ProjectViewSet(BaseViewSet):
|
|||
return Response(projects, status=status.HTTP_200_OK)
|
||||
|
||||
@allow_permission(
|
||||
allowed_roles=[ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST],
|
||||
level="WORKSPACE",
|
||||
allowed_roles=[ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST], level="WORKSPACE"
|
||||
)
|
||||
def retrieve(self, request, slug, pk):
|
||||
project = (
|
||||
|
|
@ -212,7 +180,7 @@ class ProjectViewSet(BaseViewSet):
|
|||
.filter(pk=pk)
|
||||
.annotate(
|
||||
total_issues=Issue.issue_objects.filter(
|
||||
project_id=self.kwargs.get("pk"),
|
||||
project_id=self.kwargs.get("pk")
|
||||
)
|
||||
.order_by()
|
||||
.annotate(count=Func(F("id"), function="Count"))
|
||||
|
|
@ -220,8 +188,7 @@ class ProjectViewSet(BaseViewSet):
|
|||
)
|
||||
.annotate(
|
||||
sub_issues=Issue.issue_objects.filter(
|
||||
project_id=self.kwargs.get("pk"),
|
||||
parent__isnull=False,
|
||||
project_id=self.kwargs.get("pk"), parent__isnull=False
|
||||
)
|
||||
.order_by()
|
||||
.annotate(count=Func(F("id"), function="Count"))
|
||||
|
|
@ -229,8 +196,7 @@ class ProjectViewSet(BaseViewSet):
|
|||
)
|
||||
.annotate(
|
||||
archived_issues=Issue.objects.filter(
|
||||
project_id=self.kwargs.get("pk"),
|
||||
archived_at__isnull=False,
|
||||
project_id=self.kwargs.get("pk"), archived_at__isnull=False
|
||||
)
|
||||
.order_by()
|
||||
.annotate(count=Func(F("id"), function="Count"))
|
||||
|
|
@ -248,8 +214,7 @@ class ProjectViewSet(BaseViewSet):
|
|||
)
|
||||
.annotate(
|
||||
draft_issues=Issue.objects.filter(
|
||||
project_id=self.kwargs.get("pk"),
|
||||
is_draft=True,
|
||||
project_id=self.kwargs.get("pk"), is_draft=True
|
||||
)
|
||||
.order_by()
|
||||
.annotate(count=Func(F("id"), function="Count"))
|
||||
|
|
@ -269,8 +234,7 @@ class ProjectViewSet(BaseViewSet):
|
|||
|
||||
if project is None:
|
||||
return Response(
|
||||
{"error": "Project does not exist"},
|
||||
status=status.HTTP_404_NOT_FOUND,
|
||||
{"error": "Project does not exist"}, status=status.HTTP_404_NOT_FOUND
|
||||
)
|
||||
|
||||
recent_visited_task.delay(
|
||||
|
|
@ -297,14 +261,11 @@ class ProjectViewSet(BaseViewSet):
|
|||
|
||||
# Add the user as Administrator to the project
|
||||
_ = ProjectMember.objects.create(
|
||||
project_id=serializer.data["id"],
|
||||
member=request.user,
|
||||
role=20,
|
||||
project_id=serializer.data["id"], member=request.user, role=20
|
||||
)
|
||||
# Also create the issue property for the user
|
||||
_ = IssueUserProperty.objects.create(
|
||||
project_id=serializer.data["id"],
|
||||
user=request.user,
|
||||
project_id=serializer.data["id"], user=request.user
|
||||
)
|
||||
|
||||
if serializer.data["project_lead"] is not None and str(
|
||||
|
|
@ -372,11 +333,7 @@ class ProjectViewSet(BaseViewSet):
|
|||
]
|
||||
)
|
||||
|
||||
project = (
|
||||
self.get_queryset()
|
||||
.filter(pk=serializer.data["id"])
|
||||
.first()
|
||||
)
|
||||
project = self.get_queryset().filter(pk=serializer.data["id"]).first()
|
||||
|
||||
# Create the model activity
|
||||
model_activity.delay(
|
||||
|
|
@ -390,13 +347,8 @@ class ProjectViewSet(BaseViewSet):
|
|||
)
|
||||
|
||||
serializer = ProjectListSerializer(project)
|
||||
return Response(
|
||||
serializer.data, status=status.HTTP_201_CREATED
|
||||
)
|
||||
return Response(
|
||||
serializer.errors,
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
return Response(serializer.data, status=status.HTTP_201_CREATED)
|
||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
except IntegrityError as e:
|
||||
if "already exists" in str(e):
|
||||
return Response(
|
||||
|
|
@ -405,8 +357,7 @@ class ProjectViewSet(BaseViewSet):
|
|||
)
|
||||
except Workspace.DoesNotExist:
|
||||
return Response(
|
||||
{"error": "Workspace does not exist"},
|
||||
status=status.HTTP_404_NOT_FOUND,
|
||||
{"error": "Workspace does not exist"}, status=status.HTTP_404_NOT_FOUND
|
||||
)
|
||||
except serializers.ValidationError:
|
||||
return Response(
|
||||
|
|
@ -445,10 +396,7 @@ class ProjectViewSet(BaseViewSet):
|
|||
|
||||
serializer = ProjectSerializer(
|
||||
project,
|
||||
data={
|
||||
**request.data,
|
||||
"intake_view": intake_view,
|
||||
},
|
||||
data={**request.data, "intake_view": intake_view},
|
||||
context={"workspace_id": workspace.id},
|
||||
partial=True,
|
||||
)
|
||||
|
|
@ -457,8 +405,7 @@ class ProjectViewSet(BaseViewSet):
|
|||
serializer.save()
|
||||
if intake_view:
|
||||
intake = Intake.objects.filter(
|
||||
project=project,
|
||||
is_default=True,
|
||||
project=project, is_default=True
|
||||
).first()
|
||||
if not intake:
|
||||
Intake.objects.create(
|
||||
|
|
@ -477,11 +424,7 @@ class ProjectViewSet(BaseViewSet):
|
|||
is_triage=True,
|
||||
)
|
||||
|
||||
project = (
|
||||
self.get_queryset()
|
||||
.filter(pk=serializer.data["id"])
|
||||
.first()
|
||||
)
|
||||
project = self.get_queryset().filter(pk=serializer.data["id"]).first()
|
||||
|
||||
model_activity.delay(
|
||||
model_name="project",
|
||||
|
|
@ -494,9 +437,7 @@ class ProjectViewSet(BaseViewSet):
|
|||
)
|
||||
serializer = ProjectListSerializer(project)
|
||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||
return Response(
|
||||
serializer.errors, status=status.HTTP_400_BAD_REQUEST
|
||||
)
|
||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
except IntegrityError as e:
|
||||
if "already exists" in str(e):
|
||||
|
|
@ -506,8 +447,7 @@ class ProjectViewSet(BaseViewSet):
|
|||
)
|
||||
except (Project.DoesNotExist, Workspace.DoesNotExist):
|
||||
return Response(
|
||||
{"error": "Project does not exist"},
|
||||
status=status.HTTP_404_NOT_FOUND,
|
||||
{"error": "Project does not exist"}, status=status.HTTP_404_NOT_FOUND
|
||||
)
|
||||
except serializers.ValidationError:
|
||||
return Response(
|
||||
|
|
@ -518,10 +458,7 @@ class ProjectViewSet(BaseViewSet):
|
|||
def destroy(self, request, slug, pk):
|
||||
if (
|
||||
WorkspaceMember.objects.filter(
|
||||
member=request.user,
|
||||
workspace__slug=slug,
|
||||
is_active=True,
|
||||
role=20,
|
||||
member=request.user, workspace__slug=slug, is_active=True, role=20
|
||||
).exists()
|
||||
or ProjectMember.objects.filter(
|
||||
member=request.user,
|
||||
|
|
@ -535,16 +472,10 @@ class ProjectViewSet(BaseViewSet):
|
|||
project.delete()
|
||||
|
||||
# Delete the project members
|
||||
DeployBoard.objects.filter(
|
||||
project_id=pk,
|
||||
workspace__slug=slug,
|
||||
).delete()
|
||||
DeployBoard.objects.filter(project_id=pk, workspace__slug=slug).delete()
|
||||
|
||||
# Delete the user favorite
|
||||
UserFavorite.objects.filter(
|
||||
project_id=pk,
|
||||
workspace__slug=slug,
|
||||
).delete()
|
||||
UserFavorite.objects.filter(project_id=pk, workspace__slug=slug).delete()
|
||||
|
||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||
else:
|
||||
|
|
@ -555,19 +486,14 @@ class ProjectViewSet(BaseViewSet):
|
|||
|
||||
|
||||
class ProjectArchiveUnarchiveEndpoint(BaseAPIView):
|
||||
|
||||
@allow_permission([ROLE.ADMIN, ROLE.MEMBER])
|
||||
def post(self, request, slug, project_id):
|
||||
project = Project.objects.get(pk=project_id, workspace__slug=slug)
|
||||
project.archived_at = timezone.now()
|
||||
project.save()
|
||||
UserFavorite.objects.filter(
|
||||
workspace__slug=slug,
|
||||
project=project_id,
|
||||
).delete()
|
||||
UserFavorite.objects.filter(workspace__slug=slug, project=project_id).delete()
|
||||
return Response(
|
||||
{"archived_at": str(project.archived_at)},
|
||||
status=status.HTTP_200_OK,
|
||||
{"archived_at": str(project.archived_at)}, status=status.HTTP_200_OK
|
||||
)
|
||||
|
||||
@allow_permission([ROLE.ADMIN, ROLE.MEMBER])
|
||||
|
|
@ -585,8 +511,7 @@ class ProjectIdentifierEndpoint(BaseAPIView):
|
|||
|
||||
if name == "":
|
||||
return Response(
|
||||
{"error": "Name is required"},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
{"error": "Name is required"}, status=status.HTTP_400_BAD_REQUEST
|
||||
)
|
||||
|
||||
exists = ProjectIdentifier.objects.filter(
|
||||
|
|
@ -594,8 +519,7 @@ class ProjectIdentifierEndpoint(BaseAPIView):
|
|||
).values("id", "name", "project")
|
||||
|
||||
return Response(
|
||||
{"exists": len(exists), "identifiers": exists},
|
||||
status=status.HTTP_200_OK,
|
||||
{"exists": len(exists), "identifiers": exists}, status=status.HTTP_200_OK
|
||||
)
|
||||
|
||||
@allow_permission([ROLE.ADMIN, ROLE.MEMBER], level="WORKSPACE")
|
||||
|
|
@ -604,27 +528,18 @@ class ProjectIdentifierEndpoint(BaseAPIView):
|
|||
|
||||
if name == "":
|
||||
return Response(
|
||||
{"error": "Name is required"},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
{"error": "Name is required"}, status=status.HTTP_400_BAD_REQUEST
|
||||
)
|
||||
|
||||
if Project.objects.filter(
|
||||
identifier=name, workspace__slug=slug
|
||||
).exists():
|
||||
if Project.objects.filter(identifier=name, workspace__slug=slug).exists():
|
||||
return Response(
|
||||
{
|
||||
"error": "Cannot delete an identifier of an existing project"
|
||||
},
|
||||
{"error": "Cannot delete an identifier of an existing project"},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
||||
ProjectIdentifier.objects.filter(
|
||||
name=name, workspace__slug=slug
|
||||
).delete()
|
||||
ProjectIdentifier.objects.filter(name=name, workspace__slug=slug).delete()
|
||||
|
||||
return Response(
|
||||
status=status.HTTP_204_NO_CONTENT,
|
||||
)
|
||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
|
||||
class ProjectUserViewsEndpoint(BaseAPIView):
|
||||
|
|
@ -632,15 +547,11 @@ class ProjectUserViewsEndpoint(BaseAPIView):
|
|||
project = Project.objects.get(pk=project_id, workspace__slug=slug)
|
||||
|
||||
project_member = ProjectMember.objects.filter(
|
||||
member=request.user,
|
||||
project=project,
|
||||
is_active=True,
|
||||
member=request.user, project=project, is_active=True
|
||||
).first()
|
||||
|
||||
if project_member is None:
|
||||
return Response(
|
||||
{"error": "Forbidden"}, status=status.HTTP_403_FORBIDDEN
|
||||
)
|
||||
return Response({"error": "Forbidden"}, status=status.HTTP_403_FORBIDDEN)
|
||||
|
||||
view_props = project_member.view_props
|
||||
default_props = project_member.default_props
|
||||
|
|
@ -648,12 +559,8 @@ class ProjectUserViewsEndpoint(BaseAPIView):
|
|||
sort_order = project_member.sort_order
|
||||
|
||||
project_member.view_props = request.data.get("view_props", view_props)
|
||||
project_member.default_props = request.data.get(
|
||||
"default_props", default_props
|
||||
)
|
||||
project_member.preferences = request.data.get(
|
||||
"preferences", preferences
|
||||
)
|
||||
project_member.default_props = request.data.get("default_props", default_props)
|
||||
project_member.preferences = request.data.get("preferences", preferences)
|
||||
project_member.sort_order = request.data.get("sort_order", sort_order)
|
||||
|
||||
project_member.save()
|
||||
|
|
@ -701,9 +608,7 @@ class ProjectFavoritesViewSet(BaseViewSet):
|
|||
|
||||
|
||||
class ProjectPublicCoverImagesEndpoint(BaseAPIView):
|
||||
permission_classes = [
|
||||
AllowAny,
|
||||
]
|
||||
permission_classes = [AllowAny]
|
||||
|
||||
# Cache the below api for 24 hours
|
||||
@cache_response(60 * 60 * 24, user=False)
|
||||
|
|
@ -746,17 +651,13 @@ class ProjectPublicCoverImagesEndpoint(BaseAPIView):
|
|||
|
||||
|
||||
class DeployBoardViewSet(BaseViewSet):
|
||||
permission_classes = [
|
||||
ProjectMemberPermission,
|
||||
]
|
||||
permission_classes = [ProjectMemberPermission]
|
||||
serializer_class = DeployBoardSerializer
|
||||
model = DeployBoard
|
||||
|
||||
def list(self, request, slug, project_id):
|
||||
project_deploy_board = DeployBoard.objects.filter(
|
||||
entity_name="project",
|
||||
entity_identifier=project_id,
|
||||
workspace__slug=slug,
|
||||
entity_name="project", entity_identifier=project_id, workspace__slug=slug
|
||||
).first()
|
||||
|
||||
serializer = DeployBoardSerializer(project_deploy_board)
|
||||
|
|
@ -779,9 +680,7 @@ class DeployBoardViewSet(BaseViewSet):
|
|||
)
|
||||
|
||||
project_deploy_board, _ = DeployBoard.objects.get_or_create(
|
||||
entity_name="project",
|
||||
entity_identifier=project_id,
|
||||
project_id=project_id,
|
||||
entity_name="project", entity_identifier=project_id, project_id=project_id
|
||||
)
|
||||
project_deploy_board.intake = intake
|
||||
project_deploy_board.view_props = views
|
||||
|
|
|
|||
|
|
@ -52,24 +52,19 @@ class ProjectInvitationsViewset(BaseViewSet):
|
|||
# Check if email is provided
|
||||
if not emails:
|
||||
return Response(
|
||||
{"error": "Emails are required"},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
{"error": "Emails are required"}, status=status.HTTP_400_BAD_REQUEST
|
||||
)
|
||||
|
||||
for email in emails:
|
||||
workspace_role = WorkspaceMember.objects.filter(
|
||||
workspace__slug=slug,
|
||||
member__email=email.get("email"),
|
||||
is_active=True,
|
||||
workspace__slug=slug, member__email=email.get("email"), is_active=True
|
||||
).role
|
||||
|
||||
if workspace_role in [5, 20] and workspace_role != email.get(
|
||||
"role", 5
|
||||
):
|
||||
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"
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
workspace = Workspace.objects.get(slug=slug)
|
||||
|
|
@ -84,10 +79,7 @@ class ProjectInvitationsViewset(BaseViewSet):
|
|||
project_id=project_id,
|
||||
workspace_id=workspace.id,
|
||||
token=jwt.encode(
|
||||
{
|
||||
"email": email,
|
||||
"timestamp": datetime.now().timestamp(),
|
||||
},
|
||||
{"email": email, "timestamp": datetime.now().timestamp()},
|
||||
settings.SECRET_KEY,
|
||||
algorithm="HS256",
|
||||
),
|
||||
|
|
@ -120,10 +112,7 @@ class ProjectInvitationsViewset(BaseViewSet):
|
|||
)
|
||||
|
||||
return Response(
|
||||
{
|
||||
"message": "Email sent successfully",
|
||||
},
|
||||
status=status.HTTP_200_OK,
|
||||
{"message": "Email sent successfully"}, status=status.HTTP_200_OK
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -144,9 +133,7 @@ class UserProjectInvitationsViewset(BaseViewSet):
|
|||
|
||||
# Get the workspace user role
|
||||
workspace_member = WorkspaceMember.objects.get(
|
||||
member=request.user,
|
||||
workspace__slug=slug,
|
||||
is_active=True,
|
||||
member=request.user, workspace__slug=slug, is_active=True
|
||||
)
|
||||
|
||||
workspace_role = workspace_member.role
|
||||
|
|
@ -154,9 +141,7 @@ class UserProjectInvitationsViewset(BaseViewSet):
|
|||
|
||||
# If the user was already part of workspace
|
||||
_ = ProjectMember.objects.filter(
|
||||
workspace__slug=slug,
|
||||
project_id__in=project_ids,
|
||||
member=request.user,
|
||||
workspace__slug=slug, project_id__in=project_ids, member=request.user
|
||||
).update(is_active=True)
|
||||
|
||||
ProjectMember.objects.bulk_create(
|
||||
|
|
@ -187,21 +172,16 @@ class UserProjectInvitationsViewset(BaseViewSet):
|
|||
)
|
||||
|
||||
return Response(
|
||||
{"message": "Projects joined successfully"},
|
||||
status=status.HTTP_201_CREATED,
|
||||
{"message": "Projects joined successfully"}, status=status.HTTP_201_CREATED
|
||||
)
|
||||
|
||||
|
||||
class ProjectJoinEndpoint(BaseAPIView):
|
||||
permission_classes = [
|
||||
AllowAny,
|
||||
]
|
||||
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,
|
||||
pk=pk, project_id=project_id, workspace__slug=slug
|
||||
)
|
||||
|
||||
email = request.data.get("email", "")
|
||||
|
|
@ -230,11 +210,7 @@ class ProjectJoinEndpoint(BaseAPIView):
|
|||
_ = WorkspaceMember.objects.create(
|
||||
workspace_id=project_invite.workspace_id,
|
||||
member=user,
|
||||
role=(
|
||||
15
|
||||
if project_invite.role >= 15
|
||||
else project_invite.role
|
||||
),
|
||||
role=(15 if project_invite.role >= 15 else project_invite.role),
|
||||
)
|
||||
else:
|
||||
# Else make him active
|
||||
|
|
|
|||
|
|
@ -36,20 +36,13 @@ class ProjectMemberViewSet(BaseViewSet):
|
|||
|
||||
def get_permissions(self):
|
||||
if self.action == "leave":
|
||||
self.permission_classes = [
|
||||
ProjectLitePermission,
|
||||
]
|
||||
self.permission_classes = [ProjectLitePermission]
|
||||
else:
|
||||
self.permission_classes = [
|
||||
ProjectMemberPermission,
|
||||
]
|
||||
self.permission_classes = [ProjectMemberPermission]
|
||||
|
||||
return super(ProjectMemberViewSet, self).get_permissions()
|
||||
|
||||
search_fields = [
|
||||
"member__display_name",
|
||||
"member__first_name",
|
||||
]
|
||||
search_fields = ["member__display_name", "member__first_name"]
|
||||
|
||||
def get_queryset(self):
|
||||
return self.filter_queryset(
|
||||
|
|
@ -91,14 +84,9 @@ class ProjectMemberViewSet(BaseViewSet):
|
|||
# check the workspace role of the new user
|
||||
for member in member_roles:
|
||||
workspace_member_role = WorkspaceMember.objects.get(
|
||||
workspace__slug=slug,
|
||||
member=member,
|
||||
is_active=True,
|
||||
workspace__slug=slug, member=member, is_active=True
|
||||
).role
|
||||
if workspace_member_role in [20] and member_roles.get(member) in [
|
||||
5,
|
||||
15,
|
||||
]:
|
||||
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"
|
||||
|
|
@ -106,10 +94,7 @@ class ProjectMemberViewSet(BaseViewSet):
|
|||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
||||
if workspace_member_role in [5] and member_roles.get(member) in [
|
||||
15,
|
||||
20,
|
||||
]:
|
||||
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"
|
||||
|
|
@ -143,13 +128,11 @@ class ProjectMemberViewSet(BaseViewSet):
|
|||
|
||||
# Loop through requested members
|
||||
for member in members:
|
||||
|
||||
# Get the sort orders of the member
|
||||
sort_order = [
|
||||
project_member.get("sort_order")
|
||||
for project_member in project_members
|
||||
if str(project_member.get("member_id"))
|
||||
== str(member.get("member_id"))
|
||||
if str(project_member.get("member_id")) == str(member.get("member_id"))
|
||||
]
|
||||
# Create a new project member
|
||||
bulk_project_members.append(
|
||||
|
|
@ -158,9 +141,7 @@ class ProjectMemberViewSet(BaseViewSet):
|
|||
role=member.get("role", 5),
|
||||
project_id=project_id,
|
||||
workspace_id=project.workspace_id,
|
||||
sort_order=(
|
||||
sort_order[0] - 10000 if len(sort_order) else 65535
|
||||
),
|
||||
sort_order=(sort_order[0] - 10000 if len(sort_order) else 65535),
|
||||
)
|
||||
)
|
||||
# Create a new issue property
|
||||
|
|
@ -174,9 +155,7 @@ 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,
|
||||
bulk_project_members, batch_size=10, ignore_conflicts=True
|
||||
)
|
||||
|
||||
_ = IssueUserProperty.objects.bulk_create(
|
||||
|
|
@ -219,10 +198,7 @@ class ProjectMemberViewSet(BaseViewSet):
|
|||
@allow_permission([ROLE.ADMIN])
|
||||
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,
|
||||
pk=pk, workspace__slug=slug, project_id=project_id, is_active=True
|
||||
)
|
||||
if request.user.id == project_member.member_id:
|
||||
return Response(
|
||||
|
|
@ -238,9 +214,7 @@ class ProjectMemberViewSet(BaseViewSet):
|
|||
)
|
||||
|
||||
workspace_role = WorkspaceMember.objects.get(
|
||||
workspace__slug=slug,
|
||||
member=project_member.member,
|
||||
is_active=True,
|
||||
workspace__slug=slug, member=project_member.member, is_active=True
|
||||
).role
|
||||
if workspace_role in [5] and int(
|
||||
request.data.get("role", project_member.role)
|
||||
|
|
@ -258,9 +232,7 @@ class ProjectMemberViewSet(BaseViewSet):
|
|||
> requested_project_member.role
|
||||
):
|
||||
return Response(
|
||||
{
|
||||
"error": "You cannot update a role that is higher than your own role"
|
||||
},
|
||||
{"error": "You cannot update a role that is higher than your own role"},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
||||
|
|
@ -300,9 +272,7 @@ class ProjectMemberViewSet(BaseViewSet):
|
|||
# User cannot deactivate higher role
|
||||
if requesting_project_member.role < project_member.role:
|
||||
return Response(
|
||||
{
|
||||
"error": "You cannot remove a user having role higher than you"
|
||||
},
|
||||
{"error": "You cannot remove a user having role higher than you"},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
||||
|
|
@ -323,16 +293,13 @@ class ProjectMemberViewSet(BaseViewSet):
|
|||
if (
|
||||
project_member.role == 20
|
||||
and not ProjectMember.objects.filter(
|
||||
workspace__slug=slug,
|
||||
project_id=project_id,
|
||||
role=20,
|
||||
is_active=True,
|
||||
workspace__slug=slug, project_id=project_id, role=20, is_active=True
|
||||
).count()
|
||||
> 1
|
||||
):
|
||||
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"
|
||||
},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
|
@ -343,9 +310,7 @@ class ProjectMemberViewSet(BaseViewSet):
|
|||
|
||||
|
||||
class AddTeamToProjectEndpoint(BaseAPIView):
|
||||
permission_classes = [
|
||||
ProjectBasePermission,
|
||||
]
|
||||
permission_classes = [ProjectBasePermission]
|
||||
|
||||
def post(self, request, slug, project_id):
|
||||
team_members = TeamMember.objects.filter(
|
||||
|
|
@ -354,8 +319,7 @@ class AddTeamToProjectEndpoint(BaseAPIView):
|
|||
|
||||
if len(team_members) == 0:
|
||||
return Response(
|
||||
{"error": "No such team exists"},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
{"error": "No such team exists"}, status=status.HTTP_400_BAD_REQUEST
|
||||
)
|
||||
|
||||
workspace = Workspace.objects.get(slug=slug)
|
||||
|
|
@ -406,19 +370,14 @@ class ProjectMemberUserEndpoint(BaseAPIView):
|
|||
|
||||
|
||||
class UserProjectRolesEndpoint(BaseAPIView):
|
||||
permission_classes = [
|
||||
WorkspaceUserPermission,
|
||||
]
|
||||
permission_classes = [WorkspaceUserPermission]
|
||||
|
||||
def get(self, request, slug):
|
||||
project_members = ProjectMember.objects.filter(
|
||||
workspace__slug=slug,
|
||||
member_id=request.user.id,
|
||||
is_active=True,
|
||||
workspace__slug=slug, member_id=request.user.id, is_active=True
|
||||
).values("project_id", "role")
|
||||
|
||||
project_members = {
|
||||
str(member["project_id"]): member["role"]
|
||||
for member in project_members
|
||||
str(member["project_id"]): member["role"] for member in project_members
|
||||
}
|
||||
return Response(project_members, status=status.HTTP_200_OK)
|
||||
|
|
|
|||
|
|
@ -36,9 +36,7 @@ class GlobalSearchEndpoint(BaseAPIView):
|
|||
for field in fields:
|
||||
q |= Q(**{f"{field}__icontains": query})
|
||||
return (
|
||||
Workspace.objects.filter(
|
||||
q, workspace_member__member=self.request.user
|
||||
)
|
||||
Workspace.objects.filter(q, workspace_member__member=self.request.user)
|
||||
.distinct()
|
||||
.values("name", "id", "slug")
|
||||
)
|
||||
|
|
@ -110,11 +108,7 @@ class GlobalSearchEndpoint(BaseAPIView):
|
|||
cycles = cycles.filter(project_id=project_id)
|
||||
|
||||
return cycles.distinct().values(
|
||||
"name",
|
||||
"id",
|
||||
"project_id",
|
||||
"project__identifier",
|
||||
"workspace__slug",
|
||||
"name", "id", "project_id", "project__identifier", "workspace__slug"
|
||||
)
|
||||
|
||||
def filter_modules(self, query, slug, project_id, workspace_search):
|
||||
|
|
@ -135,11 +129,7 @@ class GlobalSearchEndpoint(BaseAPIView):
|
|||
modules = modules.filter(project_id=project_id)
|
||||
|
||||
return modules.distinct().values(
|
||||
"name",
|
||||
"id",
|
||||
"project_id",
|
||||
"project__identifier",
|
||||
"workspace__slug",
|
||||
"name", "id", "project_id", "project__identifier", "workspace__slug"
|
||||
)
|
||||
|
||||
def filter_pages(self, query, slug, project_id, workspace_search):
|
||||
|
|
@ -159,12 +149,10 @@ class GlobalSearchEndpoint(BaseAPIView):
|
|||
.annotate(
|
||||
project_ids=Coalesce(
|
||||
ArrayAgg(
|
||||
"projects__id",
|
||||
distinct=True,
|
||||
filter=~Q(projects__id=True),
|
||||
"projects__id", distinct=True, filter=~Q(projects__id=True)
|
||||
),
|
||||
Value([], output_field=ArrayField(UUIDField())),
|
||||
),
|
||||
)
|
||||
)
|
||||
.annotate(
|
||||
project_identifiers=Coalesce(
|
||||
|
|
@ -174,26 +162,21 @@ class GlobalSearchEndpoint(BaseAPIView):
|
|||
filter=~Q(projects__id=True),
|
||||
),
|
||||
Value([], output_field=ArrayField(CharField())),
|
||||
),
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
if workspace_search == "false" and project_id:
|
||||
project_subquery = ProjectPage.objects.filter(
|
||||
page_id=OuterRef("id"),
|
||||
project_id=project_id,
|
||||
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",
|
||||
"name", "id", "project_ids", "project_identifiers", "workspace__slug"
|
||||
)
|
||||
|
||||
def filter_views(self, query, slug, project_id, workspace_search):
|
||||
|
|
@ -214,18 +197,12 @@ class GlobalSearchEndpoint(BaseAPIView):
|
|||
issue_views = issue_views.filter(project_id=project_id)
|
||||
|
||||
return issue_views.distinct().values(
|
||||
"name",
|
||||
"id",
|
||||
"project_id",
|
||||
"project__identifier",
|
||||
"workspace__slug",
|
||||
"name", "id", "project_id", "project__identifier", "workspace__slug"
|
||||
)
|
||||
|
||||
def get(self, request, slug):
|
||||
query = request.query_params.get("search", False)
|
||||
workspace_search = request.query_params.get(
|
||||
"workspace_search", "false"
|
||||
)
|
||||
workspace_search = request.query_params.get("workspace_search", "false")
|
||||
project_id = request.query_params.get("project_id", False)
|
||||
|
||||
if not query:
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue