Skip to content

Commit

Permalink
feat: issue attachments (#677)
Browse files Browse the repository at this point in the history
  • Loading branch information
pablohashescobar authored Apr 4, 2023
1 parent ff5cdde commit 97386e9
Show file tree
Hide file tree
Showing 8 changed files with 121 additions and 2 deletions.
1 change: 1 addition & 0 deletions apiserver/plane/api/serializers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
IssueStateSerializer,
IssueLinkSerializer,
IssueLiteSerializer,
IssueAttachmentSerializer,
)

from .module import (
Expand Down
17 changes: 17 additions & 0 deletions apiserver/plane/api/serializers/issue.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
Module,
ModuleIssue,
IssueLink,
IssueAttachment,
)


Expand Down Expand Up @@ -439,6 +440,21 @@ def create(self, validated_data):
return IssueLink.objects.create(**validated_data)


class IssueAttachmentSerializer(BaseSerializer):
class Meta:
model = IssueAttachment
fields = "__all__"
read_only_fields = [
"created_by",
"updated_by",
"created_at",
"updated_at",
"workspace",
"project",
"issue",
]


# Issue Serializer with state details
class IssueStateSerializer(BaseSerializer):
state_detail = StateSerializer(read_only=True, source="state")
Expand Down Expand Up @@ -466,6 +482,7 @@ class IssueSerializer(BaseSerializer):
issue_cycle = IssueCycleDetailSerializer(read_only=True)
issue_module = IssueModuleDetailSerializer(read_only=True)
issue_link = IssueLinkSerializer(read_only=True, many=True)
issue_attachment = IssueAttachmentSerializer(read_only=True, many=True)
sub_issues_count = serializers.IntegerField(read_only=True)

class Meta:
Expand Down
11 changes: 11 additions & 0 deletions apiserver/plane/api/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@
SubIssuesEndpoint,
IssueLinkViewSet,
BulkCreateIssueLabelsEndpoint,
IssueAttachmentEndpoint,
## End Issues
# States
StateViewSet,
Expand Down Expand Up @@ -742,6 +743,16 @@
),
name="project-issue-links",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/issues/<uuid:issue_id>/issue-attachments/",
IssueAttachmentEndpoint.as_view(),
name="project-issue-attachments",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/issues/<uuid:issue_id>/issue-attachments/<uuid:pk>/",
IssueAttachmentEndpoint.as_view(),
name="project-issue-attachments",
),
## End Issues
## Issue Activity
path(
Expand Down
1 change: 1 addition & 0 deletions apiserver/plane/api/views/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@
SubIssuesEndpoint,
IssueLinkViewSet,
BulkCreateIssueLabelsEndpoint,
IssueAttachmentEndpoint,
)

from .auth_extended import (
Expand Down
2 changes: 2 additions & 0 deletions apiserver/plane/api/views/asset.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@ def delete(self, request, workspace_id, asset_key):


class UserAssetsEndpoint(BaseAPIView):
parser_classes = (MultiPartParser, FormParser)

def get(self, request, asset_key):
try:
files = FileAsset.objects.filter(asset=asset_key, created_by=request.user)
Expand Down
51 changes: 51 additions & 0 deletions apiserver/plane/api/views/issue.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
# Third Party imports
from rest_framework.response import Response
from rest_framework import status
from rest_framework.parsers import MultiPartParser, FormParser
from sentry_sdk import capture_exception

# Module imports
Expand All @@ -28,6 +29,7 @@
IssueFlatSerializer,
IssueLinkSerializer,
IssueLiteSerializer,
IssueAttachmentSerializer,
)
from plane.api.permissions import (
ProjectEntityPermission,
Expand All @@ -43,6 +45,7 @@
IssueProperty,
Label,
IssueLink,
IssueAttachment,
)
from plane.bgtasks.issue_activites_task import issue_activity
from plane.utils.grouper import group_results
Expand Down Expand Up @@ -683,3 +686,51 @@ def post(self, request, slug, project_id):
{"error": "Something went wrong please try again later"},
status=status.HTTP_400_BAD_REQUEST,
)


class IssueAttachmentEndpoint(BaseAPIView):
serializer_class = IssueAttachmentSerializer
permission_classes = [
ProjectEntityPermission,
]
model = IssueAttachment
parser_classes = (MultiPartParser, FormParser)

def post(self, request, slug, project_id, issue_id):
try:
serializer = IssueAttachmentSerializer(data=request.data)
if serializer.is_valid():
serializer.save(project_id=project_id, issue_id=issue_id)
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
except Exception as e:
capture_exception(e)
return Response(
{"error": "Something went wrong please try again later"},
status=status.HTTP_400_BAD_REQUEST,
)

def delete(self, request, slug, project_id, issue_id, pk):
try:
issue_attachment = IssueAttachment.objects.get(pk=pk)
issue_attachment.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
except IssueAttachment.DoesNotExist:
return Response(
{"error": "Issue Attachment does not exist"},
status=status.HTTP_400_BAD_REQUEST,
)

def get(self, request, slug, project_id, issue_id):
try:
issue_attachments = IssueAttachment.objects.filter(
issue_id=issue_id, workspace__slug=slug, project_id=project_id
)
serilaizer = IssueAttachmentSerializer(issue_attachments, many=True)
return Response(serilaizer.data, status=status.HTTP_200_OK)
except Exception as e:
capture_exception(e)
return Response(
{"error": "Something went wrong please try again later"},
status=status.HTTP_400_BAD_REQUEST,
)
3 changes: 2 additions & 1 deletion apiserver/plane/db/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
IssueBlocker,
IssueLink,
IssueSequence,
IssueAttachment,
)

from .asset import FileAsset
Expand Down Expand Up @@ -61,4 +62,4 @@

from .importer import Importer

from .page import Page, PageBlock, PageFavorite, PageLabel
from .page import Page, PageBlock, PageFavorite, PageLabel
37 changes: 36 additions & 1 deletion apiserver/plane/db/models/issue.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
# Python import
from uuid import uuid4

# Django imports
from django.contrib.postgres.fields import ArrayField
from django.db import models
from django.conf import settings
from django.db.models.signals import post_save
from django.dispatch import receiver
from django.utils import timezone
from django.core.exceptions import ValidationError

# Module imports
from . import ProjectBaseModel
Expand Down Expand Up @@ -54,7 +58,6 @@ class Issue(ProjectBaseModel):
through_fields=("issue", "assignee"),
)
sequence_id = models.IntegerField(default=1, verbose_name="Issue Sequence ID")
attachments = ArrayField(models.URLField(), size=10, blank=True, default=list)
labels = models.ManyToManyField(
"db.Label", blank=True, related_name="labels", through="IssueLabel"
)
Expand Down Expand Up @@ -194,6 +197,38 @@ def __str__(self):
return f"{self.issue.name} {self.url}"


def get_upload_path(instance, filename):
return f"{instance.workspace.id}/{uuid4().hex}-{filename}"


def file_size(value):
limit = 5 * 1024 * 1024
if value.size > limit:
raise ValidationError("File too large. Size should not exceed 5 MB.")


class IssueAttachment(ProjectBaseModel):
attributes = models.JSONField(default=dict)
asset = models.FileField(
upload_to=get_upload_path,
validators=[
file_size,
],
)
issue = models.ForeignKey(
"db.Issue", on_delete=models.CASCADE, related_name="issue_attachment"
)

class Meta:
verbose_name = "Issue Attachment"
verbose_name_plural = "Issue Attachments"
db_table = "issue_attachments"
ordering = ("-created_at",)

def __str__(self):
return f"{self.issue.name} {self.asset}"


class IssueActivity(ProjectBaseModel):
issue = models.ForeignKey(
Issue, on_delete=models.SET_NULL, null=True, related_name="issue_activity"
Expand Down

0 comments on commit 97386e9

Please sign in to comment.