mirror of
https://github.com/makeplane/plane
synced 2024-06-14 14:31:34 +00:00
Merge branch 'update-file-uploads' of github.com:makeplane/plane into update-file-uploads
This commit is contained in:
commit
14c6cc1420
@ -26,3 +26,31 @@ def delete_file_asset():
|
||||
file_asset.asset.delete(save=False)
|
||||
# Delete the file object
|
||||
file_asset.delete()
|
||||
|
||||
|
||||
@shared_task
|
||||
def file_asset_size(slug, email, members, issue_count, cycle_count, module_count):
|
||||
asset_size = []
|
||||
# s3_client = boto3.client('s3')
|
||||
assets_to_update = []
|
||||
|
||||
# for asset in FileAsset.objects.filter(size__isnull=True):
|
||||
# try:
|
||||
# key = f"{workspace_id}/{asset_key}"
|
||||
# response = s3_client.head_object(Bucket=settings.AWS_STORAGE_BUCKET_NAME, Key=key)
|
||||
# size = response['ContentLength']
|
||||
# asset.size = size
|
||||
# assets_to_update.append(asset)
|
||||
# except Exception as e:
|
||||
# # Handle exceptions such as S3 object not found
|
||||
# print(f"Error updating asset size for {asset.asset.key}: {e}")
|
||||
|
||||
# # Bulk update only objects that need updating
|
||||
# FileAsset.objects.bulk_update(assets_to_update, ["size"], batch_size=50)
|
||||
|
||||
for asset in FileAsset.objects.filter(size__isnull=True):
|
||||
asset.size = asset.asset.size
|
||||
asset_size.append(asset)
|
||||
|
||||
FileAsset.objects.bulk_update(asset_size, ["size"], batch_size=50)
|
||||
print("File asset size updated successfully")
|
||||
|
23
apiserver/plane/db/management/commands/file_asset_size.py
Normal file
23
apiserver/plane/db/management/commands/file_asset_size.py
Normal file
@ -0,0 +1,23 @@
|
||||
# Python imports
|
||||
import getpass
|
||||
|
||||
# Django imports
|
||||
from django.core.management import BaseCommand
|
||||
|
||||
# Module imports
|
||||
from plane.db.models import User
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = "Check the file asset size of the file"
|
||||
|
||||
|
||||
def handle(self, *args, **options):
|
||||
|
||||
from plane.bgtasks.file_asset_task import file_asset_size
|
||||
|
||||
file_asset_size.delay()
|
||||
|
||||
self.stdout.write(
|
||||
self.style.SUCCESS(f"File asset size pushed to queue")
|
||||
)
|
@ -33,7 +33,7 @@ def convert_issue_description_image_sources(apps, schema_editor):
|
||||
src = img.get("src", "")
|
||||
if src and (src.startswith(prefix1)):
|
||||
img["src"] = (
|
||||
f"/api/workspaces/{issue.workspace.slug}/projects/{issue.project_id}/issues/{issue.id}/attachments/{src[len(prefix1): ]}"
|
||||
f"{src[len(prefix1): ]}"
|
||||
)
|
||||
file_assets[src[len(prefix1) :]] = {
|
||||
"project_id": str(issue.project_id),
|
||||
@ -45,7 +45,7 @@ def convert_issue_description_image_sources(apps, schema_editor):
|
||||
# prefix 2
|
||||
if not settings.USE_MINIO and src and src.startswith(prefix2):
|
||||
img["src"] = (
|
||||
f"/api/workspaces/{issue.workspace.slug}/projects/{issue.project_id}/issues/{issue.id}/attachments/{src[len(prefix2): ]}"
|
||||
f"{src[len(prefix2): ]}"
|
||||
)
|
||||
file_assets[src[len(prefix2) :]] = {
|
||||
"project_id": str(issue.project_id),
|
||||
@ -109,7 +109,7 @@ def convert_page_image_sources(apps, schema_editor):
|
||||
src = img.get("src", "")
|
||||
if src and (src.startswith(prefix1)):
|
||||
img["src"] = (
|
||||
f"/api/workspaces/{page.workspace.slug}/projects/{page.project_id}/issues/{page.id}/attachments/{src[len(prefix1): ]}/"
|
||||
f"{src[len(prefix1): ]}/"
|
||||
)
|
||||
file_assets[src[len(prefix1) :]] = {
|
||||
"project_id": str(page.project_id),
|
||||
@ -121,7 +121,7 @@ def convert_page_image_sources(apps, schema_editor):
|
||||
# prefix 2
|
||||
if not settings.USE_MINIO and src and src.startswith(prefix2):
|
||||
img["src"] = (
|
||||
f"/api/workspaces/{page.workspace.slug}/projects/{page.project_id}/issues/{page.id}/attachments/{src[len(prefix2): ]}/"
|
||||
f"{src[len(prefix2): ]}/"
|
||||
)
|
||||
file_assets[src[len(prefix2) :]] = {
|
||||
"project_id": str(page.project_id),
|
||||
@ -181,7 +181,7 @@ def convert_comment_image_sources(apps, schema_editor):
|
||||
src = img.get("src", "")
|
||||
if src and (src.startswith(prefix1)):
|
||||
img["src"] = (
|
||||
f"/api/workspaces/{comment.workspace.slug}/projects/{comment.project_id}/issues/{comment.id}/attachments/{src[len(prefix1): ]}/"
|
||||
f"{src[len(prefix1): ]}/"
|
||||
)
|
||||
file_assets[src[len(prefix1) :]] = {
|
||||
"project_id": str(comment.project_id),
|
||||
@ -193,7 +193,7 @@ def convert_comment_image_sources(apps, schema_editor):
|
||||
# prefix 2
|
||||
if not settings.USE_MINIO and src and src.startswith(prefix2):
|
||||
img["src"] = (
|
||||
f"/api/workspaces/{comment.workspace.slug}/projects/{comment.project_id}/issues/{comment.id}/attachments/{src[len(prefix2): ]}/"
|
||||
f"{src[len(prefix2): ]}/"
|
||||
)
|
||||
file_assets[src[len(prefix2) :]] = {
|
||||
"project_id": str(comment.project_id),
|
||||
|
@ -3,3 +3,5 @@ from .user import UserLiteSerializer
|
||||
from .issue import LabelLiteSerializer, StateLiteSerializer
|
||||
|
||||
from .state import StateSerializer, StateLiteSerializer
|
||||
|
||||
from .asset import FileAssetSerializer
|
15
apiserver/plane/space/serializer/asset.py
Normal file
15
apiserver/plane/space/serializer/asset.py
Normal file
@ -0,0 +1,15 @@
|
||||
from .base import BaseFileSerializer
|
||||
from plane.db.models import FileAsset
|
||||
|
||||
|
||||
class FileAssetSerializer(BaseFileSerializer):
|
||||
|
||||
class Meta:
|
||||
model = FileAsset
|
||||
fields = "__all__"
|
||||
read_only_fields = [
|
||||
"created_by",
|
||||
"updated_by",
|
||||
"created_at",
|
||||
"updated_at",
|
||||
]
|
@ -56,3 +56,22 @@ class DynamicBaseSerializer(BaseSerializer):
|
||||
self.fields.pop(field_name)
|
||||
|
||||
return self.fields
|
||||
|
||||
|
||||
class BaseFileSerializer(DynamicBaseSerializer):
|
||||
|
||||
class Meta:
|
||||
abstract = True # Make this serializer abstract
|
||||
|
||||
def to_representation(self, instance):
|
||||
"""
|
||||
Object instance -> Dict of primitive datatypes.
|
||||
"""
|
||||
response = super().to_representation(instance)
|
||||
response[
|
||||
"asset"
|
||||
] = (
|
||||
instance.asset.name
|
||||
) # Ensure 'asset' field is consistently serialized
|
||||
# Apply custom method to get download URL
|
||||
return response
|
@ -36,6 +36,26 @@ urlpatterns = [
|
||||
),
|
||||
name="issue-comments-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",
|
||||
),
|
||||
path(
|
||||
"workspaces/<str:slug>/project-boards/<uuid:project_id>/issues/<uuid:issue_id>/reactions/",
|
||||
IssueReactionPublicViewSet.as_view(
|
||||
|
@ -1,7 +1,6 @@
|
||||
# Python imports
|
||||
import json
|
||||
|
||||
# Django imports
|
||||
from django.core.serializers.json import DjangoJSONEncoder
|
||||
from django.db.models import (
|
||||
Case,
|
||||
@ -18,10 +17,13 @@ from django.db.models import (
|
||||
When,
|
||||
)
|
||||
from django.http import HttpResponseRedirect
|
||||
|
||||
# Django imports
|
||||
from django.utils import timezone
|
||||
|
||||
# Third Party imports
|
||||
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
|
||||
|
||||
@ -34,6 +36,7 @@ from plane.app.serializers import (
|
||||
IssueReactionSerializer,
|
||||
IssueVoteSerializer,
|
||||
)
|
||||
from plane.app.views.base import BaseAPIView, BaseViewSet
|
||||
from plane.bgtasks.issue_activites_task import issue_activity
|
||||
from plane.db.models import (
|
||||
CommentReaction,
|
||||
@ -48,13 +51,12 @@ from plane.db.models import (
|
||||
ProjectMember,
|
||||
ProjectPublicMember,
|
||||
State,
|
||||
Workspace,
|
||||
)
|
||||
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):
|
||||
serializer_class = IssueCommentSerializer
|
||||
@ -218,6 +220,182 @@ class IssueCommentPublicViewSet(BaseViewSet):
|
||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
|
||||
class IssueAttachmentPublicEndpoint(BaseAPIView):
|
||||
def get_permissions(self):
|
||||
if self.action in ["get"]:
|
||||
self.permission_classes = [
|
||||
AllowAny,
|
||||
]
|
||||
else:
|
||||
self.permission_classes = [
|
||||
IsAuthenticated,
|
||||
]
|
||||
|
||||
return super(IssueAttachmentPublicEndpoint, self).get_permissions()
|
||||
|
||||
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)
|
||||
|
||||
|
||||
class CommentAssetPublicEndpoint(BaseAPIView):
|
||||
def get_permissions(self):
|
||||
if self.action in ["get"]:
|
||||
self.permission_classes = [
|
||||
AllowAny,
|
||||
]
|
||||
else:
|
||||
self.permission_classes = [
|
||||
IsAuthenticated,
|
||||
]
|
||||
|
||||
return super(CommentAssetPublicEndpoint, self).get_permissions()
|
||||
|
||||
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)
|
||||
|
||||
|
||||
class IssueReactionPublicViewSet(BaseViewSet):
|
||||
serializer_class = IssueReactionSerializer
|
||||
model = IssueReaction
|
||||
@ -690,73 +868,3 @@ 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