-
Notifications
You must be signed in to change notification settings - Fork 2.3k
[WIKI-419] chore: new asset duplicate endpoint added #7172
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: preview
Are you sure you want to change the base?
Conversation
""" WalkthroughA new API endpoint for duplicating assets within a workspace has been introduced. The backend adds the Changes
Sequence Diagram(s)sequenceDiagram
participant Frontend as FileService (frontend)
participant Backend as DuplicateAssetEndpoint (backend)
participant DB as Database
participant S3 as S3Storage
Frontend->>Backend: POST /workspaces/{slug}/duplicate-assets/ (asset_ids, entity_type, entity_id, ...)
Backend->>DB: Validate workspace, project, and fetch original assets
loop For each asset_id
Backend->>S3: Copy storage object to new asset key
Backend->>DB: Create new FileAsset record with copied attributes
end
Backend->>DB: Set is_uploaded=True for new assets
Backend-->>Frontend: Return mapping {original_id: new_id}
Suggested labels
Suggested reviewers
Poem
✨ Finishing Touches
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
Pull Request Linked with Plane Work Items
Comment Automatically Generated by Plane |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 5
🔭 Outside diff range comments (1)
web/core/services/file.service.ts (1)
36-45
: 🛠️ Refactor suggestionRemove duplicate enum definition.
The local
TFileAssetType
enum duplicates the importedEFileAssetType
enum. This creates unnecessary duplication and potential maintenance issues.Since
EFileAssetType
is now imported from@plane/types
, remove the local enum definition and use the imported one consistently throughout the codebase.- export enum TFileAssetType { - COMMENT_DESCRIPTION = "COMMENT_DESCRIPTION", - ISSUE_ATTACHMENT = "ISSUE_ATTACHMENT", - ISSUE_DESCRIPTION = "ISSUE_DESCRIPTION", - PAGE_DESCRIPTION = "PAGE_DESCRIPTION", - PROJECT_COVER = "PROJECT_COVER", - USER_AVATAR = "USER_AVATAR", - USER_COVER = "USER_COVER", - WORKSPACE_LOGO = "WORKSPACE_LOGO", - }
🧹 Nitpick comments (1)
apiserver/plane/app/views/asset/v2.py (1)
783-783
: Fix line length violation.The line exceeds the maximum length limit (106 > 88 characters) as flagged by static analysis.
Break the long line for better readability:
- destination_key = f"{workspace.id}/{uuid.uuid4().hex}-{original_asset.attributes.get('name')}" + destination_key = ( + f"{workspace.id}/{uuid.uuid4().hex}-" + f"{original_asset.attributes.get('name')}" + )🧰 Tools
🪛 Ruff (0.11.9)
783-783: Line too long (106 > 88)
(E501)
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (4)
apiserver/plane/app/urls/asset.py
(2 hunks)apiserver/plane/app/views/__init__.py
(1 hunks)apiserver/plane/app/views/asset/v2.py
(1 hunks)web/core/services/file.service.ts
(2 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
apiserver/plane/app/urls/asset.py (1)
apiserver/plane/app/views/asset/v2.py (1)
DuplicateAssetEndpoint
(723-810)
🪛 Ruff (0.11.9)
apiserver/plane/app/views/asset/v2.py
783-783: Line too long (106 > 88)
(E501)
🪛 Pylint (3.3.7)
apiserver/plane/app/views/asset/v2.py
[refactor] 724-724: Too many return statements (7/6)
(R0911)
⏰ Context from checks skipped due to timeout of 90000ms (1)
- GitHub Check: Analyze (javascript)
🔇 Additional comments (5)
apiserver/plane/app/views/__init__.py (1)
110-110
: LGTM!The import addition is correctly placed and follows the existing pattern for asset v2 endpoints.
apiserver/plane/app/urls/asset.py (1)
16-16
: LGTM!The import addition follows the existing pattern and is correctly placed.
web/core/services/file.service.ts (2)
4-4
: Good import addition.The import of
EFileAssetType
from the types package is the correct approach for type consistency.
262-276
: LGTM!The
duplicateAssets
method is well-implemented with:
- Correct parameter types and return type
- Proper endpoint URL construction
- Consistent error handling pattern
- Clear method signature that matches the backend API
apiserver/plane/app/views/asset/v2.py (1)
804-808
: LGTM!The bulk update to set
is_uploaded=True
for all duplicated assets is efficient and correct. The conditional check ensures the update only happens when there are successfully duplicated assets.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
♻️ Duplicate comments (4)
apiserver/plane/app/views/asset/v2.py (4)
728-759
: 🛠️ Refactor suggestionExtract duplicated method to reduce code duplication.
This method is duplicated across multiple endpoint classes (
WorkspaceFileAssetEndpoint
,ProjectAssetEndpoint
, and nowDuplicateAssetEndpoint
), violating the DRY principle.Extract this method to the
BaseAPIView
class or create a mixin. Additionally, refactor to use a dictionary mapping to reduce the number of return statements:def get_entity_id_field(self, entity_type, entity_id): entity_field_mapping = { FileAsset.EntityTypeContext.WORKSPACE_LOGO: {"workspace_id": entity_id}, FileAsset.EntityTypeContext.PROJECT_COVER: {"project_id": entity_id}, FileAsset.EntityTypeContext.USER_AVATAR: {"user_id": entity_id}, FileAsset.EntityTypeContext.USER_COVER: {"user_id": entity_id}, FileAsset.EntityTypeContext.ISSUE_ATTACHMENT: {"issue_id": entity_id}, FileAsset.EntityTypeContext.ISSUE_DESCRIPTION: {"issue_id": entity_id}, FileAsset.EntityTypeContext.PAGE_DESCRIPTION: {"page_id": entity_id}, FileAsset.EntityTypeContext.COMMENT_DESCRIPTION: {"comment_id": entity_id}, } return entity_field_mapping.get(entity_type, {})🧰 Tools
🪛 Pylint (3.3.7)
[refactor] 728-728: Too many return statements (7/6)
(R0911)
761-767
: 🛠️ Refactor suggestionAdd validation for entity_type parameter.
The
entity_type
parameter is retrieved but not validated against allowed values, which could lead to invalid data being stored.Add validation after line 766:
entity_type = request.data.get("entity_type", None) + + if entity_type and entity_type not in FileAsset.EntityTypeContext.values: + return Response( + {"error": "Invalid entity type.", "status": False}, + status=status.HTTP_400_BAD_REQUEST, + )
783-785
: 🛠️ Refactor suggestionAdd validation for asset existence and upload status.
The code fetches assets but doesn't validate that all requested
asset_ids
exist or that the assets are uploaded, which could lead to silent failures.Add validation after fetching the assets:
original_assets = FileAsset.objects.filter( workspace=workspace, id__in=asset_ids ) + + # Validate all requested assets exist + found_asset_ids = set(str(asset.id) for asset in original_assets) + missing_asset_ids = set(asset_ids) - found_asset_ids + if missing_asset_ids: + return Response( + {"error": f"Assets not found: {', '.join(missing_asset_ids)}"}, + status=status.HTTP_404_NOT_FOUND, + ) + + # Validate all assets are uploaded + unuploaded_assets = [str(asset.id) for asset in original_assets if not asset.is_uploaded] + if unuploaded_assets: + return Response( + {"error": f"Assets not uploaded: {', '.join(unuploaded_assets)}"}, + status=status.HTTP_400_BAD_REQUEST, + )
805-806
:⚠️ Potential issueAdd error handling for storage copy operation.
The
storage.copy_object()
call lacks error handling, which could leave the database in an inconsistent state if the copy fails.Wrap the storage operation in error handling:
- storage.copy_object(original_asset.asset, destination_key) - duplicated_assets[str(original_asset.id)] = str(duplicated_asset.id) + try: + storage.copy_object(original_asset.asset, destination_key) + duplicated_assets[str(original_asset.id)] = str(duplicated_asset.id) + except Exception as e: + # Clean up the created asset record if storage copy fails + duplicated_asset.delete() + return Response( + {"error": f"Failed to copy storage object: {str(e)}"}, + status=status.HTTP_500_INTERNAL_SERVER_ERROR, + )
🧹 Nitpick comments (1)
apiserver/plane/app/views/asset/v2.py (1)
808-814
: Consider transaction safety for bulk operations.The bulk update operation and the preceding asset creation loop could benefit from database transaction wrapping to ensure atomicity.
Consider wrapping the duplication logic in a database transaction:
+ from django.db import transaction + + @transaction.atomic def post(self, request, slug):This ensures that if any part of the duplication process fails, all changes are rolled back consistently.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
apiserver/plane/app/views/asset/v2.py
(2 hunks)apiserver/plane/settings/common.py
(1 hunks)
🧰 Additional context used
🪛 Ruff (0.11.9)
apiserver/plane/app/views/asset/v2.py
787-787: Line too long (106 > 88)
(E501)
🪛 Pylint (3.3.7)
apiserver/plane/app/views/asset/v2.py
[refactor] 728-728: Too many return statements (7/6)
(R0911)
⏰ Context from checks skipped due to timeout of 90000ms (1)
- GitHub Check: Analyze (javascript)
🔇 Additional comments (3)
apiserver/plane/settings/common.py (1)
74-78
: LGTM! Throttling configuration is appropriate.The throttling settings are well-configured for the new duplicate asset endpoint:
AnonRateThrottle
provides rate limiting for anonymous users- The
image_duplicate
scope with 10 requests/minute is reasonable for asset duplication operations- The anonymous rate of 30 requests/minute is balanced
apiserver/plane/app/views/asset/v2.py (2)
14-14
: Import addition looks good.The
ScopedRateThrottle
import is necessary for the new duplicate endpoint's throttling functionality.
723-727
: Throttling configuration is properly implemented.The class-level throttling configuration using
ScopedRateThrottle
with the"image_duplicate"
scope addresses the rate limiting requirement mentioned in past reviews.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
♻️ Duplicate comments (4)
apiserver/plane/app/views/asset/v2.py (4)
727-758
: 🛠️ Refactor suggestionExtract duplicated method to base class.
The
get_entity_id_field
method is duplicated across multiple endpoint classes in this file (WorkspaceFileAssetEndpoint
,ProjectAssetEndpoint
, and nowDuplicateAssetEndpoint
). This violates the DRY principle and creates maintenance overhead.Consider extracting this method to the
BaseAPIView
class or creating a mixin class that can be shared across these asset endpoints.Additionally, the static analysis correctly identifies that this method has too many return statements (7/6), which affects readability. Consider using a dictionary lookup pattern:
def get_entity_id_field(self, entity_type, entity_id): entity_field_mapping = { FileAsset.EntityTypeContext.WORKSPACE_LOGO: {"workspace_id": entity_id}, FileAsset.EntityTypeContext.PROJECT_COVER: {"project_id": entity_id}, FileAsset.EntityTypeContext.USER_AVATAR: {"user_id": entity_id}, FileAsset.EntityTypeContext.USER_COVER: {"user_id": entity_id}, FileAsset.EntityTypeContext.ISSUE_ATTACHMENT: {"issue_id": entity_id}, FileAsset.EntityTypeContext.ISSUE_DESCRIPTION: {"issue_id": entity_id}, FileAsset.EntityTypeContext.PAGE_DESCRIPTION: {"page_id": entity_id}, FileAsset.EntityTypeContext.COMMENT_DESCRIPTION: {"comment_id": entity_id}, } return entity_field_mapping.get(entity_type, {})🧰 Tools
🪛 Pylint (3.3.7)
[refactor] 727-727: Too many return statements (7/6)
(R0911)
764-764
: 🛠️ Refactor suggestionAdd validation for entity_type when provided.
The
entity_type
parameter is accepted but not validated against allowed values. This could lead to invalid entity types being stored in the database.Add validation similar to other endpoints in this file:
+ if entity_type and entity_type not in FileAsset.EntityTypeContext.values: + return Response( + {"error": "Invalid entity type.", "status": False}, + status=status.HTTP_400_BAD_REQUEST, + )
775-777
:⚠️ Potential issueAdd validation for asset existence and upload status.
The code fetches the original asset but doesn't validate that the requested
asset_id
exists or that the asset is uploaded. This could lead to silent failures or attempting to copy non-existent storage objects.Add validation after fetching the asset:
original_asset = FileAsset.objects.filter( workspace=workspace, id=asset_id ).first() + + # Validate asset exists + if not original_asset: + return Response( + {"error": "Asset not found"}, + status=status.HTTP_404_NOT_FOUND, + ) + + # Validate asset is uploaded + if not original_asset.is_uploaded: + return Response( + {"error": "Asset not uploaded"}, + status=status.HTTP_400_BAD_REQUEST, + )
795-795
:⚠️ Potential issueAdd error handling for storage copy operation.
The
storage.copy_object()
call can fail if the source object doesn't exist or due to storage service issues, but there's no error handling. This could leave the database in an inconsistent state with asset records that don't have corresponding storage objects.Add error handling around the storage operation:
- storage.copy_object(original_asset.asset, destination_key) + try: + storage.copy_object(original_asset.asset, destination_key) + except Exception as e: + # Clean up the created asset record if storage copy fails + duplicated_asset.delete() + return Response( + {"error": f"Failed to copy storage object: {str(e)}"}, + status=status.HTTP_500_INTERNAL_SERVER_ERROR, + )
🧹 Nitpick comments (1)
apiserver/plane/app/views/asset/v2.py (1)
779-779
: Fix line length violation.The line exceeds the maximum length of 88 characters.
- destination_key = f"{workspace.id}/{uuid.uuid4().hex}-{original_asset.attributes.get('name')}" + destination_key = ( + f"{workspace.id}/{uuid.uuid4().hex}-" + f"{original_asset.attributes.get('name')}" + )🧰 Tools
🪛 Ruff (0.11.9)
779-779: Line too long (102 > 88)
(E501)
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (4)
apiserver/plane/app/throttles/asset.py
(1 hunks)apiserver/plane/app/urls/asset.py
(2 hunks)apiserver/plane/app/views/asset/v2.py
(2 hunks)apiserver/plane/settings/common.py
(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
- apiserver/plane/app/urls/asset.py
- apiserver/plane/settings/common.py
🧰 Additional context used
🪛 Pylint (3.3.7)
apiserver/plane/app/throttles/asset.py
[refactor] 4-4: Too few public methods (1/2)
(R0903)
apiserver/plane/app/views/asset/v2.py
[refactor] 727-727: Too many return statements (7/6)
(R0911)
🪛 Ruff (0.11.9)
apiserver/plane/app/views/asset/v2.py
779-779: Line too long (102 > 88)
(E501)
⏰ Context from checks skipped due to timeout of 90000ms (1)
- GitHub Check: Analyze (javascript)
🔇 Additional comments (3)
apiserver/plane/app/throttles/asset.py (1)
1-11
: LGTM! Clean throttling implementation.The
AssetRateThrottle
class correctly implements rate limiting based onasset_id
. The implementation follows Django REST framework patterns and properly handles cases whereasset_id
might not be present.🧰 Tools
🪛 Pylint (3.3.7)
[refactor] 4-4: Too few public methods (1/2)
(R0903)
apiserver/plane/app/views/asset/v2.py (2)
22-22
: LGTM! Proper import of throttling class.The import correctly adds the
AssetRateThrottle
class needed for the new endpoint.
723-726
: LGTM! Throttling properly configured.The endpoint correctly applies the
AssetRateThrottle
to implement rate limiting based on asset ID as requested in previous reviews.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 3
♻️ Duplicate comments (2)
apiserver/plane/app/views/asset/v2.py (2)
795-797
:storage.copy_object
still lacks error handlingA previous review pointed this out. Failure here leaves a DB row without a real object.
Wrap intry/except
, roll back the newFileAsset
, and return a 500.
727-758
: 🛠️ Refactor suggestion
get_entity_id_field
is still duplicated – extract or centralise to stay DRYThis helper is now present in three endpoint classes with identical logic. The earlier review already requested moving it into a shared location (e.g., a mix-in or
BaseAPIView
) and replacing the cascade ofif
statements with a lookup dict.
Keeping three copies will inevitably drift.🧰 Tools
🪛 Pylint (3.3.7)
[refactor] 727-727: Too many return statements (7/6)
(R0911)
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
apiserver/plane/app/urls/asset.py
(2 hunks)apiserver/plane/app/views/__init__.py
(1 hunks)apiserver/plane/app/views/asset/v2.py
(2 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
- apiserver/plane/app/views/init.py
- apiserver/plane/app/urls/asset.py
🧰 Additional context used
🪛 Ruff (0.11.9)
apiserver/plane/app/views/asset/v2.py
779-779: Line too long (102 > 88)
(E501)
🪛 Pylint (3.3.7)
apiserver/plane/app/views/asset/v2.py
[refactor] 727-727: Too many return statements (7/6)
(R0911)
⏰ Context from checks skipped due to timeout of 90000ms (1)
- GitHub Check: Analyze (javascript)
# Update the is_uploaded field for all newly created assets | ||
FileAsset.objects.filter(id=duplicated_asset.id).update(is_uploaded=True) | ||
|
||
return Response(duplicated_asset.id, status=status.HTTP_200_OK) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
API should return JSON, not bare UUID
The rest of the API surface consistently wraps IDs in a JSON object. Returning a raw UUID string breaks client expectations and makes the response non-extensible.
- return Response(duplicated_asset.id, status=status.HTTP_200_OK)
+ return Response(
+ {"asset_id": str(duplicated_asset.id)},
+ status=status.HTTP_201_CREATED,
+ )
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
return Response(duplicated_asset.id, status=status.HTTP_200_OK) | |
return Response( | |
{"asset_id": str(duplicated_asset.id)}, | |
status=status.HTTP_201_CREATED, | |
) |
🤖 Prompt for AI Agents
In apiserver/plane/app/views/asset/v2.py at line 799, the API currently returns
a bare UUID string which breaks client expectations and consistency. Modify the
return statement to wrap the duplicated_asset.id inside a JSON object, for
example returning a dictionary with a key like "id" mapped to the UUID, and then
pass that dictionary to the Response. This ensures the response is JSON
formatted and extensible.
@allow_permission([ROLE.ADMIN, ROLE.MEMBER], level="WORKSPACE") | ||
def post(self, request, slug, asset_id): | ||
project_id = request.data.get("project_id", None) | ||
entity_id = request.data.get("entity_id", None) | ||
entity_type = request.data.get("entity_type", None) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Validate request payload before touching the DB
entity_type
, entity_id
and (optionally) project_id
are accepted unchecked.
If entity_type
is missing or invalid, FileAsset
will be created with an invalid enum value and raise at save-time.
Add upfront validation consistent with other endpoints.
+ if entity_type not in FileAsset.EntityTypeContext.values:
+ return Response(
+ {"error": "Invalid entity type", "status": False},
+ status=status.HTTP_400_BAD_REQUEST,
+ )
+ if not entity_id:
+ return Response(
+ {"error": "Missing entity_id", "status": False},
+ status=status.HTTP_400_BAD_REQUEST,
+ )
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
@allow_permission([ROLE.ADMIN, ROLE.MEMBER], level="WORKSPACE") | |
def post(self, request, slug, asset_id): | |
project_id = request.data.get("project_id", None) | |
entity_id = request.data.get("entity_id", None) | |
entity_type = request.data.get("entity_type", None) | |
@allow_permission([ROLE.ADMIN, ROLE.MEMBER], level="WORKSPACE") | |
def post(self, request, slug, asset_id): | |
project_id = request.data.get("project_id", None) | |
entity_id = request.data.get("entity_id", None) | |
entity_type = request.data.get("entity_type", None) | |
if entity_type not in FileAsset.EntityTypeContext.values: | |
return Response( | |
{"error": "Invalid entity type", "status": False}, | |
status=status.HTTP_400_BAD_REQUEST, | |
) | |
if not entity_id: | |
return Response( | |
{"error": "Missing entity_id", "status": False}, | |
status=status.HTTP_400_BAD_REQUEST, | |
) | |
# …rest of the existing implementation… |
🤖 Prompt for AI Agents
In apiserver/plane/app/views/asset/v2.py around lines 760 to 765, the request
payload fields entity_type, entity_id, and optionally project_id are used
without validation, which can cause errors when saving FileAsset with invalid
enum values. Add validation logic before any database operations to check that
entity_type is present and valid, entity_id is provided, and project_id if given
is valid. This validation should follow the pattern used in other endpoints to
ensure consistent and early error handling.
workspace = Workspace.objects.get(slug=slug) | ||
if project_id: | ||
# check if project exists in the workspace | ||
if not Project.objects.filter(id=project_id, workspace=workspace).exists(): | ||
return Response( | ||
{"error": "Project not found"}, status=status.HTTP_404_NOT_FOUND | ||
) | ||
|
||
storage = S3Storage() | ||
original_asset = FileAsset.objects.filter( | ||
workspace=workspace, id=asset_id | ||
).first() | ||
# for original_asset in original_assets: | ||
destination_key = f"{workspace.id}/{uuid.uuid4().hex}-{original_asset.attributes.get('name')}" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Guard against missing or not-uploaded source asset
original_asset = … .first()
may return None
, or the asset may still be uploading.
Accessing .attributes
on None
(or copying an incomplete asset) will raise 500s.
- original_asset = FileAsset.objects.filter(
- workspace=workspace, id=asset_id
- ).first()
+ original_asset = FileAsset.objects.filter(
+ workspace=workspace,
+ id=asset_id,
+ is_uploaded=True,
+ ).first()
+
+ if not original_asset:
+ return Response(
+ {"error": "Source asset not found or not uploaded"},
+ status=status.HTTP_404_NOT_FOUND,
+ )
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
workspace = Workspace.objects.get(slug=slug) | |
if project_id: | |
# check if project exists in the workspace | |
if not Project.objects.filter(id=project_id, workspace=workspace).exists(): | |
return Response( | |
{"error": "Project not found"}, status=status.HTTP_404_NOT_FOUND | |
) | |
storage = S3Storage() | |
original_asset = FileAsset.objects.filter( | |
workspace=workspace, id=asset_id | |
).first() | |
# for original_asset in original_assets: | |
destination_key = f"{workspace.id}/{uuid.uuid4().hex}-{original_asset.attributes.get('name')}" | |
workspace = Workspace.objects.get(slug=slug) | |
if project_id: | |
# check if project exists in the workspace | |
if not Project.objects.filter(id=project_id, workspace=workspace).exists(): | |
return Response( | |
{"error": "Project not found"}, status=status.HTTP_404_NOT_FOUND | |
) | |
storage = S3Storage() | |
- original_asset = FileAsset.objects.filter( | |
- workspace=workspace, id=asset_id | |
- ).first() | |
+ original_asset = FileAsset.objects.filter( | |
+ workspace=workspace, | |
+ id=asset_id, | |
+ is_uploaded=True, | |
+ ).first() | |
+ | |
+ if not original_asset: | |
+ return Response( | |
+ {"error": "Source asset not found or not uploaded"}, | |
+ status=status.HTTP_404_NOT_FOUND, | |
+ ) | |
# for original_asset in original_assets: | |
destination_key = f"{workspace.id}/{uuid.uuid4().hex}-{original_asset.attributes.get('name')}" |
🧰 Tools
🪛 Ruff (0.11.9)
779-779: Line too long (102 > 88)
(E501)
🤖 Prompt for AI Agents
In apiserver/plane/app/views/asset/v2.py around lines 766 to 779, the code
assigns original_asset using .first() which can return None if no matching asset
is found. Accessing original_asset.attributes without checking for None can
cause a server error. Add a guard to check if original_asset is None or if it is
still uploading before proceeding. If either condition is true, return an
appropriate error response to prevent 500 errors.
Description
This PR introduces a new endpoint to duplicate assets and attach them to an entiyt without the need to re-upload them.
Type of Change
Summary by CodeRabbit