mirror of
https://github.com/makeplane/plane
synced 2024-06-14 14:31:34 +00:00
dev: fix conflicts and errors
This commit is contained in:
parent
6037321572
commit
9bc144649a
@ -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",
|
||||
),
|
||||
]
|
||||
|
@ -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",
|
||||
),
|
||||
]
|
||||
|
@ -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",
|
||||
),
|
||||
]
|
||||
|
@ -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
|
||||
|
@ -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(
|
||||
|
@ -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(
|
||||
|
@ -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(
|
||||
|
111
apiserver/plane/app/views/issue/attachment.py
Normal file
111
apiserver/plane/app/views/issue/attachment.py
Normal 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)
|
@ -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)
|
||||
|
@ -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(
|
||||
|
@ -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(
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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(
|
||||
|
@ -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}"
|
||||
|
@ -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,
|
||||
|
@ -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",
|
||||
),
|
||||
]
|
||||
|
@ -10,6 +10,8 @@ from .issue import (
|
||||
IssueVotePublicViewSet,
|
||||
IssueRetrievePublicEndpoint,
|
||||
ProjectIssuesPublicEndpoint,
|
||||
CommentAssetPublicEndpoint,
|
||||
IssueAttachmentPublicEndpoint,
|
||||
)
|
||||
|
||||
from .inbox import InboxIssuePublicViewSet
|
||||
|
@ -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"))
|
||||
|
@ -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)
|
||||
|
Loading…
Reference in New Issue
Block a user