dev: update attachments for issues

This commit is contained in:
pablohashescobar 2024-02-05 14:30:49 +05:30
parent cedc08bc08
commit 02e5e0da4b
25 changed files with 193 additions and 287 deletions

View File

@ -5,9 +5,7 @@ from .issue import (
IssueSerializer, IssueSerializer,
LabelSerializer, LabelSerializer,
IssueLinkSerializer, IssueLinkSerializer,
IssueAttachmentSerializer,
IssueCommentSerializer, IssueCommentSerializer,
IssueAttachmentSerializer,
IssueActivitySerializer, IssueActivitySerializer,
IssueExpandSerializer, IssueExpandSerializer,
) )

View File

@ -17,7 +17,6 @@ from plane.db.models import (
IssueLabel, IssueLabel,
IssueLink, IssueLink,
IssueComment, IssueComment,
IssueAttachment,
IssueActivity, IssueActivity,
ProjectMember, ProjectMember,
) )
@ -296,22 +295,6 @@ class IssueLinkSerializer(BaseSerializer):
return IssueLink.objects.create(**validated_data) return IssueLink.objects.create(**validated_data)
class IssueAttachmentSerializer(BaseSerializer):
class Meta:
model = IssueAttachment
fields = "__all__"
read_only_fields = [
"id",
"workspace",
"project",
"issue",
"created_by",
"updated_by",
"created_at",
"updated_at",
]
class IssueCommentSerializer(BaseSerializer): class IssueCommentSerializer(BaseSerializer):
is_member = serializers.BooleanField(read_only=True) is_member = serializers.BooleanField(read_only=True)

View File

@ -17,7 +17,7 @@ from plane.db.models import (
Issue, Issue,
CycleIssue, CycleIssue,
IssueLink, IssueLink,
IssueAttachment, FileAsset,
) )
from plane.app.permissions import ProjectEntityPermission from plane.app.permissions import ProjectEntityPermission
from plane.api.serializers import ( from plane.api.serializers import (
@ -390,8 +390,9 @@ class CycleIssueAPIEndpoint(WebhookMixin, BaseAPIView):
.values("count") .values("count")
) )
.annotate( .annotate(
attachment_count=IssueAttachment.objects.filter( attachment_count=FileAsset.objects.filter(
issue=OuterRef("id") entity_identifier=OuterRef("id"),
entity_type="issue_attachment",
) )
.order_by() .order_by()
.annotate(count=Func(F("id"), function="Count")) .annotate(count=Func(F("id"), function="Count"))

View File

@ -31,13 +31,13 @@ from plane.app.permissions import (
) )
from plane.db.models import ( from plane.db.models import (
Issue, Issue,
IssueAttachment,
IssueLink, IssueLink,
Project, Project,
Label, Label,
ProjectMember, ProjectMember,
IssueComment, IssueComment,
IssueActivity, IssueActivity,
FileAsset,
) )
from plane.bgtasks.issue_activites_task import issue_activity from plane.bgtasks.issue_activites_task import issue_activity
from plane.api.serializers import ( from plane.api.serializers import (
@ -126,8 +126,9 @@ class IssueAPIEndpoint(WebhookMixin, BaseAPIView):
.values("count") .values("count")
) )
.annotate( .annotate(
attachment_count=IssueAttachment.objects.filter( attachment_count=FileAsset.objects.filter(
issue=OuterRef("id") entity_identifier=OuterRef("id"),
entity_type="issue_attachment",
) )
.order_by() .order_by()
.annotate(count=Func(F("id"), function="Count")) .annotate(count=Func(F("id"), function="Count"))

View File

@ -19,8 +19,8 @@ from plane.db.models import (
ModuleLink, ModuleLink,
Issue, Issue,
ModuleIssue, ModuleIssue,
IssueAttachment,
IssueLink, IssueLink,
FileAsset,
) )
from plane.api.serializers import ( from plane.api.serializers import (
ModuleSerializer, ModuleSerializer,
@ -273,8 +273,9 @@ class ModuleIssueAPIEndpoint(WebhookMixin, BaseAPIView):
.values("count") .values("count")
) )
.annotate( .annotate(
attachment_count=IssueAttachment.objects.filter( attachment_count=FileAsset.objects.filter(
issue=OuterRef("id") entity_identifier=OuterRef("id"),
entity_type="issue_attachment",
) )
.order_by() .order_by()
.annotate(count=Func(F("id"), function="Count")) .annotate(count=Func(F("id"), function="Count"))

View File

@ -60,7 +60,6 @@ from .issue import (
IssueStateSerializer, IssueStateSerializer,
IssueLinkSerializer, IssueLinkSerializer,
IssueLiteSerializer, IssueLiteSerializer,
IssueAttachmentSerializer,
IssueSubscriberSerializer, IssueSubscriberSerializer,
IssueReactionSerializer, IssueReactionSerializer,
CommentReactionSerializer, CommentReactionSerializer,

View File

@ -25,7 +25,7 @@ from plane.db.models import (
Module, Module,
ModuleIssue, ModuleIssue,
IssueLink, IssueLink,
IssueAttachment, FileAsset,
IssueReaction, IssueReaction,
CommentReaction, CommentReaction,
IssueVote, IssueVote,
@ -444,22 +444,6 @@ class IssueLinkSerializer(BaseSerializer):
return IssueLink.objects.create(**validated_data) return IssueLink.objects.create(**validated_data)
class IssueAttachmentSerializer(BaseFileSerializer):
class Meta:
model = IssueAttachment
fields = "__all__"
read_only_fields = [
"created_by",
"updated_by",
"created_at",
"updated_at",
"workspace",
"project",
"issue",
]
class IssueReactionSerializer(BaseSerializer): class IssueReactionSerializer(BaseSerializer):
actor_detail = UserLiteSerializer(read_only=True, source="actor") actor_detail = UserLiteSerializer(read_only=True, source="actor")

View File

@ -117,12 +117,12 @@ urlpatterns = [
name="project-issue-links", name="project-issue-links",
), ),
path( path(
"workspaces/<str:slug>/projects/<uuid:project_id>/issues/<uuid:issue_id>/issue-attachments/", "workspaces/<str:slug>/projects/<uuid:project_id>/issues/<uuid:issue_id>/attachments/",
IssueAttachmentEndpoint.as_view(), IssueAttachmentEndpoint.as_view(),
name="project-issue-attachments", name="project-issue-attachments",
), ),
path( path(
"workspaces/<str:slug>/projects/<uuid:project_id>/issues/<uuid:issue_id>/issue-attachments/<uuid:pk>/", "workspaces/<str:slug>/projects/<uuid:project_id>/issues/<uuid:issue_id>/attachments/<uuid:workspace_id>/<str:asset_key>/",
IssueAttachmentEndpoint.as_view(), IssueAttachmentEndpoint.as_view(),
name="project-issue-attachments", name="project-issue-attachments",
), ),

View File

@ -47,10 +47,10 @@ from plane.db.models import (
Issue, Issue,
CycleFavorite, CycleFavorite,
IssueLink, IssueLink,
IssueAttachment,
Label, Label,
CycleUserProperties, CycleUserProperties,
IssueSubscriber, IssueSubscriber,
FileAsset,
) )
from plane.bgtasks.issue_activites_task import issue_activity from plane.bgtasks.issue_activites_task import issue_activity
from plane.utils.issue_filters import issue_filters from plane.utils.issue_filters import issue_filters
@ -611,7 +611,7 @@ class CycleIssueViewSet(WebhookMixin, BaseViewSet):
.values("count") .values("count")
) )
.annotate( .annotate(
attachment_count=IssueAttachment.objects.filter( attachment_count=FileAsset.objects.filter(
issue=OuterRef("id") issue=OuterRef("id")
) )
.order_by() .order_by()

View File

@ -32,7 +32,7 @@ from plane.db.models import (
Dashboard, Dashboard,
Project, Project,
IssueLink, IssueLink,
IssueAttachment, FileAsset,
IssueRelation, IssueRelation,
) )
from plane.app.serializers import ( from plane.app.serializers import (
@ -117,8 +117,9 @@ def dashboard_assigned_issues(self, request, slug):
.values("count") .values("count")
) )
.annotate( .annotate(
attachment_count=IssueAttachment.objects.filter( attachment_count=FileAsset.objects.filter(
issue=OuterRef("id") entity_identifier=OuterRef("id"),
entity_type="issue_attachment",
) )
.order_by() .order_by()
.annotate(count=Func(F("id"), function="Count")) .annotate(count=Func(F("id"), function="Count"))
@ -229,8 +230,9 @@ def dashboard_created_issues(self, request, slug):
.values("count") .values("count")
) )
.annotate( .annotate(
attachment_count=IssueAttachment.objects.filter( attachment_count=FileAsset.objects.filter(
issue=OuterRef("id") entity_identifier=OuterRef("id"),
entity_type="issue_attachment",
) )
.order_by() .order_by()
.annotate(count=Func(F("id"), function="Count")) .annotate(count=Func(F("id"), function="Count"))

View File

@ -19,7 +19,7 @@ from plane.db.models import (
Issue, Issue,
State, State,
IssueLink, IssueLink,
IssueAttachment, FileAsset,
ProjectMember, ProjectMember,
) )
from plane.app.serializers import ( from plane.app.serializers import (
@ -92,7 +92,7 @@ class InboxIssueViewSet(BaseViewSet):
Issue.objects.filter( Issue.objects.filter(
project_id=self.kwargs.get("project_id"), project_id=self.kwargs.get("project_id"),
workspace__slug=self.kwargs.get("slug"), workspace__slug=self.kwargs.get("slug"),
issue_inbox__inbox_id=self.kwargs.get("inbox_id") issue_inbox__inbox_id=self.kwargs.get("inbox_id"),
) )
.select_related("workspace", "project", "state", "parent") .select_related("workspace", "project", "state", "parent")
.prefetch_related("assignees", "labels", "issue_module__module") .prefetch_related("assignees", "labels", "issue_module__module")
@ -112,8 +112,9 @@ class InboxIssueViewSet(BaseViewSet):
.values("count") .values("count")
) )
.annotate( .annotate(
attachment_count=IssueAttachment.objects.filter( attachment_count=FileAsset.objects.filter(
issue=OuterRef("id") entity_identifier=OuterRef("id"),
entity_type="issue_attachment",
) )
.order_by() .order_by()
.annotate(count=Func(F("id"), function="Count")) .annotate(count=Func(F("id"), function="Count"))
@ -131,8 +132,14 @@ class InboxIssueViewSet(BaseViewSet):
def list(self, request, slug, project_id, inbox_id): def list(self, request, slug, project_id, inbox_id):
filters = issue_filters(request.query_params, "GET") filters = issue_filters(request.query_params, "GET")
issue_queryset = self.get_queryset().filter(**filters).order_by("issue_inbox__snoozed_till", "issue_inbox__status") issue_queryset = (
issues_data = IssueSerializer(issue_queryset, expand=self.expand, many=True).data self.get_queryset()
.filter(**filters)
.order_by("issue_inbox__snoozed_till", "issue_inbox__status")
)
issues_data = IssueSerializer(
issue_queryset, expand=self.expand, many=True
).data
return Response( return Response(
issues_data, issues_data,
status=status.HTTP_200_OK, status=status.HTTP_200_OK,
@ -199,8 +206,8 @@ class InboxIssueViewSet(BaseViewSet):
source=request.data.get("source", "in-app"), source=request.data.get("source", "in-app"),
) )
issue = (self.get_queryset().filter(pk=issue.id).first()) issue = self.get_queryset().filter(pk=issue.id).first()
serializer = IssueSerializer(issue ,expand=self.expand) serializer = IssueSerializer(issue, expand=self.expand)
return Response(serializer.data, status=status.HTTP_200_OK) return Response(serializer.data, status=status.HTTP_200_OK)
def partial_update(self, request, slug, project_id, inbox_id, issue_id): def partial_update(self, request, slug, project_id, inbox_id, issue_id):
@ -320,20 +327,23 @@ class InboxIssueViewSet(BaseViewSet):
if state is not None: if state is not None:
issue.state = state issue.state = state
issue.save() issue.save()
issue = (self.get_queryset().filter(pk=issue_id).first()) issue = self.get_queryset().filter(pk=issue_id).first()
serializer = IssueSerializer(issue, expand=self.expand) serializer = IssueSerializer(issue, expand=self.expand)
return Response(serializer.data, status=status.HTTP_200_OK) return Response(serializer.data, status=status.HTTP_200_OK)
return Response( return Response(
serializer.errors, status=status.HTTP_400_BAD_REQUEST serializer.errors, status=status.HTTP_400_BAD_REQUEST
) )
else: else:
issue = (self.get_queryset().filter(pk=issue_id).first()) issue = self.get_queryset().filter(pk=issue_id).first()
serializer = IssueSerializer(issue ,expand=self.expand) serializer = IssueSerializer(issue, expand=self.expand)
return Response(serializer.data, status=status.HTTP_200_OK) return Response(serializer.data, status=status.HTTP_200_OK)
def retrieve(self, request, slug, project_id, inbox_id, issue_id): def retrieve(self, request, slug, project_id, inbox_id, issue_id):
issue = self.get_queryset().filter(pk=issue_id).first() issue = self.get_queryset().filter(pk=issue_id).first()
serializer = IssueSerializer(issue, expand=self.expand,) serializer = IssueSerializer(
issue,
expand=self.expand,
)
return Response(serializer.data, status=status.HTTP_200_OK) return Response(serializer.data, status=status.HTTP_200_OK)
def destroy(self, request, slug, project_id, inbox_id, issue_id): def destroy(self, request, slug, project_id, inbox_id, issue_id):

View File

@ -4,7 +4,7 @@ import random
from itertools import chain from itertools import chain
# Django imports # Django imports
from django.db import models from django.http import HttpResponseRedirect
from django.utils import timezone from django.utils import timezone
from django.db.models import ( from django.db.models import (
Prefetch, Prefetch,
@ -29,7 +29,7 @@ from django.db import IntegrityError
# Third Party imports # Third Party imports
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework import status from rest_framework import status
from rest_framework.parsers import MultiPartParser, FormParser from rest_framework.parsers import MultiPartParser, FormParser, JSONParser
# Module imports # Module imports
from . import BaseViewSet, BaseAPIView, WebhookMixin from . import BaseViewSet, BaseAPIView, WebhookMixin
@ -43,13 +43,13 @@ from plane.app.serializers import (
IssueFlatSerializer, IssueFlatSerializer,
IssueLinkSerializer, IssueLinkSerializer,
IssueLiteSerializer, IssueLiteSerializer,
IssueAttachmentSerializer,
IssueSubscriberSerializer, IssueSubscriberSerializer,
ProjectMemberLiteSerializer, ProjectMemberLiteSerializer,
IssueReactionSerializer, IssueReactionSerializer,
CommentReactionSerializer, CommentReactionSerializer,
IssueRelationSerializer, IssueRelationSerializer,
RelatedIssueSerializer, RelatedIssueSerializer,
FileAssetSerializer,
) )
from plane.app.permissions import ( from plane.app.permissions import (
ProjectEntityPermission, ProjectEntityPermission,
@ -58,6 +58,7 @@ from plane.app.permissions import (
ProjectLitePermission, ProjectLitePermission,
) )
from plane.db.models import ( from plane.db.models import (
Workspace,
Project, Project,
Issue, Issue,
IssueActivity, IssueActivity,
@ -65,7 +66,7 @@ from plane.db.models import (
IssueProperty, IssueProperty,
Label, Label,
IssueLink, IssueLink,
IssueAttachment, FileAsset,
State, State,
IssueSubscriber, IssueSubscriber,
ProjectMember, ProjectMember,
@ -75,10 +76,12 @@ from plane.db.models import (
IssueVote, IssueVote,
IssueRelation, IssueRelation,
ProjectPublicMember, ProjectPublicMember,
FileAsset,
) )
from plane.bgtasks.issue_activites_task import issue_activity from plane.bgtasks.issue_activites_task import issue_activity
from plane.utils.grouper import group_results from plane.utils.grouper import group_results
from plane.utils.issue_filters import issue_filters from plane.utils.issue_filters import issue_filters
from plane.utils.presigned_url_generator import generate_download_presigned_url
from collections import defaultdict from collections import defaultdict
@ -128,8 +131,9 @@ class IssueViewSet(WebhookMixin, BaseViewSet):
.values("count") .values("count")
) )
.annotate( .annotate(
attachment_count=IssueAttachment.objects.filter( attachment_count=FileAsset.objects.filter(
issue=OuterRef("id") entity_identifier=OuterRef("id"),
entity_type="issue_attachment",
) )
.order_by() .order_by()
.annotate(count=Func(F("id"), function="Count")) .annotate(count=Func(F("id"), function="Count"))
@ -372,8 +376,9 @@ class UserWorkSpaceIssues(BaseAPIView):
.values("count") .values("count")
) )
.annotate( .annotate(
attachment_count=IssueAttachment.objects.filter( attachment_count=FileAsset.objects.filter(
issue=OuterRef("id") entity_identifier=OuterRef("id"),
entity_type="issue_attachment",
) )
.order_by() .order_by()
.annotate(count=Func(F("id"), function="Count")) .annotate(count=Func(F("id"), function="Count"))
@ -792,8 +797,9 @@ class SubIssuesEndpoint(BaseAPIView):
.values("count") .values("count")
) )
.annotate( .annotate(
attachment_count=IssueAttachment.objects.filter( attachment_count=FileAsset.objects.filter(
issue=OuterRef("id") entity_identifier=OuterRef("id"),
entity_type="issue_attachment",
) )
.order_by() .order_by()
.annotate(count=Func(F("id"), function="Count")) .annotate(count=Func(F("id"), function="Count"))
@ -1010,17 +1016,21 @@ class BulkCreateIssueLabelsEndpoint(BaseAPIView):
class IssueAttachmentEndpoint(BaseAPIView): class IssueAttachmentEndpoint(BaseAPIView):
serializer_class = IssueAttachmentSerializer
permission_classes = [ permission_classes = [
ProjectEntityPermission, ProjectEntityPermission,
] ]
model = IssueAttachment parser_classes = (MultiPartParser, FormParser, JSONParser,)
parser_classes = (MultiPartParser, FormParser)
def post(self, request, slug, project_id, issue_id): def post(self, request, slug, project_id, issue_id):
serializer = IssueAttachmentSerializer(data=request.data) serializer = FileAssetSerializer(data=request.data)
workspace = Workspace.objects.get(slug=slug)
if serializer.is_valid(): if serializer.is_valid():
serializer.save(project_id=project_id, issue_id=issue_id) serializer.save(
workspace=workspace,
project_id=project_id,
entity_type="issue_attachment",
entity_identifier=issue_id,
)
issue_activity.delay( issue_activity.delay(
type="attachment.activity.created", type="attachment.activity.created",
requested_data=None, requested_data=None,
@ -1038,10 +1048,19 @@ class IssueAttachmentEndpoint(BaseAPIView):
return Response(serializer.data, status=status.HTTP_201_CREATED) return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
def delete(self, request, slug, project_id, issue_id, pk): def delete(
issue_attachment = IssueAttachment.objects.get(pk=pk) self, request, slug, project_id, issue_id, workspace_id, asset_key
issue_attachment.asset.delete(save=False) ):
issue_attachment.delete() key = f"{workspace_id}/{asset_key}"
asset = FileAsset.objects.get(
asset=key,
entity_identifier=issue_id,
entity_type="issue_attachment",
workspace__slug=slug,
project_id=project_id,
)
asset.is_deleted = True
asset.save()
issue_activity.delay( issue_activity.delay(
type="attachment.activity.deleted", type="attachment.activity.deleted",
requested_data=None, requested_data=None,
@ -1053,14 +1072,30 @@ class IssueAttachmentEndpoint(BaseAPIView):
notification=True, notification=True,
origin=request.META.get("HTTP_ORIGIN"), origin=request.META.get("HTTP_ORIGIN"),
) )
return Response(status=status.HTTP_204_NO_CONTENT) return Response(status=status.HTTP_204_NO_CONTENT)
def get(self, request, slug, project_id, issue_id): def get(
issue_attachments = IssueAttachment.objects.filter( self,
issue_id=issue_id, workspace__slug=slug, project_id=project_id request,
slug,
project_id,
issue_id,
workspace_id=None,
asset_key=None,
):
if workspace_id and asset_key:
key = f"{workspace_id}/{asset_key}"
url = generate_download_presigned_url(key)
return HttpResponseRedirect(url)
# For listing
issue_attachments = FileAsset.objects.filter(
entity_type="issue_attachment",
entity_identifier=issue_id,
workspace__slug=slug,
project_id=project_id,
) )
serializer = IssueAttachmentSerializer(issue_attachments, many=True) serializer = FileAssetSerializer(issue_attachments, many=True)
return Response(serializer.data, status=status.HTTP_200_OK) return Response(serializer.data, status=status.HTTP_200_OK)
@ -1084,7 +1119,7 @@ class IssueArchiveViewSet(BaseViewSet):
.filter(workspace__slug=self.kwargs.get("slug")) .filter(workspace__slug=self.kwargs.get("slug"))
.select_related("workspace", "project", "state", "parent") .select_related("workspace", "project", "state", "parent")
.prefetch_related("assignees", "labels", "issue_module__module") .prefetch_related("assignees", "labels", "issue_module__module")
.annotate(cycle_id=F("issue_cycle__cycle_id")) .annotate(cycle_id=F("issue_cycle__cycle_id"))
.annotate( .annotate(
link_count=IssueLink.objects.filter(issue=OuterRef("id")) link_count=IssueLink.objects.filter(issue=OuterRef("id"))
.order_by() .order_by()
@ -1092,8 +1127,9 @@ class IssueArchiveViewSet(BaseViewSet):
.values("count") .values("count")
) )
.annotate( .annotate(
attachment_count=IssueAttachment.objects.filter( attachment_count=FileAsset.objects.filter(
issue=OuterRef("id") entity_identifier=OuterRef("id"),
entity_type="issue_attachment",
) )
.order_by() .order_by()
.annotate(count=Func(F("id"), function="Count")) .annotate(count=Func(F("id"), function="Count"))
@ -1131,10 +1167,7 @@ class IssueArchiveViewSet(BaseViewSet):
order_by_param = request.GET.get("order_by", "-created_at") order_by_param = request.GET.get("order_by", "-created_at")
issue_queryset = ( issue_queryset = self.get_queryset().filter(**filters)
self.get_queryset()
.filter(**filters)
)
# Priority Ordering # Priority Ordering
if order_by_param == "priority" or order_by_param == "-priority": if order_by_param == "priority" or order_by_param == "-priority":
@ -1579,15 +1612,17 @@ class IssueRelationViewSet(BaseViewSet):
issue_relation = IssueRelation.objects.bulk_create( issue_relation = IssueRelation.objects.bulk_create(
[ [
IssueRelation( IssueRelation(
issue_id=issue issue_id=(
if relation_type == "blocking" issue if relation_type == "blocking" else issue_id
else issue_id, ),
related_issue_id=issue_id related_issue_id=(
if relation_type == "blocking" issue_id if relation_type == "blocking" else issue
else issue, ),
relation_type="blocked_by" relation_type=(
if relation_type == "blocking" "blocked_by"
else relation_type, if relation_type == "blocking"
else relation_type
),
project_id=project_id, project_id=project_id,
workspace_id=project.workspace_id, workspace_id=project.workspace_id,
created_by=request.user, created_by=request.user,
@ -1695,8 +1730,9 @@ class IssueDraftViewSet(BaseViewSet):
.values("count") .values("count")
) )
.annotate( .annotate(
attachment_count=IssueAttachment.objects.filter( attachment_count=FileAsset.objects.filter(
issue=OuterRef("id") entity_identifier=OuterRef("id"),
entity_type="issue_attachment",
) )
.order_by() .order_by()
.annotate(count=Func(F("id"), function="Count")) .annotate(count=Func(F("id"), function="Count"))
@ -1733,10 +1769,7 @@ class IssueDraftViewSet(BaseViewSet):
order_by_param = request.GET.get("order_by", "-created_at") order_by_param = request.GET.get("order_by", "-created_at")
issue_queryset = ( issue_queryset = self.get_queryset().filter(**filters)
self.get_queryset()
.filter(**filters)
)
# Priority Ordering # Priority Ordering
if order_by_param == "priority" or order_by_param == "-priority": if order_by_param == "priority" or order_by_param == "-priority":

View File

@ -37,7 +37,7 @@ from plane.db.models import (
ModuleLink, ModuleLink,
ModuleFavorite, ModuleFavorite,
IssueLink, IssueLink,
IssueAttachment, FileAsset,
IssueSubscriber, IssueSubscriber,
ModuleUserProperties, ModuleUserProperties,
) )
@ -331,17 +331,16 @@ class ModuleIssueViewSet(WebhookMixin, BaseViewSet):
ProjectEntityPermission, ProjectEntityPermission,
] ]
def get_queryset(self): def get_queryset(self):
return ( return (
Issue.objects.filter( Issue.objects.filter(
project_id=self.kwargs.get("project_id"), project_id=self.kwargs.get("project_id"),
workspace__slug=self.kwargs.get("slug"), workspace__slug=self.kwargs.get("slug"),
issue_module__module_id=self.kwargs.get("module_id") issue_module__module_id=self.kwargs.get("module_id"),
) )
.select_related("workspace", "project", "state", "parent") .select_related("workspace", "project", "state", "parent")
.prefetch_related("labels", "assignees") .prefetch_related("labels", "assignees")
.prefetch_related('issue_module__module') .prefetch_related("issue_module__module")
.annotate(cycle_id=F("issue_cycle__cycle_id")) .annotate(cycle_id=F("issue_cycle__cycle_id"))
.annotate( .annotate(
link_count=IssueLink.objects.filter(issue=OuterRef("id")) link_count=IssueLink.objects.filter(issue=OuterRef("id"))
@ -350,8 +349,9 @@ class ModuleIssueViewSet(WebhookMixin, BaseViewSet):
.values("count") .values("count")
) )
.annotate( .annotate(
attachment_count=IssueAttachment.objects.filter( attachment_count=FileAsset.objects.filter(
issue=OuterRef("id") entity_identifier=OuterRef("id"),
entity_type="issue_attachment",
) )
.order_by() .order_by()
.annotate(count=Func(F("id"), function="Count")) .annotate(count=Func(F("id"), function="Count"))
@ -420,10 +420,9 @@ class ModuleIssueViewSet(WebhookMixin, BaseViewSet):
) )
for issue in issues for issue in issues
] ]
issues = (self.get_queryset().filter(pk__in=issues)) issues = self.get_queryset().filter(pk__in=issues)
serializer = IssueSerializer(issues , many=True) serializer = IssueSerializer(issues, many=True)
return Response(serializer.data, status=status.HTTP_201_CREATED) return Response(serializer.data, status=status.HTTP_201_CREATED)
# create multiple module inside an issue # create multiple module inside an issue
def create_issue_modules(self, request, slug, project_id, issue_id): def create_issue_modules(self, request, slug, project_id, issue_id):
@ -466,11 +465,10 @@ class ModuleIssueViewSet(WebhookMixin, BaseViewSet):
for module in modules for module in modules
] ]
issue = (self.get_queryset().filter(pk=issue_id).first()) issue = self.get_queryset().filter(pk=issue_id).first()
serializer = IssueSerializer(issue) serializer = IssueSerializer(issue)
return Response(serializer.data, status=status.HTTP_201_CREATED) return Response(serializer.data, status=status.HTTP_201_CREATED)
def destroy(self, request, slug, project_id, module_id, issue_id): def destroy(self, request, slug, project_id, module_id, issue_id):
module_issue = ModuleIssue.objects.get( module_issue = ModuleIssue.objects.get(
workspace__slug=slug, workspace__slug=slug,
@ -484,7 +482,9 @@ class ModuleIssueViewSet(WebhookMixin, BaseViewSet):
actor_id=str(request.user.id), actor_id=str(request.user.id),
issue_id=str(issue_id), issue_id=str(issue_id),
project_id=str(project_id), project_id=str(project_id),
current_instance=json.dumps({"module_name": module_issue.module.name}), current_instance=json.dumps(
{"module_name": module_issue.module.name}
),
epoch=int(timezone.now().timestamp()), epoch=int(timezone.now().timestamp()),
notification=True, notification=True,
origin=request.META.get("HTTP_ORIGIN"), origin=request.META.get("HTTP_ORIGIN"),

View File

@ -41,7 +41,7 @@ from plane.db.models import (
IssueViewFavorite, IssueViewFavorite,
IssueReaction, IssueReaction,
IssueLink, IssueLink,
IssueAttachment, FileAsset,
IssueSubscriber, IssueSubscriber,
) )
from plane.utils.issue_filters import issue_filters from plane.utils.issue_filters import issue_filters
@ -130,8 +130,9 @@ class GlobalViewIssuesViewSet(BaseViewSet):
.values("count") .values("count")
) )
.annotate( .annotate(
attachment_count=IssueAttachment.objects.filter( attachment_count=FileAsset.objects.filter(
issue=OuterRef("id") entity_identifier=OuterRef("id"),
entity_type="issue_attachment",
) )
.order_by() .order_by()
.annotate(count=Func(F("id"), function="Count")) .annotate(count=Func(F("id"), function="Count"))

View File

@ -66,7 +66,7 @@ from plane.db.models import (
Issue, Issue,
WorkspaceTheme, WorkspaceTheme,
IssueLink, IssueLink,
IssueAttachment, FileAsset,
IssueSubscriber, IssueSubscriber,
Project, Project,
Label, Label,
@ -1360,8 +1360,9 @@ class WorkspaceUserProfileIssuesEndpoint(BaseAPIView):
.values("count") .values("count")
) )
.annotate( .annotate(
attachment_count=IssueAttachment.objects.filter( attachment_count=FileAsset.objects.filter(
issue=OuterRef("id") entity_identifier=OuterRef("id"),
entity_type="issue_attachment",
) )
.order_by() .order_by()
.annotate(count=Func(F("id"), function="Count")) .annotate(count=Func(F("id"), function="Count"))
@ -1558,10 +1559,14 @@ class WorkspaceLogoEndpoint(BaseAPIView):
workspace = Workspace.objects.get(slug=slug) workspace = Workspace.objects.get(slug=slug)
if serializer.is_valid(): if serializer.is_valid():
serializer.save(workspace=workspace) serializer.save(workspace=workspace)
workspace.logo = f"/api/workspaces/{slug}/logo/{serializer.data['asset']}/" workspace.logo = (
f"/api/workspaces/{slug}/logo/{serializer.data['asset']}/"
)
workspace.save() workspace.save()
workspace_serializer = WorkSpaceSerializer(workspace) workspace_serializer = WorkSpaceSerializer(workspace)
return Response(workspace_serializer.data, status=status.HTTP_201_CREATED) return Response(
workspace_serializer.data, 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)
def delete(self, request, slug, workspace_id, logo_key): def delete(self, request, slug, workspace_id, logo_key):

View File

@ -224,7 +224,8 @@ class Migration(migrations.Migration):
name="entity_type", name="entity_type",
field=models.CharField( field=models.CharField(
choices=[ choices=[
("issue", "Issue"), ("issue_attachment", "Issue Attachment"),
("issue_description", "Issue Description"),
("comment", "Comment"), ("comment", "Comment"),
("page", "Page"), ("page", "Page"),
], ],

View File

@ -67,7 +67,7 @@ def convert_issue_description_image_sources(apps, schema_editor):
for asset in FileAsset.objects.filter(asset__in=file_assets.keys()): for asset in FileAsset.objects.filter(asset__in=file_assets.keys()):
asset.project_id = file_assets[str(asset.asset)]["project_id"] asset.project_id = file_assets[str(asset.asset)]["project_id"]
asset.entity_identifier = file_assets[str(asset.asset)]["issue_id"] asset.entity_identifier = file_assets[str(asset.asset)]["issue_id"]
asset.entity_type = "issue" asset.entity_type = "issue_description"
bulk_assets.append(asset) bulk_assets.append(asset)
FileAsset.objects.bulk_update( FileAsset.objects.bulk_update(

View File

@ -4,6 +4,31 @@ from django.db import migrations, models
import django.db.models.deletion import django.db.models.deletion
def create_attachment_assets(apps, schema_editor):
bulk_assets = []
issue_attachments = {}
FileAsset = apps.get_model("db", "FileAsset")
IssueAttachment = apps.get_model("db", "IssueAttachment")
for issue_attachment in IssueAttachment.objects.values():
bulk_assets.append(
FileAsset(
workspace_id=issue_attachment["workspace_id"],
project_id=issue_attachment["project_id"],
entity_identifier=issue_attachment["issue_id"],
entity_type="issue_attachment",
asset=issue_attachment["asset"],
attributes=issue_attachment["attributes"],
)
)
issue_attachments[str(issue_attachment["asset"])] = str(
issue_attachment["id"]
)
FileAsset.objects.bulk_create(bulk_assets, batch_size=100)
class Migration(migrations.Migration): class Migration(migrations.Migration):
@ -12,21 +37,8 @@ class Migration(migrations.Migration):
] ]
operations = [ operations = [
migrations.AddField( migrations.RunPython(create_attachment_assets),
model_name="issueattachment", migrations.DeleteModel(
name="asset_key", name="IssueAttachment",
field=models.ForeignKey(
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name="issue_assets",
to="db.fileasset",
),
),
migrations.AddField(
model_name="issueattachment",
name="type",
field=models.PositiveSmallIntegerField(
choices=[(0, "Attachment"), (1, "Description")], default=0
),
), ),
] ]

View File

@ -1,74 +0,0 @@
# Generated by Django 4.2.7 on 2024-02-05 07:03
from django.db import migrations
def update_attachment_assets(apps, schema_editor):
pass
def create_description_assets(apps, schema_editor):
FileAsset = apps.get_model("db", "FileAsset")
IssueAttachment = apps.get_model("db", "IssueAttachment")
bulk_issue_attachments = []
for asset in FileAsset.objects.filter(entity_type="issue").values():
bulk_issue_attachments.append(
IssueAttachment(
workspace_id=asset["workspace_id"],
project_id=asset["project_id"],
issue_id=asset["entity_identifier"],
asset_key_id=asset["id"],
type=1,
)
)
IssueAttachment.objects.bulk_create(bulk_issue_attachments, batch_size=100)
def create_attachment_assets(apps, schema_editor):
bulk_assets = []
issue_attachments = {}
FileAsset = apps.get_model("db", "FileAsset")
IssueAttachment = apps.get_model("db", "IssueAttachment")
for issue_attachment in IssueAttachment.objects.filter(type=0).values():
bulk_assets.append(
FileAsset(
workspace_id=issue_attachment["workspace_id"],
project_id=issue_attachment["project_id"],
entity_identifier=issue_attachment["issue_id"],
entity_type="issue",
asset=issue_attachment["asset"],
attributes=issue_attachment["attributes"],
)
)
issue_attachments[str(issue_attachment["asset"])] = str(issue_attachment["id"])
FileAsset.objects.bulk_create(bulk_assets, batch_size=100)
assets = FileAsset.objects.filter(asset__in=issue_attachments.keys()).values("id", "asset")
bulk_issue_attachments = []
for issue_attachment in IssueAttachment.objects.filter(type=0):
asset_key_id = [asset.get("id") for asset in assets if str(asset.get("asset")) == str(issue_attachment.asset)]
if asset_key_id:
issue_attachment.asset_key_id = str(asset_key_id[0])
bulk_issue_attachments.append(issue_attachment)
IssueAttachment.objects.bulk_update(bulk_issue_attachments, ["asset_key"], batch_size=100)
class Migration(migrations.Migration):
dependencies = [
("db", "0061_issueattachment_asset_key_issueattachment_type"),
]
operations = [
migrations.RunPython(create_description_assets),
migrations.RunPython(create_attachment_assets),
]

View File

@ -37,7 +37,6 @@ from .issue import (
IssueMention, IssueMention,
IssueLink, IssueLink,
IssueSequence, IssueSequence,
IssueAttachment,
IssueSubscriber, IssueSubscriber,
IssueReaction, IssueReaction,
CommentReaction, CommentReaction,

View File

@ -48,7 +48,8 @@ class FileAsset(BaseModel):
) )
entity_type = models.CharField( entity_type = models.CharField(
choices=( choices=(
("issue", "Issue"), ("issue_attachment", "Issue Attachment"),
("issue_description", "Issue Description"),
("comment", "Comment"), ("comment", "Comment"),
("page", "Page"), ("page", "Page"),
), ),

View File

@ -345,42 +345,6 @@ def file_size(value):
if value.size > settings.FILE_SIZE_LIMIT: if value.size > settings.FILE_SIZE_LIMIT:
raise ValidationError("File too large. Size should not exceed 5 MB.") raise ValidationError("File too large. Size should not exceed 5 MB.")
class IssueAttachment(ProjectBaseModel):
attributes = models.JSONField(default=dict)
asset = models.FileField(
upload_to=get_upload_path,
validators=[
file_size,
],
)
asset_key = models.ForeignKey(
"db.FileAsset",
on_delete=models.CASCADE,
related_name="issue_assets",
null=True,
)
issue = models.ForeignKey(
"db.Issue", on_delete=models.CASCADE, related_name="issue_attachment"
)
type = models.PositiveSmallIntegerField(
choices=(
(0, "Attachment"),
(1, "Description"),
),
default=0,
)
class Meta:
verbose_name = "Issue Attachment"
verbose_name_plural = "Issue Attachments"
db_table = "issue_attachments"
ordering = ("-created_at",)
def __str__(self):
return f"{self.issue.name} {self.asset}"
class IssueActivity(ProjectBaseModel): class IssueActivity(ProjectBaseModel):
issue = models.ForeignKey( issue = models.ForeignKey(
Issue, Issue,

View File

@ -22,7 +22,7 @@ from plane.db.models import (
CycleIssue, CycleIssue,
ModuleIssue, ModuleIssue,
IssueLink, IssueLink,
IssueAttachment, FileAsset,
IssueReaction, IssueReaction,
CommentReaction, CommentReaction,
IssueVote, IssueVote,
@ -171,22 +171,6 @@ class IssueLinkSerializer(BaseSerializer):
) )
return IssueLink.objects.create(**validated_data) return IssueLink.objects.create(**validated_data)
class IssueAttachmentSerializer(BaseSerializer):
class Meta:
model = IssueAttachment
fields = "__all__"
read_only_fields = [
"created_by",
"updated_by",
"created_at",
"updated_at",
"workspace",
"project",
"issue",
]
class IssueReactionSerializer(BaseSerializer): class IssueReactionSerializer(BaseSerializer):
actor_detail = UserLiteSerializer(read_only=True, source="actor") actor_detail = UserLiteSerializer(read_only=True, source="actor")
@ -218,7 +202,6 @@ class IssueSerializer(BaseSerializer):
issue_cycle = IssueCycleDetailSerializer(read_only=True) issue_cycle = IssueCycleDetailSerializer(read_only=True)
issue_module = IssueModuleDetailSerializer(read_only=True) issue_module = IssueModuleDetailSerializer(read_only=True)
issue_link = IssueLinkSerializer(read_only=True, many=True) issue_link = IssueLinkSerializer(read_only=True, many=True)
issue_attachment = IssueAttachmentSerializer(read_only=True, many=True)
sub_issues_count = serializers.IntegerField(read_only=True) sub_issues_count = serializers.IntegerField(read_only=True)
issue_reactions = IssueReactionSerializer(read_only=True, many=True) issue_reactions = IssueReactionSerializer(read_only=True, many=True)

View File

@ -17,7 +17,7 @@ from plane.db.models import (
Issue, Issue,
State, State,
IssueLink, IssueLink,
IssueAttachment, FileAsset,
ProjectDeployBoard, ProjectDeployBoard,
) )
from plane.app.serializers import ( from plane.app.serializers import (
@ -95,8 +95,9 @@ class InboxIssuePublicViewSet(BaseViewSet):
.values("count") .values("count")
) )
.annotate( .annotate(
attachment_count=IssueAttachment.objects.filter( attachment_count=FileAsset.objects.filter(
issue=OuterRef("id") entity_identifier=OuterRef("id"),
entity_type="issue_attachment",
) )
.order_by() .order_by()
.annotate(count=Func(F("id"), function="Count")) .annotate(count=Func(F("id"), function="Count"))

View File

@ -40,7 +40,7 @@ from plane.db.models import (
IssueComment, IssueComment,
Label, Label,
IssueLink, IssueLink,
IssueAttachment, FileAsset,
State, State,
ProjectMember, ProjectMember,
IssueReaction, IssueReaction,
@ -567,8 +567,9 @@ class ProjectIssuesPublicEndpoint(BaseAPIView):
.values("count") .values("count")
) )
.annotate( .annotate(
attachment_count=IssueAttachment.objects.filter( attachment_count=FileAsset.objects.filter(
issue=OuterRef("id") entity_identifier=OuterRef("id"),
entity_type="issue_attachment",
) )
.order_by() .order_by()
.annotate(count=Func(F("id"), function="Count")) .annotate(count=Func(F("id"), function="Count"))