dev: fix conflicts and errors

This commit is contained in:
pablohashescobar 2024-03-21 17:21:44 +05:30
parent 6037321572
commit 9bc144649a
20 changed files with 608 additions and 120 deletions

View File

@ -3,10 +3,12 @@ from django.urls import path
from plane.app.views import (
BulkCreateIssueLabelsEndpoint,
BulkDeleteIssuesEndpoint,
CommentAssetEndpoint,
CommentReactionViewSet,
ExportIssuesEndpoint,
IssueActivityEndpoint,
IssueArchiveViewSet,
IssueAttachmentEndpoint,
IssueCommentViewSet,
IssueDraftViewSet,
IssueLinkViewSet,
@ -286,5 +288,26 @@ urlpatterns = [
}
),
name="project-issue-draft",
), # Comment Assets
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/comments/<uuid:comment_id>/attachments/",
CommentAssetEndpoint.as_view(),
name="project-comment-attachments",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/comments/<uuid:comment_id>/attachments/<uuid:workspace_id>/<str:asset_key>/",
CommentAssetEndpoint.as_view(),
name="project-comment-attachments",
),
## End Comments
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/issues/<uuid:issue_id>/attachments/",
IssueAttachmentEndpoint.as_view(),
name="project-issue-attachments",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/issues/<uuid:issue_id>/attachments/<uuid:workspace_id>/<str:asset_key>/",
IssueAttachmentEndpoint.as_view(),
name="project-issue-attachments",
),
]

View File

@ -3,6 +3,7 @@ from django.urls import path
from plane.app.views import (
AddTeamToProjectEndpoint,
ProjectArchiveUnarchiveEndpoint,
ProjectCoverImageEndpoint,
ProjectDeployBoardViewSet,
ProjectFavoritesViewSet,
ProjectIdentifierEndpoint,
@ -180,4 +181,14 @@ urlpatterns = [
ProjectArchiveUnarchiveEndpoint.as_view(),
name="project-archive-unarchive",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/cover-image/<str:workspace_id>/<str:cover_image_key>/",
ProjectCoverImageEndpoint.as_view(),
name="project-cover-image",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/cover-image/",
ProjectCoverImageEndpoint.as_view(),
name="project-cover-image",
),
]

View File

@ -11,6 +11,7 @@ from plane.app.views import (
WorkspaceInvitationsViewset,
WorkspaceJoinEndpoint,
WorkspaceLabelsEndpoint,
WorkspaceLogoEndpoint,
WorkspaceMemberUserEndpoint,
WorkspaceMemberUserViewsEndpoint,
WorkSpaceMemberViewSet,
@ -235,4 +236,14 @@ urlpatterns = [
WorkspaceCyclesEndpoint.as_view(),
name="workspace-cycles",
),
path(
"workspaces/<str:slug>/logo/",
WorkspaceLogoEndpoint.as_view(),
name="workspace-logo",
),
path(
"workspaces/<str:slug>/logo/<str:workspace_id>/<str:logo_key>/",
WorkspaceLogoEndpoint.as_view(),
name="workspace-logo",
),
]

View File

@ -6,6 +6,7 @@ from .project.base import (
ProjectPublicCoverImagesEndpoint,
ProjectDeployBoardViewSet,
ProjectArchiveUnarchiveEndpoint,
ProjectCoverImageEndpoint,
)
from .project.invite import (
@ -41,6 +42,7 @@ from .workspace.base import (
UserWorkspaceDashboardEndpoint,
WorkspaceThemeViewSet,
ExportWorkspaceUserActivityEndpoint,
WorkspaceLogoEndpoint,
)
from .workspace.member import (
@ -116,10 +118,12 @@ from .issue.archive import (
IssueArchiveViewSet,
)
from .issue.attachment import IssueAttachmentEndpoint
from .issue.comment import (
IssueCommentViewSet,
CommentReactionViewSet,
CommentAssetEndpoint,
)
from .issue.draft import IssueDraftViewSet

View File

@ -29,12 +29,7 @@ from plane.app.serializers import (
IssueSerializer,
)
from plane.bgtasks.issue_activites_task import issue_activity
from plane.db.models import (
Cycle,
CycleIssue,
Issue,
IssueLink,
)
from plane.db.models import Cycle, CycleIssue, FileAsset, Issue, IssueLink
from plane.utils.issue_filters import issue_filters
# Module imports
@ -123,6 +118,15 @@ class CycleIssueViewSet(WebhookMixin, BaseViewSet):
.annotate(count=Func(F("id"), function="Count"))
.values("count")
)
.annotate(
attachment_count=FileAsset.objects.filter(
entity_identifier=OuterRef("id"),
entity_type="issue_attachment",
)
.order_by()
.annotate(count=Func(F("id"), function="Count"))
.values("count")
)
.annotate(
label_ids=Coalesce(
ArrayAgg(

View File

@ -145,6 +145,15 @@ class InboxIssueViewSet(BaseViewSet):
.annotate(count=Func(F("id"), function="Count"))
.values("count")
)
.annotate(
attachment_count=FileAsset.objects.filter(
entity_identifier=OuterRef("id"),
entity_type="issue_attachment",
)
.order_by()
.annotate(count=Func(F("id"), function="Count"))
.values("count")
)
.annotate(
label_ids=Coalesce(
ArrayAgg(

View File

@ -39,6 +39,7 @@ from plane.app.serializers import (
)
from plane.bgtasks.issue_activites_task import issue_activity
from plane.db.models import (
FileAsset,
Issue,
IssueLink,
IssueReaction,
@ -85,6 +86,15 @@ class IssueArchiveViewSet(BaseViewSet):
.annotate(count=Func(F("id"), function="Count"))
.values("count")
)
.annotate(
attachment_count=FileAsset.objects.filter(
entity_identifier=OuterRef("id"),
entity_type="issue_attachment",
)
.order_by()
.annotate(count=Func(F("id"), function="Count"))
.values("count")
)
.annotate(
label_ids=Coalesce(
ArrayAgg(

View File

@ -0,0 +1,111 @@
# Python imports
import json
# Django imports
from django.core.serializers.json import DjangoJSONEncoder
from django.http import HttpResponseRedirect
from django.utils import timezone
# Third party imports
from rest_framework import status
from rest_framework.parsers import FormParser, JSONParser, MultiPartParser
from rest_framework.response import Response
# Module imports
from plane.app.permissions import ProjectEntityPermission
from plane.app.serializers import FileAssetSerializer
from plane.app.views.base import BaseAPIView
from plane.bgtasks.issue_activites_task import issue_activity
from plane.db.models import FileAsset, Workspace
from plane.utils.presigned_url_generator import generate_download_presigned_url
class IssueAttachmentEndpoint(BaseAPIView):
permission_classes = [
ProjectEntityPermission,
]
parser_classes = (
MultiPartParser,
FormParser,
JSONParser,
)
def post(self, request, slug, project_id, issue_id):
serializer = FileAssetSerializer(data=request.data)
workspace = Workspace.objects.get(slug=slug)
if serializer.is_valid():
serializer.save(
workspace=workspace,
project_id=project_id,
entity_identifier=issue_id,
)
issue_activity.delay(
type="attachment.activity.created",
requested_data=None,
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,
),
epoch=int(timezone.now().timestamp()),
notification=True,
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)
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,
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=None,
epoch=int(timezone.now().timestamp()),
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,
workspace_id=None,
asset_key=None,
):
if workspace_id and asset_key:
key = f"{workspace_id}/{asset_key}"
url = generate_download_presigned_url(
key=key,
host=request.get_host(),
scheme=request.scheme,
)
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 = FileAssetSerializer(issue_attachments, many=True)
return Response(serializer.data, status=status.HTTP_200_OK)

View File

@ -2,27 +2,33 @@
import json
# Django imports
from django.utils import timezone
from django.db.models import Exists
from django.core.serializers.json import DjangoJSONEncoder
from django.db.models import Exists
from django.http import HttpResponseRedirect
from django.utils import timezone
# Third Party imports
from rest_framework.response import Response
from rest_framework import status
from rest_framework.parsers import FormParser, JSONParser, MultiPartParser
from rest_framework.response import Response
# Module imports
from .. import BaseViewSet, WebhookMixin
from plane.app.serializers import (
IssueCommentSerializer,
CommentReactionSerializer,
)
from plane.app.permissions import ProjectLitePermission
from plane.app.serializers import (
CommentReactionSerializer,
FileAssetSerializer,
IssueCommentSerializer,
)
from plane.app.views.base import BaseAPIView, BaseViewSet, WebhookMixin
from plane.bgtasks.issue_activites_task import issue_activity
from plane.db.models import (
CommentReaction,
FileAsset,
IssueComment,
ProjectMember,
CommentReaction,
Workspace,
)
from plane.bgtasks.issue_activites_task import issue_activity
from plane.utils.presigned_url_generator import generate_download_presigned_url
class IssueCommentViewSet(WebhookMixin, BaseViewSet):
@ -219,3 +225,70 @@ class CommentReactionViewSet(BaseViewSet):
)
comment_reaction.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
class CommentAssetEndpoint(BaseAPIView):
permission_classes = [
ProjectLitePermission,
]
parser_classes = (
MultiPartParser,
FormParser,
JSONParser,
)
def post(self, request, slug, project_id, comment_id):
serializer = FileAssetSerializer(data=request.data)
workspace = Workspace.objects.get(slug=slug)
if serializer.is_valid():
serializer.save(
workspace=workspace,
project_id=project_id,
entity_type="comment",
entity_identifier=comment_id,
)
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, comment_id, workspace_id, asset_key
):
key = f"{workspace_id}/{asset_key}"
asset = FileAsset.objects.get(
asset=key,
entity_identifier=comment_id,
entity_type="comment",
workspace__slug=slug,
project_id=project_id,
)
asset.is_deleted = True
asset.save()
return Response(status=status.HTTP_204_NO_CONTENT)
def get(
self,
request,
slug,
project_id,
comment_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=key,
host=request.get_host(),
scheme=request.scheme,
)
return HttpResponseRedirect(url)
# For listing
comment_assets = FileAsset.objects.filter(
entity_type="comment",
entity_identifier=comment_id,
workspace__slug=slug,
project_id=project_id,
)
serializer = FileAssetSerializer(comment_assets, many=True)
return Response(serializer.data, status=status.HTTP_200_OK)

View File

@ -38,6 +38,7 @@ from plane.app.serializers import (
)
from plane.bgtasks.issue_activites_task import issue_activity
from plane.db.models import (
FileAsset,
Issue,
IssueLink,
IssueReaction,
@ -79,6 +80,15 @@ class IssueDraftViewSet(BaseViewSet):
.annotate(count=Func(F("id"), function="Count"))
.values("count")
)
.annotate(
attachment_count=FileAsset.objects.filter(
entity_identifier=OuterRef("id"),
entity_type="issue_attachment",
)
.order_by()
.annotate(count=Func(F("id"), function="Count"))
.values("count")
)
.annotate(
label_ids=Coalesce(
ArrayAgg(

View File

@ -22,6 +22,7 @@ from plane.app.serializers import (
)
from plane.bgtasks.issue_activites_task import issue_activity
from plane.db.models import (
FileAsset,
Issue,
IssueLink,
ModuleIssue,
@ -72,6 +73,15 @@ class ModuleIssueViewSet(WebhookMixin, BaseViewSet):
.annotate(count=Func(F("id"), function="Count"))
.values("count")
)
.annotate(
attachment_count=FileAsset.objects.filter(
entity_identifier=OuterRef("id"),
entity_type="issue_attachment",
)
.order_by()
.annotate(count=Func(F("id"), function="Count"))
.values("count")
)
.annotate(
label_ids=Coalesce(
ArrayAgg(

View File

@ -2,54 +2,57 @@
import boto3
# Django imports
from django.conf import settings
from django.db import IntegrityError
from django.db.models import (
Prefetch,
Q,
Exists,
OuterRef,
F,
Func,
OuterRef,
Prefetch,
Q,
Subquery,
)
from django.conf import settings
from django.http import HttpResponseRedirect
from django.utils import timezone
# Third Party imports
from rest_framework import serializers, status
from rest_framework.parsers import FormParser, JSONParser, MultiPartParser
from rest_framework.permissions import AllowAny, IsAuthenticated
from rest_framework.response import Response
from rest_framework import status
from rest_framework import serializers
from rest_framework.permissions import AllowAny
# Module imports
from plane.app.views.base import BaseViewSet, BaseAPIView, WebhookMixin
from plane.app.serializers import (
ProjectSerializer,
ProjectListSerializer,
ProjectFavoriteSerializer,
ProjectDeployBoardSerializer,
)
from plane.app.permissions import (
ProjectBasePermission,
ProjectMemberPermission,
)
from plane.app.serializers import (
FileAssetSerializer,
ProjectDeployBoardSerializer,
ProjectFavoriteSerializer,
ProjectListSerializer,
ProjectLiteSerializer,
ProjectSerializer,
)
from plane.app.views.base import BaseAPIView, BaseViewSet, WebhookMixin
from plane.db.models import (
Cycle,
FileAsset,
Inbox,
Issue,
IssueProperty,
Module,
Project,
ProjectMember,
Workspace,
State,
ProjectDeployBoard,
ProjectFavorite,
ProjectIdentifier,
Module,
Cycle,
Inbox,
ProjectDeployBoard,
IssueProperty,
Issue,
ProjectMember,
State,
Workspace,
)
from plane.utils.cache import cache_response
from plane.utils.presigned_url_generator import generate_download_presigned_url
class ProjectViewSet(WebhookMixin, BaseViewSet):
@ -372,7 +375,7 @@ class ProjectViewSet(WebhookMixin, BaseViewSet):
return Response(
{"error": "Archived projects cannot be updated"},
status=status.HTTP_400_BAD_REQUEST,
)
)
serializer = ProjectSerializer(
project,
@ -433,11 +436,12 @@ class ProjectArchiveUnarchiveEndpoint(BaseAPIView):
permission_classes = [
ProjectBasePermission,
]
def post(self, request, slug, project_id):
project = Project.objects.get(pk=project_id, workspace__slug=slug)
project.archived_at = timezone.now()
project.save()
return Response(status=status.HTTP_204_NO_CONTENT)
return Response(status=status.HTTP_204_NO_CONTENT)
def delete(self, request, slug, project_id):
project = Project.objects.get(pk=project_id, workspace__slug=slug)
@ -646,3 +650,51 @@ class ProjectDeployBoardViewSet(BaseViewSet):
serializer = ProjectDeployBoardSerializer(project_deploy_board)
return Response(serializer.data, status=status.HTTP_200_OK)
class ProjectCoverImageEndpoint(BaseAPIView):
parser_classes = (
MultiPartParser,
FormParser,
JSONParser,
)
def get_permissions(self):
if self.request.method == "POST" or self.request.method == "DELETE":
return [
IsAuthenticated(),
]
return [
AllowAny(),
]
def get(self, request, slug, project_id, workspace_id, cover_image_key):
key = f"{workspace_id}/{cover_image_key}"
url = generate_download_presigned_url(
key=key,
host=request.get_host(),
scheme=request.scheme,
)
return HttpResponseRedirect(url)
def post(self, request, slug, project_id):
serializer = FileAssetSerializer(data=request.data)
workspace = Workspace.objects.get(slug=slug)
if serializer.is_valid():
serializer.save(workspace=workspace)
project = Project.objects.get(pk=project_id)
project.cover_image = f"/api/workspaces/{slug}/projects/{project_id}/cover-image/{serializer.data['asset']}/"
project.save()
project_serializer = ProjectLiteSerializer(project)
return Response(
project_serializer.data, status=status.HTTP_201_CREATED
)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
def delete(self, request, project_id, workspace_id, cover_image_key):
key = f"{workspace_id}/{cover_image_key}"
file_asset = FileAsset.objects.get(asset=key)
file_asset.is_deleted = True
file_asset.save()
return Response(status=status.HTTP_204_NO_CONTENT)

View File

@ -4,6 +4,8 @@ import io
from datetime import date
from dateutil.relativedelta import relativedelta
# Django imports
from django.db import IntegrityError
from django.db.models import (
Count,
@ -15,28 +17,29 @@ from django.db.models import (
)
from django.db.models.fields import DateField
from django.db.models.functions import Cast, ExtractDay, ExtractWeek
# Django imports
from django.http import HttpResponse
from django.http import HttpResponse, HttpResponseRedirect
from django.utils import timezone
# Third party modules
from rest_framework import status
from rest_framework.parsers import FormParser, JSONParser, MultiPartParser
from rest_framework.permissions import AllowAny, IsAuthenticated
from rest_framework.response import Response
# Module imports
from plane.app.permissions import (
WorkSpaceAdminPermission,
WorkSpaceBasePermission,
WorkspaceEntityPermission,
)
# Module imports
from plane.app.serializers import (
FileAssetSerializer,
WorkSpaceSerializer,
WorkspaceThemeSerializer,
)
from plane.app.views.base import BaseAPIView, BaseViewSet
from plane.db.models import (
FileAsset,
Issue,
IssueActivity,
Workspace,
@ -44,6 +47,7 @@ from plane.db.models import (
WorkspaceTheme,
)
from plane.utils.cache import cache_response, invalidate_cache
from plane.utils.presigned_url_generator import generate_download_presigned_url
class WorkSpaceViewSet(BaseViewSet):
@ -416,3 +420,52 @@ class ExportWorkspaceUserActivityEndpoint(BaseAPIView):
'attachment; filename="workspace-user-activity.csv"'
)
return response
class WorkspaceLogoEndpoint(BaseAPIView):
parser_classes = (
MultiPartParser,
FormParser,
JSONParser,
)
def get_permissions(self):
if self.request.method == "POST" or self.request.method == "DELETE":
return [
IsAuthenticated(),
]
return [
AllowAny(),
]
def get(self, request, slug, workspace_id, logo_key):
key = f"{workspace_id}/{logo_key}"
url = generate_download_presigned_url(
key=key,
host=request.get_host(),
scheme=request.scheme,
)
return HttpResponseRedirect(url)
def post(self, request, slug):
serializer = FileAssetSerializer(data=request.data)
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.save()
workspace_serializer = WorkSpaceSerializer(workspace)
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):
key = f"{workspace_id}/{logo_key}"
file_asset = FileAsset.objects.get(asset=key)
file_asset.is_deleted = True
file_asset.save()
return Response(status=status.HTTP_204_NO_CONTENT)

View File

@ -44,6 +44,7 @@ from plane.app.serializers import (
from plane.app.views.base import BaseAPIView
from plane.db.models import (
CycleIssue,
FileAsset,
Issue,
IssueActivity,
IssueLink,
@ -144,6 +145,15 @@ class WorkspaceUserProfileIssuesEndpoint(BaseAPIView):
.annotate(count=Func(F("id"), function="Count"))
.values("count")
)
.annotate(
attachment_count=FileAsset.objects.filter(
entity_identifier=OuterRef("id"),
entity_type="issue_attachment",
)
.order_by()
.annotate(count=Func(F("id"), function="Count"))
.values("count")
)
.annotate(
label_ids=Coalesce(
ArrayAgg(

View File

@ -2,14 +2,16 @@
from uuid import uuid4
# Django import
from django.db import models
from django.core.exceptions import ValidationError
from django.conf import settings
from django.core.exceptions import ValidationError
from django.db import models
# Module import
from . import BaseModel
from plane.settings.storage import S3PrivateBucketStorage
from .base import BaseModel
def get_upload_path(instance, filename):
if instance.workspace_id is not None:
return f"{instance.workspace.id}/{uuid4().hex}"

View File

@ -1,19 +1,16 @@
# Python import
from uuid import uuid4
# Django imports
from django.db import models
from django.conf import settings
from django.core.validators import MaxValueValidator, MinValueValidator
from django.db import models
from django.db.models.signals import post_save
from django.dispatch import receiver
from django.core.validators import MinValueValidator, MaxValueValidator
from django.core.exceptions import ValidationError
from django.utils import timezone
# Module imports
from . import ProjectBaseModel
from plane.utils.html_processor import strip_tags
from .project import ProjectBaseModel
def get_default_properties():
return {
@ -95,6 +92,17 @@ class IssueManager(models.Manager):
)
def get_upload_path(instance, filename):
if instance.workspace_id is not None:
return f"{instance.workspace.id}/{uuid4().hex}"
return f"user-{uuid4().hex}"
def file_size(value):
if value.size > settings.FILE_SIZE_LIMIT:
raise ValidationError("File too large. Size should not exceed 5 MB.")
class Issue(ProjectBaseModel):
PRIORITY_CHOICES = (
("urgent", "Urgent"),
@ -336,15 +344,6 @@ class IssueLink(ProjectBaseModel):
return f"{self.issue.name} {self.url}"
def get_upload_path(instance, filename):
return f"{instance.workspace.id}/{uuid4().hex}"
def file_size(value):
# File limit check is only for cloud hosted
if value.size > settings.FILE_SIZE_LIMIT:
raise ValidationError("File too large. Size should not exceed 5 MB.")
class IssueActivity(ProjectBaseModel):
issue = models.ForeignKey(
Issue,

View File

@ -1,11 +1,12 @@
from django.urls import path
from plane.space.views import (
IssueRetrievePublicEndpoint,
CommentAssetPublicEndpoint,
CommentReactionPublicViewSet,
IssueAttachmentPublicEndpoint,
IssueCommentPublicViewSet,
IssueReactionPublicViewSet,
CommentReactionPublicViewSet,
IssueRetrievePublicEndpoint,
)
urlpatterns = [
@ -73,4 +74,24 @@ urlpatterns = [
),
name="comment-reactions-project-board",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/issues/<uuid:issue_id>/attachments/",
IssueAttachmentPublicEndpoint.as_view(),
name="project-issue-attachments",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/issues/<uuid:issue_id>/attachments/<uuid:workspace_id>/<str:asset_key>/",
IssueAttachmentPublicEndpoint.as_view(),
name="project-issue-attachments",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/comments/<uuid:comment_id>/attachments/",
CommentAssetPublicEndpoint.as_view(),
name="issue-comments-project-board-attachments",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/comments/<uuid:comment_id>/attachments/<uuid:workspace_id>/<str:asset_key>/",
CommentAssetPublicEndpoint.as_view(),
name="issue-comments-project-board-attachments",
),
]

View File

@ -10,6 +10,8 @@ from .issue import (
IssueVotePublicViewSet,
IssueRetrievePublicEndpoint,
ProjectIssuesPublicEndpoint,
CommentAssetPublicEndpoint,
IssueAttachmentPublicEndpoint,
)
from .inbox import InboxIssuePublicViewSet

View File

@ -2,32 +2,32 @@
import json
# Django import
from django.utils import timezone
from django.db.models import Q, OuterRef, Func, F, Prefetch
from django.core.serializers.json import DjangoJSONEncoder
from django.db.models import F, Func, OuterRef, Prefetch, Q
from django.utils import timezone
# Third party imports
from rest_framework import status
from rest_framework.response import Response
# Module imports
from .base import BaseViewSet
from plane.db.models import (
InboxIssue,
Issue,
State,
IssueLink,
FileAsset,
ProjectDeployBoard,
)
from plane.app.serializers import (
IssueSerializer,
InboxIssueSerializer,
IssueCreateSerializer,
IssueSerializer,
IssueStateInboxSerializer,
)
from plane.utils.issue_filters import issue_filters
from plane.app.views.base import BaseViewSet
from plane.bgtasks.issue_activites_task import issue_activity
from plane.db.models import (
FileAsset,
InboxIssue,
Issue,
IssueLink,
ProjectDeployBoard,
State,
)
from plane.utils.issue_filters import issue_filters
class InboxIssuePublicViewSet(BaseViewSet):
@ -96,8 +96,8 @@ class InboxIssuePublicViewSet(BaseViewSet):
)
.annotate(
attachment_count=FileAsset.objects.filter(
entity_identifier=OuterRef("id"),
entity_type="issue_attachment",
entity_identifier=OuterRef("id"),
entity_type="issue_attachment",
)
.order_by()
.annotate(count=Func(F("id"), function="Count"))

View File

@ -2,55 +2,58 @@
import json
# Django imports
from django.utils import timezone
from django.db.models import (
Prefetch,
OuterRef,
Func,
F,
Q,
Case,
Value,
CharField,
When,
Exists,
Max,
IntegerField,
)
from django.core.serializers.json import DjangoJSONEncoder
from django.db.models import (
Case,
CharField,
Exists,
F,
Func,
IntegerField,
Max,
OuterRef,
Prefetch,
Q,
Value,
When,
)
from django.http import HttpResponseRedirect
from django.utils import timezone
# Third Party imports
from rest_framework.response import Response
from rest_framework import status
from rest_framework.permissions import AllowAny, IsAuthenticated
from rest_framework.response import Response
# Module imports
from .base import BaseViewSet, BaseAPIView
from plane.app.serializers import (
IssueCommentSerializer,
IssueReactionSerializer,
CommentReactionSerializer,
IssueVoteSerializer,
FileAssetSerializer,
IssueCommentSerializer,
IssuePublicSerializer,
)
from plane.db.models import (
Issue,
IssueComment,
Label,
IssueLink,
FileAsset,
State,
ProjectMember,
IssueReaction,
CommentReaction,
ProjectDeployBoard,
IssueVote,
ProjectPublicMember,
IssueReactionSerializer,
IssueVoteSerializer,
)
from plane.bgtasks.issue_activites_task import issue_activity
from plane.db.models import (
CommentReaction,
FileAsset,
Issue,
IssueComment,
IssueLink,
IssueReaction,
IssueVote,
Label,
ProjectDeployBoard,
ProjectMember,
ProjectPublicMember,
State,
)
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 .base import BaseAPIView, BaseViewSet
class IssueCommentPublicViewSet(BaseViewSet):
@ -687,3 +690,73 @@ class ProjectIssuesPublicEndpoint(BaseAPIView):
},
status=status.HTTP_200_OK,
)
class IssueAttachmentPublicEndpoint(BaseAPIView):
permission_classes = [
AllowAny,
]
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=key,
host=request.get_host(),
scheme=request.scheme,
)
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 = FileAssetSerializer(issue_attachments, many=True)
return Response(serializer.data, status=status.HTTP_200_OK)
class CommentAssetPublicEndpoint(BaseAPIView):
permission_classes = [
AllowAny,
]
def get(
self,
request,
slug,
project_id,
comment_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=key,
host=request.get_host(),
scheme=request.scheme,
)
return HttpResponseRedirect(url)
# For listing
comment_assets = FileAsset.objects.filter(
entity_type="comment",
entity_identifier=comment_id,
workspace__slug=slug,
project_id=project_id,
)
serializer = FileAssetSerializer(comment_assets, many=True)
return Response(serializer.data, status=status.HTTP_200_OK)