dev: update the response for assets

This commit is contained in:
pablohashescobar 2024-01-31 18:43:02 +05:30
parent c9d2ea36b8
commit 42f307421a
8 changed files with 102 additions and 20 deletions

View File

@ -1,8 +1,9 @@
from .base import BaseSerializer from .base import BaseFileSerializer
from plane.db.models import FileAsset from plane.db.models import FileAsset
class FileAssetSerializer(BaseSerializer): class FileAssetSerializer(BaseFileSerializer):
class Meta: class Meta:
model = FileAsset model = FileAsset
fields = "__all__" fields = "__all__"

View File

@ -1,4 +1,5 @@
from rest_framework import serializers from rest_framework import serializers
from plane.settings.storage import S3PrivateBucketStorage
class BaseSerializer(serializers.ModelSerializer): class BaseSerializer(serializers.ModelSerializer):
@ -60,7 +61,7 @@ class DynamicBaseSerializer(BaseSerializer):
CycleIssueSerializer, CycleIssueSerializer,
IssueFlatSerializer, IssueFlatSerializer,
IssueRelationSerializer, IssueRelationSerializer,
InboxIssueLiteSerializer InboxIssueLiteSerializer,
) )
# Expansion mapper # Expansion mapper
@ -84,7 +85,19 @@ class DynamicBaseSerializer(BaseSerializer):
"issue_inbox": InboxIssueLiteSerializer, "issue_inbox": InboxIssueLiteSerializer,
} }
self.fields[field] = expansion[field](many=True if field in ["members", "assignees", "labels", "issue_cycle", "issue_relation", "issue_inbox"] else False) self.fields[field] = expansion[field](
many=True
if field
in [
"members",
"assignees",
"labels",
"issue_cycle",
"issue_relation",
"issue_inbox",
]
else False
)
return self.fields return self.fields
@ -105,7 +118,7 @@ class DynamicBaseSerializer(BaseSerializer):
LabelSerializer, LabelSerializer,
CycleIssueSerializer, CycleIssueSerializer,
IssueRelationSerializer, IssueRelationSerializer,
InboxIssueLiteSerializer InboxIssueLiteSerializer,
) )
# Expansion mapper # Expansion mapper
@ -146,3 +159,29 @@ class DynamicBaseSerializer(BaseSerializer):
) )
return response return response
class BaseFileSerializer(DynamicBaseSerializer):
download_url = serializers.SerializerMethodField()
class Meta:
abstract = True # Make this serializer abstract
def get_download_url(self, obj):
if hasattr(obj, "asset") and obj.asset:
storage = S3PrivateBucketStorage()
return storage.download_url(obj.asset.name)
return None
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

View File

@ -5,7 +5,7 @@ from django.utils import timezone
from rest_framework import serializers from rest_framework import serializers
# Module imports # Module imports
from .base import BaseSerializer, DynamicBaseSerializer from .base import BaseSerializer, DynamicBaseSerializer, BaseFileSerializer
from .user import UserLiteSerializer from .user import UserLiteSerializer
from .state import StateSerializer, StateLiteSerializer from .state import StateSerializer, StateLiteSerializer
from .project import ProjectLiteSerializer from .project import ProjectLiteSerializer
@ -444,7 +444,8 @@ class IssueLinkSerializer(BaseSerializer):
return IssueLink.objects.create(**validated_data) return IssueLink.objects.create(**validated_data)
class IssueAttachmentSerializer(BaseSerializer): class IssueAttachmentSerializer(BaseFileSerializer):
class Meta: class Meta:
model = IssueAttachment model = IssueAttachment
fields = "__all__" fields = "__all__"
@ -503,9 +504,7 @@ class IssueCommentSerializer(BaseSerializer):
workspace_detail = WorkspaceLiteSerializer( workspace_detail = WorkspaceLiteSerializer(
read_only=True, source="workspace" read_only=True, source="workspace"
) )
comment_reactions = CommentReactionSerializer( comment_reactions = CommentReactionSerializer(read_only=True, many=True)
read_only=True, many=True
)
is_member = serializers.BooleanField(read_only=True) is_member = serializers.BooleanField(read_only=True)
class Meta: class Meta:
@ -615,7 +614,10 @@ class IssueSerializer(DynamicBaseSerializer):
def get_module_ids(self, obj): def get_module_ids(self, obj):
# Access the prefetched modules and extract module IDs # Access the prefetched modules and extract module IDs
return [module for module in obj.issue_module.values_list("module_id", flat=True)] return [
module
for module in obj.issue_module.values_list("module_id", flat=True)
]
class IssueLiteSerializer(DynamicBaseSerializer): class IssueLiteSerializer(DynamicBaseSerializer):

View File

@ -8,12 +8,12 @@ from django.conf import settings
# Module import # Module import
from . import BaseModel from . import BaseModel
from plane.settings.storage import S3PrivateBucketStorage
def get_upload_path(instance, filename): def get_upload_path(instance, filename):
if instance.workspace_id is not None: if instance.workspace_id is not None:
return f"{instance.workspace.id}/{uuid4().hex}-{filename}" return f"{instance.workspace.id}/{uuid4().hex}"
return f"user-{uuid4().hex}-{filename}" return f"user-{uuid4().hex}"
def file_size(value): def file_size(value):
@ -32,6 +32,7 @@ class FileAsset(BaseModel):
validators=[ validators=[
file_size, file_size,
], ],
storage=S3PrivateBucketStorage(),
) )
workspace = models.ForeignKey( workspace = models.ForeignKey(
"db.Workspace", "db.Workspace",

View File

@ -337,7 +337,7 @@ class IssueLink(ProjectBaseModel):
def get_upload_path(instance, filename): def get_upload_path(instance, filename):
return f"{instance.workspace.id}/{uuid4().hex}-{filename}" return f"{instance.workspace.id}/{uuid4().hex}"
def file_size(value): def file_size(value):

View File

@ -226,14 +226,14 @@ STORAGES = {
}, },
} }
STORAGES["default"] = { STORAGES["default"] = {
"BACKEND": "storages.backends.s3boto3.S3Boto3Storage", "BACKEND": "plane.settings.storage.S3PrivateBucketStorage",
} }
AWS_ACCESS_KEY_ID = os.environ.get("AWS_ACCESS_KEY_ID", "access-key") AWS_ACCESS_KEY_ID = os.environ.get("AWS_ACCESS_KEY_ID", "access-key")
AWS_SECRET_ACCESS_KEY = os.environ.get("AWS_SECRET_ACCESS_KEY", "secret-key") AWS_SECRET_ACCESS_KEY = os.environ.get("AWS_SECRET_ACCESS_KEY", "secret-key")
AWS_STORAGE_BUCKET_NAME = os.environ.get("AWS_S3_BUCKET_NAME", "uploads") AWS_STORAGE_BUCKET_NAME = os.environ.get("AWS_S3_BUCKET_NAME", "uploads")
AWS_REGION = os.environ.get("AWS_REGION", "") AWS_REGION = os.environ.get("AWS_REGION", "")
AWS_DEFAULT_ACL = "public-read" AWS_DEFAULT_ACL = "public-read"
AWS_QUERYSTRING_AUTH = False AWS_QUERYSTRING_AUTH = True
AWS_S3_FILE_OVERWRITE = False AWS_S3_FILE_OVERWRITE = False
AWS_S3_ENDPOINT_URL = os.environ.get( AWS_S3_ENDPOINT_URL = os.environ.get(
"AWS_S3_ENDPOINT_URL", None "AWS_S3_ENDPOINT_URL", None

View File

@ -0,0 +1,15 @@
# Third party imports
from storages.backends.s3boto3 import S3Boto3Storage
# Module imports
from plane.utils.presigned_url_generator import generate_download_presigned_url
class S3PrivateBucketStorage(S3Boto3Storage):
def url(self, name):
# Return an empty string or None, or implement custom logic here
return name
def download_url(self, name):
return generate_download_presigned_url(name)

View File

@ -0,0 +1,24 @@
import boto3
from django.conf import settings
def generate_download_presigned_url(object_name, expiration=3600):
"""
Generate a presigned URL to download an object from S3.
:param object_name: The key name of the object in the S3 bucket.
:param expiration: Time in seconds for the presigned URL to remain valid (default is 1 hour).
:return: Presigned URL as a string. If error, returns None.
"""
s3_client = boto3.client('s3',
aws_access_key_id=settings.AWS_ACCESS_KEY_ID,
aws_secret_access_key=settings.AWS_SECRET_ACCESS_KEY,
region_name=settings.AWS_REGION,
endpoint_url=settings.AWS_S3_ENDPOINT_URL)
try:
response = s3_client.generate_presigned_url('get_object',
Params={'Bucket': settings.AWS_STORAGE_BUCKET_NAME,
'Key': object_name},
ExpiresIn=expiration)
return response
except Exception as e:
print(f"Error generating presigned download URL: {e}")
return None