From 02e5e0da4b9d033c266674b5cec7c9ad3b98e72d Mon Sep 17 00:00:00 2001 From: pablohashescobar Date: Mon, 5 Feb 2024 14:30:49 +0530 Subject: [PATCH] dev: update attachments for issues --- apiserver/plane/api/serializers/__init__.py | 2 - apiserver/plane/api/serializers/issue.py | 17 --- apiserver/plane/api/views/cycle.py | 7 +- apiserver/plane/api/views/issue.py | 7 +- apiserver/plane/api/views/module.py | 7 +- apiserver/plane/app/serializers/__init__.py | 1 - apiserver/plane/app/serializers/issue.py | 18 +-- apiserver/plane/app/urls/issue.py | 4 +- apiserver/plane/app/views/cycle.py | 4 +- apiserver/plane/app/views/dashboard.py | 12 +- apiserver/plane/app/views/inbox.py | 34 +++-- apiserver/plane/app/views/issue.py | 125 +++++++++++------- apiserver/plane/app/views/module.py | 24 ++-- apiserver/plane/app/views/view.py | 7 +- apiserver/plane/app/views/workspace.py | 15 ++- .../db/migrations/0059_auto_20240131_1334.py | 3 +- .../db/migrations/0060_fileasset_size.py | 2 +- ...tachment_asset_key_issueattachment_type.py | 44 +++--- .../db/migrations/0062_auto_20240205_0703.py | 74 ----------- apiserver/plane/db/models/__init__.py | 1 - apiserver/plane/db/models/asset.py | 3 +- apiserver/plane/db/models/issue.py | 36 ----- apiserver/plane/space/serializer/issue.py | 19 +-- apiserver/plane/space/views/inbox.py | 7 +- apiserver/plane/space/views/issue.py | 7 +- 25 files changed, 193 insertions(+), 287 deletions(-) delete mode 100644 apiserver/plane/db/migrations/0062_auto_20240205_0703.py diff --git a/apiserver/plane/api/serializers/__init__.py b/apiserver/plane/api/serializers/__init__.py index 10b0182d6..e5e4c0bb4 100644 --- a/apiserver/plane/api/serializers/__init__.py +++ b/apiserver/plane/api/serializers/__init__.py @@ -5,9 +5,7 @@ from .issue import ( IssueSerializer, LabelSerializer, IssueLinkSerializer, - IssueAttachmentSerializer, IssueCommentSerializer, - IssueAttachmentSerializer, IssueActivitySerializer, IssueExpandSerializer, ) diff --git a/apiserver/plane/api/serializers/issue.py b/apiserver/plane/api/serializers/issue.py index 4c8d6e815..22d801a62 100644 --- a/apiserver/plane/api/serializers/issue.py +++ b/apiserver/plane/api/serializers/issue.py @@ -17,7 +17,6 @@ from plane.db.models import ( IssueLabel, IssueLink, IssueComment, - IssueAttachment, IssueActivity, ProjectMember, ) @@ -296,22 +295,6 @@ class IssueLinkSerializer(BaseSerializer): 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): is_member = serializers.BooleanField(read_only=True) diff --git a/apiserver/plane/api/views/cycle.py b/apiserver/plane/api/views/cycle.py index c296bb111..83f7ca37c 100644 --- a/apiserver/plane/api/views/cycle.py +++ b/apiserver/plane/api/views/cycle.py @@ -17,7 +17,7 @@ from plane.db.models import ( Issue, CycleIssue, IssueLink, - IssueAttachment, + FileAsset, ) from plane.app.permissions import ProjectEntityPermission from plane.api.serializers import ( @@ -390,8 +390,9 @@ class CycleIssueAPIEndpoint(WebhookMixin, BaseAPIView): .values("count") ) .annotate( - attachment_count=IssueAttachment.objects.filter( - issue=OuterRef("id") + attachment_count=FileAsset.objects.filter( + entity_identifier=OuterRef("id"), + entity_type="issue_attachment", ) .order_by() .annotate(count=Func(F("id"), function="Count")) diff --git a/apiserver/plane/api/views/issue.py b/apiserver/plane/api/views/issue.py index 2b8de68fe..8b5f72206 100644 --- a/apiserver/plane/api/views/issue.py +++ b/apiserver/plane/api/views/issue.py @@ -31,13 +31,13 @@ from plane.app.permissions import ( ) from plane.db.models import ( Issue, - IssueAttachment, IssueLink, Project, Label, ProjectMember, IssueComment, IssueActivity, + FileAsset, ) from plane.bgtasks.issue_activites_task import issue_activity from plane.api.serializers import ( @@ -126,8 +126,9 @@ class IssueAPIEndpoint(WebhookMixin, BaseAPIView): .values("count") ) .annotate( - attachment_count=IssueAttachment.objects.filter( - issue=OuterRef("id") + attachment_count=FileAsset.objects.filter( + entity_identifier=OuterRef("id"), + entity_type="issue_attachment", ) .order_by() .annotate(count=Func(F("id"), function="Count")) diff --git a/apiserver/plane/api/views/module.py b/apiserver/plane/api/views/module.py index 1a9a21a3c..bf760695d 100644 --- a/apiserver/plane/api/views/module.py +++ b/apiserver/plane/api/views/module.py @@ -19,8 +19,8 @@ from plane.db.models import ( ModuleLink, Issue, ModuleIssue, - IssueAttachment, IssueLink, + FileAsset, ) from plane.api.serializers import ( ModuleSerializer, @@ -273,8 +273,9 @@ class ModuleIssueAPIEndpoint(WebhookMixin, BaseAPIView): .values("count") ) .annotate( - attachment_count=IssueAttachment.objects.filter( - issue=OuterRef("id") + attachment_count=FileAsset.objects.filter( + entity_identifier=OuterRef("id"), + entity_type="issue_attachment", ) .order_by() .annotate(count=Func(F("id"), function="Count")) diff --git a/apiserver/plane/app/serializers/__init__.py b/apiserver/plane/app/serializers/__init__.py index 0d72f9192..85fd2e2c5 100644 --- a/apiserver/plane/app/serializers/__init__.py +++ b/apiserver/plane/app/serializers/__init__.py @@ -60,7 +60,6 @@ from .issue import ( IssueStateSerializer, IssueLinkSerializer, IssueLiteSerializer, - IssueAttachmentSerializer, IssueSubscriberSerializer, IssueReactionSerializer, CommentReactionSerializer, diff --git a/apiserver/plane/app/serializers/issue.py b/apiserver/plane/app/serializers/issue.py index e6425a6a3..2c5ab86b7 100644 --- a/apiserver/plane/app/serializers/issue.py +++ b/apiserver/plane/app/serializers/issue.py @@ -25,7 +25,7 @@ from plane.db.models import ( Module, ModuleIssue, IssueLink, - IssueAttachment, + FileAsset, IssueReaction, CommentReaction, IssueVote, @@ -444,22 +444,6 @@ class IssueLinkSerializer(BaseSerializer): 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): actor_detail = UserLiteSerializer(read_only=True, source="actor") diff --git a/apiserver/plane/app/urls/issue.py b/apiserver/plane/app/urls/issue.py index 234c2824d..a098dee70 100644 --- a/apiserver/plane/app/urls/issue.py +++ b/apiserver/plane/app/urls/issue.py @@ -117,12 +117,12 @@ urlpatterns = [ name="project-issue-links", ), path( - "workspaces//projects//issues//issue-attachments/", + "workspaces//projects//issues//attachments/", IssueAttachmentEndpoint.as_view(), name="project-issue-attachments", ), path( - "workspaces//projects//issues//issue-attachments//", + "workspaces//projects//issues//attachments///", IssueAttachmentEndpoint.as_view(), name="project-issue-attachments", ), diff --git a/apiserver/plane/app/views/cycle.py b/apiserver/plane/app/views/cycle.py index 23a227fef..535164d69 100644 --- a/apiserver/plane/app/views/cycle.py +++ b/apiserver/plane/app/views/cycle.py @@ -47,10 +47,10 @@ from plane.db.models import ( Issue, CycleFavorite, IssueLink, - IssueAttachment, Label, CycleUserProperties, IssueSubscriber, + FileAsset, ) from plane.bgtasks.issue_activites_task import issue_activity from plane.utils.issue_filters import issue_filters @@ -611,7 +611,7 @@ class CycleIssueViewSet(WebhookMixin, BaseViewSet): .values("count") ) .annotate( - attachment_count=IssueAttachment.objects.filter( + attachment_count=FileAsset.objects.filter( issue=OuterRef("id") ) .order_by() diff --git a/apiserver/plane/app/views/dashboard.py b/apiserver/plane/app/views/dashboard.py index 47fae2c9c..e4ddd31f0 100644 --- a/apiserver/plane/app/views/dashboard.py +++ b/apiserver/plane/app/views/dashboard.py @@ -32,7 +32,7 @@ from plane.db.models import ( Dashboard, Project, IssueLink, - IssueAttachment, + FileAsset, IssueRelation, ) from plane.app.serializers import ( @@ -117,8 +117,9 @@ def dashboard_assigned_issues(self, request, slug): .values("count") ) .annotate( - attachment_count=IssueAttachment.objects.filter( - issue=OuterRef("id") + attachment_count=FileAsset.objects.filter( + entity_identifier=OuterRef("id"), + entity_type="issue_attachment", ) .order_by() .annotate(count=Func(F("id"), function="Count")) @@ -229,8 +230,9 @@ def dashboard_created_issues(self, request, slug): .values("count") ) .annotate( - attachment_count=IssueAttachment.objects.filter( - issue=OuterRef("id") + attachment_count=FileAsset.objects.filter( + entity_identifier=OuterRef("id"), + entity_type="issue_attachment", ) .order_by() .annotate(count=Func(F("id"), function="Count")) diff --git a/apiserver/plane/app/views/inbox.py b/apiserver/plane/app/views/inbox.py index 01eee78e3..3f70be359 100644 --- a/apiserver/plane/app/views/inbox.py +++ b/apiserver/plane/app/views/inbox.py @@ -19,7 +19,7 @@ from plane.db.models import ( Issue, State, IssueLink, - IssueAttachment, + FileAsset, ProjectMember, ) from plane.app.serializers import ( @@ -92,7 +92,7 @@ class InboxIssueViewSet(BaseViewSet): Issue.objects.filter( project_id=self.kwargs.get("project_id"), 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") .prefetch_related("assignees", "labels", "issue_module__module") @@ -112,8 +112,9 @@ class InboxIssueViewSet(BaseViewSet): .values("count") ) .annotate( - attachment_count=IssueAttachment.objects.filter( - issue=OuterRef("id") + attachment_count=FileAsset.objects.filter( + entity_identifier=OuterRef("id"), + entity_type="issue_attachment", ) .order_by() .annotate(count=Func(F("id"), function="Count")) @@ -131,8 +132,14 @@ class InboxIssueViewSet(BaseViewSet): def list(self, request, slug, project_id, inbox_id): filters = issue_filters(request.query_params, "GET") - issue_queryset = 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 + issue_queryset = ( + 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( issues_data, status=status.HTTP_200_OK, @@ -199,8 +206,8 @@ class InboxIssueViewSet(BaseViewSet): source=request.data.get("source", "in-app"), ) - issue = (self.get_queryset().filter(pk=issue.id).first()) - serializer = IssueSerializer(issue ,expand=self.expand) + issue = self.get_queryset().filter(pk=issue.id).first() + serializer = IssueSerializer(issue, expand=self.expand) return Response(serializer.data, status=status.HTTP_200_OK) def partial_update(self, request, slug, project_id, inbox_id, issue_id): @@ -320,20 +327,23 @@ class InboxIssueViewSet(BaseViewSet): if state is not None: issue.state = state 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) return Response(serializer.data, status=status.HTTP_200_OK) return Response( serializer.errors, status=status.HTTP_400_BAD_REQUEST ) else: - issue = (self.get_queryset().filter(pk=issue_id).first()) - serializer = IssueSerializer(issue ,expand=self.expand) + issue = self.get_queryset().filter(pk=issue_id).first() + serializer = IssueSerializer(issue, expand=self.expand) return Response(serializer.data, status=status.HTTP_200_OK) def retrieve(self, request, slug, project_id, inbox_id, issue_id): 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) def destroy(self, request, slug, project_id, inbox_id, issue_id): diff --git a/apiserver/plane/app/views/issue.py b/apiserver/plane/app/views/issue.py index 0b5c612d3..9adf25abb 100644 --- a/apiserver/plane/app/views/issue.py +++ b/apiserver/plane/app/views/issue.py @@ -4,7 +4,7 @@ import random from itertools import chain # Django imports -from django.db import models +from django.http import HttpResponseRedirect from django.utils import timezone from django.db.models import ( Prefetch, @@ -29,7 +29,7 @@ from django.db import IntegrityError # Third Party imports from rest_framework.response import Response from rest_framework import status -from rest_framework.parsers import MultiPartParser, FormParser +from rest_framework.parsers import MultiPartParser, FormParser, JSONParser # Module imports from . import BaseViewSet, BaseAPIView, WebhookMixin @@ -43,13 +43,13 @@ from plane.app.serializers import ( IssueFlatSerializer, IssueLinkSerializer, IssueLiteSerializer, - IssueAttachmentSerializer, IssueSubscriberSerializer, ProjectMemberLiteSerializer, IssueReactionSerializer, CommentReactionSerializer, IssueRelationSerializer, RelatedIssueSerializer, + FileAssetSerializer, ) from plane.app.permissions import ( ProjectEntityPermission, @@ -58,6 +58,7 @@ from plane.app.permissions import ( ProjectLitePermission, ) from plane.db.models import ( + Workspace, Project, Issue, IssueActivity, @@ -65,7 +66,7 @@ from plane.db.models import ( IssueProperty, Label, IssueLink, - IssueAttachment, + FileAsset, State, IssueSubscriber, ProjectMember, @@ -75,10 +76,12 @@ from plane.db.models import ( IssueVote, IssueRelation, ProjectPublicMember, + FileAsset, ) from plane.bgtasks.issue_activites_task import issue_activity from plane.utils.grouper import group_results from plane.utils.issue_filters import issue_filters +from plane.utils.presigned_url_generator import generate_download_presigned_url from collections import defaultdict @@ -128,8 +131,9 @@ class IssueViewSet(WebhookMixin, BaseViewSet): .values("count") ) .annotate( - attachment_count=IssueAttachment.objects.filter( - issue=OuterRef("id") + attachment_count=FileAsset.objects.filter( + entity_identifier=OuterRef("id"), + entity_type="issue_attachment", ) .order_by() .annotate(count=Func(F("id"), function="Count")) @@ -372,8 +376,9 @@ class UserWorkSpaceIssues(BaseAPIView): .values("count") ) .annotate( - attachment_count=IssueAttachment.objects.filter( - issue=OuterRef("id") + attachment_count=FileAsset.objects.filter( + entity_identifier=OuterRef("id"), + entity_type="issue_attachment", ) .order_by() .annotate(count=Func(F("id"), function="Count")) @@ -792,8 +797,9 @@ class SubIssuesEndpoint(BaseAPIView): .values("count") ) .annotate( - attachment_count=IssueAttachment.objects.filter( - issue=OuterRef("id") + attachment_count=FileAsset.objects.filter( + entity_identifier=OuterRef("id"), + entity_type="issue_attachment", ) .order_by() .annotate(count=Func(F("id"), function="Count")) @@ -1010,17 +1016,21 @@ class BulkCreateIssueLabelsEndpoint(BaseAPIView): class IssueAttachmentEndpoint(BaseAPIView): - serializer_class = IssueAttachmentSerializer permission_classes = [ ProjectEntityPermission, ] - model = IssueAttachment - parser_classes = (MultiPartParser, FormParser) + parser_classes = (MultiPartParser, FormParser, JSONParser,) 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(): - 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( type="attachment.activity.created", requested_data=None, @@ -1038,10 +1048,19 @@ class IssueAttachmentEndpoint(BaseAPIView): return Response(serializer.data, status=status.HTTP_201_CREATED) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) - def delete(self, request, slug, project_id, issue_id, pk): - issue_attachment = IssueAttachment.objects.get(pk=pk) - issue_attachment.asset.delete(save=False) - issue_attachment.delete() + def delete( + self, request, slug, project_id, issue_id, workspace_id, asset_key + ): + 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( type="attachment.activity.deleted", requested_data=None, @@ -1053,14 +1072,30 @@ class IssueAttachmentEndpoint(BaseAPIView): notification=True, origin=request.META.get("HTTP_ORIGIN"), ) - return Response(status=status.HTTP_204_NO_CONTENT) - def get(self, request, slug, project_id, issue_id): - issue_attachments = IssueAttachment.objects.filter( - issue_id=issue_id, workspace__slug=slug, project_id=project_id + def get( + self, + 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) @@ -1084,7 +1119,7 @@ class IssueArchiveViewSet(BaseViewSet): .filter(workspace__slug=self.kwargs.get("slug")) .select_related("workspace", "project", "state", "parent") .prefetch_related("assignees", "labels", "issue_module__module") - .annotate(cycle_id=F("issue_cycle__cycle_id")) + .annotate(cycle_id=F("issue_cycle__cycle_id")) .annotate( link_count=IssueLink.objects.filter(issue=OuterRef("id")) .order_by() @@ -1092,8 +1127,9 @@ class IssueArchiveViewSet(BaseViewSet): .values("count") ) .annotate( - attachment_count=IssueAttachment.objects.filter( - issue=OuterRef("id") + attachment_count=FileAsset.objects.filter( + entity_identifier=OuterRef("id"), + entity_type="issue_attachment", ) .order_by() .annotate(count=Func(F("id"), function="Count")) @@ -1131,10 +1167,7 @@ class IssueArchiveViewSet(BaseViewSet): order_by_param = request.GET.get("order_by", "-created_at") - issue_queryset = ( - self.get_queryset() - .filter(**filters) - ) + issue_queryset = self.get_queryset().filter(**filters) # Priority Ordering if order_by_param == "priority" or order_by_param == "-priority": @@ -1579,15 +1612,17 @@ class IssueRelationViewSet(BaseViewSet): issue_relation = IssueRelation.objects.bulk_create( [ IssueRelation( - issue_id=issue - if relation_type == "blocking" - else issue_id, - related_issue_id=issue_id - if relation_type == "blocking" - else issue, - relation_type="blocked_by" - if relation_type == "blocking" - else relation_type, + issue_id=( + issue if relation_type == "blocking" else issue_id + ), + related_issue_id=( + issue_id if relation_type == "blocking" else issue + ), + relation_type=( + "blocked_by" + if relation_type == "blocking" + else relation_type + ), project_id=project_id, workspace_id=project.workspace_id, created_by=request.user, @@ -1695,8 +1730,9 @@ class IssueDraftViewSet(BaseViewSet): .values("count") ) .annotate( - attachment_count=IssueAttachment.objects.filter( - issue=OuterRef("id") + attachment_count=FileAsset.objects.filter( + entity_identifier=OuterRef("id"), + entity_type="issue_attachment", ) .order_by() .annotate(count=Func(F("id"), function="Count")) @@ -1733,10 +1769,7 @@ class IssueDraftViewSet(BaseViewSet): order_by_param = request.GET.get("order_by", "-created_at") - issue_queryset = ( - self.get_queryset() - .filter(**filters) - ) + issue_queryset = self.get_queryset().filter(**filters) # Priority Ordering if order_by_param == "priority" or order_by_param == "-priority": diff --git a/apiserver/plane/app/views/module.py b/apiserver/plane/app/views/module.py index 1f055129a..cb98bff63 100644 --- a/apiserver/plane/app/views/module.py +++ b/apiserver/plane/app/views/module.py @@ -37,7 +37,7 @@ from plane.db.models import ( ModuleLink, ModuleFavorite, IssueLink, - IssueAttachment, + FileAsset, IssueSubscriber, ModuleUserProperties, ) @@ -331,17 +331,16 @@ class ModuleIssueViewSet(WebhookMixin, BaseViewSet): ProjectEntityPermission, ] - def get_queryset(self): return ( Issue.objects.filter( project_id=self.kwargs.get("project_id"), 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") .prefetch_related("labels", "assignees") - .prefetch_related('issue_module__module') + .prefetch_related("issue_module__module") .annotate(cycle_id=F("issue_cycle__cycle_id")) .annotate( link_count=IssueLink.objects.filter(issue=OuterRef("id")) @@ -350,8 +349,9 @@ class ModuleIssueViewSet(WebhookMixin, BaseViewSet): .values("count") ) .annotate( - attachment_count=IssueAttachment.objects.filter( - issue=OuterRef("id") + attachment_count=FileAsset.objects.filter( + entity_identifier=OuterRef("id"), + entity_type="issue_attachment", ) .order_by() .annotate(count=Func(F("id"), function="Count")) @@ -420,10 +420,9 @@ class ModuleIssueViewSet(WebhookMixin, BaseViewSet): ) for issue in issues ] - issues = (self.get_queryset().filter(pk__in=issues)) - serializer = IssueSerializer(issues , many=True) + issues = self.get_queryset().filter(pk__in=issues) + serializer = IssueSerializer(issues, many=True) return Response(serializer.data, status=status.HTTP_201_CREATED) - # create multiple module inside an issue def create_issue_modules(self, request, slug, project_id, issue_id): @@ -466,11 +465,10 @@ class ModuleIssueViewSet(WebhookMixin, BaseViewSet): 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) return Response(serializer.data, status=status.HTTP_201_CREATED) - def destroy(self, request, slug, project_id, module_id, issue_id): module_issue = ModuleIssue.objects.get( workspace__slug=slug, @@ -484,7 +482,9 @@ class ModuleIssueViewSet(WebhookMixin, BaseViewSet): actor_id=str(request.user.id), issue_id=str(issue_id), project_id=str(project_id), - current_instance=json.dumps({"module_name": module_issue.module.name}), + current_instance=json.dumps( + {"module_name": module_issue.module.name} + ), epoch=int(timezone.now().timestamp()), notification=True, origin=request.META.get("HTTP_ORIGIN"), diff --git a/apiserver/plane/app/views/view.py b/apiserver/plane/app/views/view.py index 27f31f7a9..8be9a7465 100644 --- a/apiserver/plane/app/views/view.py +++ b/apiserver/plane/app/views/view.py @@ -41,7 +41,7 @@ from plane.db.models import ( IssueViewFavorite, IssueReaction, IssueLink, - IssueAttachment, + FileAsset, IssueSubscriber, ) from plane.utils.issue_filters import issue_filters @@ -130,8 +130,9 @@ class GlobalViewIssuesViewSet(BaseViewSet): .values("count") ) .annotate( - attachment_count=IssueAttachment.objects.filter( - issue=OuterRef("id") + attachment_count=FileAsset.objects.filter( + entity_identifier=OuterRef("id"), + entity_type="issue_attachment", ) .order_by() .annotate(count=Func(F("id"), function="Count")) diff --git a/apiserver/plane/app/views/workspace.py b/apiserver/plane/app/views/workspace.py index a303be622..e25367210 100644 --- a/apiserver/plane/app/views/workspace.py +++ b/apiserver/plane/app/views/workspace.py @@ -66,7 +66,7 @@ from plane.db.models import ( Issue, WorkspaceTheme, IssueLink, - IssueAttachment, + FileAsset, IssueSubscriber, Project, Label, @@ -1360,8 +1360,9 @@ class WorkspaceUserProfileIssuesEndpoint(BaseAPIView): .values("count") ) .annotate( - attachment_count=IssueAttachment.objects.filter( - issue=OuterRef("id") + attachment_count=FileAsset.objects.filter( + entity_identifier=OuterRef("id"), + entity_type="issue_attachment", ) .order_by() .annotate(count=Func(F("id"), function="Count")) @@ -1558,10 +1559,14 @@ class WorkspaceLogoEndpoint(BaseAPIView): workspace = Workspace.objects.get(slug=slug) if serializer.is_valid(): 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_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) def delete(self, request, slug, workspace_id, logo_key): diff --git a/apiserver/plane/db/migrations/0059_auto_20240131_1334.py b/apiserver/plane/db/migrations/0059_auto_20240131_1334.py index 94f25fc5a..e8e74a6cc 100644 --- a/apiserver/plane/db/migrations/0059_auto_20240131_1334.py +++ b/apiserver/plane/db/migrations/0059_auto_20240131_1334.py @@ -224,7 +224,8 @@ class Migration(migrations.Migration): name="entity_type", field=models.CharField( choices=[ - ("issue", "Issue"), + ("issue_attachment", "Issue Attachment"), + ("issue_description", "Issue Description"), ("comment", "Comment"), ("page", "Page"), ], diff --git a/apiserver/plane/db/migrations/0060_fileasset_size.py b/apiserver/plane/db/migrations/0060_fileasset_size.py index 8322022d4..054d69bdf 100644 --- a/apiserver/plane/db/migrations/0060_fileasset_size.py +++ b/apiserver/plane/db/migrations/0060_fileasset_size.py @@ -67,7 +67,7 @@ def convert_issue_description_image_sources(apps, schema_editor): for asset in FileAsset.objects.filter(asset__in=file_assets.keys()): asset.project_id = file_assets[str(asset.asset)]["project_id"] asset.entity_identifier = file_assets[str(asset.asset)]["issue_id"] - asset.entity_type = "issue" + asset.entity_type = "issue_description" bulk_assets.append(asset) FileAsset.objects.bulk_update( diff --git a/apiserver/plane/db/migrations/0061_issueattachment_asset_key_issueattachment_type.py b/apiserver/plane/db/migrations/0061_issueattachment_asset_key_issueattachment_type.py index d975edfff..7198edc53 100644 --- a/apiserver/plane/db/migrations/0061_issueattachment_asset_key_issueattachment_type.py +++ b/apiserver/plane/db/migrations/0061_issueattachment_asset_key_issueattachment_type.py @@ -4,6 +4,31 @@ from django.db import migrations, models 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): @@ -12,21 +37,8 @@ class Migration(migrations.Migration): ] operations = [ - migrations.AddField( - model_name="issueattachment", - name="asset_key", - 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 - ), + migrations.RunPython(create_attachment_assets), + migrations.DeleteModel( + name="IssueAttachment", ), ] diff --git a/apiserver/plane/db/migrations/0062_auto_20240205_0703.py b/apiserver/plane/db/migrations/0062_auto_20240205_0703.py deleted file mode 100644 index 8311503dd..000000000 --- a/apiserver/plane/db/migrations/0062_auto_20240205_0703.py +++ /dev/null @@ -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), - ] diff --git a/apiserver/plane/db/models/__init__.py b/apiserver/plane/db/models/__init__.py index d9096bd01..9cc71bda6 100644 --- a/apiserver/plane/db/models/__init__.py +++ b/apiserver/plane/db/models/__init__.py @@ -37,7 +37,6 @@ from .issue import ( IssueMention, IssueLink, IssueSequence, - IssueAttachment, IssueSubscriber, IssueReaction, CommentReaction, diff --git a/apiserver/plane/db/models/asset.py b/apiserver/plane/db/models/asset.py index 4fa847ed9..be9d7adfa 100644 --- a/apiserver/plane/db/models/asset.py +++ b/apiserver/plane/db/models/asset.py @@ -48,7 +48,8 @@ class FileAsset(BaseModel): ) entity_type = models.CharField( choices=( - ("issue", "Issue"), + ("issue_attachment", "Issue Attachment"), + ("issue_description", "Issue Description"), ("comment", "Comment"), ("page", "Page"), ), diff --git a/apiserver/plane/db/models/issue.py b/apiserver/plane/db/models/issue.py index 8a63da9a3..0246cae4a 100644 --- a/apiserver/plane/db/models/issue.py +++ b/apiserver/plane/db/models/issue.py @@ -345,42 +345,6 @@ def file_size(value): if value.size > settings.FILE_SIZE_LIMIT: 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): issue = models.ForeignKey( Issue, diff --git a/apiserver/plane/space/serializer/issue.py b/apiserver/plane/space/serializer/issue.py index c7b044b21..691232c53 100644 --- a/apiserver/plane/space/serializer/issue.py +++ b/apiserver/plane/space/serializer/issue.py @@ -22,7 +22,7 @@ from plane.db.models import ( CycleIssue, ModuleIssue, IssueLink, - IssueAttachment, + FileAsset, IssueReaction, CommentReaction, IssueVote, @@ -171,22 +171,6 @@ class IssueLinkSerializer(BaseSerializer): ) 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): actor_detail = UserLiteSerializer(read_only=True, source="actor") @@ -218,7 +202,6 @@ class IssueSerializer(BaseSerializer): issue_cycle = IssueCycleDetailSerializer(read_only=True) issue_module = IssueModuleDetailSerializer(read_only=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) issue_reactions = IssueReactionSerializer(read_only=True, many=True) diff --git a/apiserver/plane/space/views/inbox.py b/apiserver/plane/space/views/inbox.py index 2bf8f8303..e872fc1f7 100644 --- a/apiserver/plane/space/views/inbox.py +++ b/apiserver/plane/space/views/inbox.py @@ -17,7 +17,7 @@ from plane.db.models import ( Issue, State, IssueLink, - IssueAttachment, + FileAsset, ProjectDeployBoard, ) from plane.app.serializers import ( @@ -95,8 +95,9 @@ class InboxIssuePublicViewSet(BaseViewSet): .values("count") ) .annotate( - attachment_count=IssueAttachment.objects.filter( - issue=OuterRef("id") + attachment_count=FileAsset.objects.filter( + entity_identifier=OuterRef("id"), + entity_type="issue_attachment", ) .order_by() .annotate(count=Func(F("id"), function="Count")) diff --git a/apiserver/plane/space/views/issue.py b/apiserver/plane/space/views/issue.py index 8f7fc0eaa..7f8499474 100644 --- a/apiserver/plane/space/views/issue.py +++ b/apiserver/plane/space/views/issue.py @@ -40,7 +40,7 @@ from plane.db.models import ( IssueComment, Label, IssueLink, - IssueAttachment, + FileAsset, State, ProjectMember, IssueReaction, @@ -567,8 +567,9 @@ class ProjectIssuesPublicEndpoint(BaseAPIView): .values("count") ) .annotate( - attachment_count=IssueAttachment.objects.filter( - issue=OuterRef("id") + attachment_count=FileAsset.objects.filter( + entity_identifier=OuterRef("id"), + entity_type="issue_attachment", ) .order_by() .annotate(count=Func(F("id"), function="Count"))