forked from github/plane
fix: file structuring (#2797)
* fix: file structure changes * fix: pages update * fix: license imports changed
This commit is contained in:
parent
4c2074b6ff
commit
51e17643a2
@ -1,5 +0,0 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class ApiConfig(AppConfig):
|
||||
name = "plane.api"
|
@ -1,151 +0,0 @@
|
||||
from django.urls import path
|
||||
|
||||
|
||||
from plane.api.views import (
|
||||
ProjectDeployBoardViewSet,
|
||||
ProjectDeployBoardPublicSettingsEndpoint,
|
||||
ProjectIssuesPublicEndpoint,
|
||||
IssueRetrievePublicEndpoint,
|
||||
IssueCommentPublicViewSet,
|
||||
IssueReactionPublicViewSet,
|
||||
CommentReactionPublicViewSet,
|
||||
InboxIssuePublicViewSet,
|
||||
IssueVotePublicViewSet,
|
||||
WorkspaceProjectDeployBoardEndpoint,
|
||||
)
|
||||
|
||||
|
||||
urlpatterns = [
|
||||
path(
|
||||
"workspaces/<str:slug>/projects/<uuid:project_id>/project-deploy-boards/",
|
||||
ProjectDeployBoardViewSet.as_view(
|
||||
{
|
||||
"get": "list",
|
||||
"post": "create",
|
||||
}
|
||||
),
|
||||
name="project-deploy-board",
|
||||
),
|
||||
path(
|
||||
"workspaces/<str:slug>/projects/<uuid:project_id>/project-deploy-boards/<uuid:pk>/",
|
||||
ProjectDeployBoardViewSet.as_view(
|
||||
{
|
||||
"get": "retrieve",
|
||||
"patch": "partial_update",
|
||||
"delete": "destroy",
|
||||
}
|
||||
),
|
||||
name="project-deploy-board",
|
||||
),
|
||||
path(
|
||||
"public/workspaces/<str:slug>/project-boards/<uuid:project_id>/settings/",
|
||||
ProjectDeployBoardPublicSettingsEndpoint.as_view(),
|
||||
name="project-deploy-board-settings",
|
||||
),
|
||||
path(
|
||||
"public/workspaces/<str:slug>/project-boards/<uuid:project_id>/issues/",
|
||||
ProjectIssuesPublicEndpoint.as_view(),
|
||||
name="project-deploy-board",
|
||||
),
|
||||
path(
|
||||
"public/workspaces/<str:slug>/project-boards/<uuid:project_id>/issues/<uuid:issue_id>/",
|
||||
IssueRetrievePublicEndpoint.as_view(),
|
||||
name="workspace-project-boards",
|
||||
),
|
||||
path(
|
||||
"public/workspaces/<str:slug>/project-boards/<uuid:project_id>/issues/<uuid:issue_id>/comments/",
|
||||
IssueCommentPublicViewSet.as_view(
|
||||
{
|
||||
"get": "list",
|
||||
"post": "create",
|
||||
}
|
||||
),
|
||||
name="issue-comments-project-board",
|
||||
),
|
||||
path(
|
||||
"public/workspaces/<str:slug>/project-boards/<uuid:project_id>/issues/<uuid:issue_id>/comments/<uuid:pk>/",
|
||||
IssueCommentPublicViewSet.as_view(
|
||||
{
|
||||
"get": "retrieve",
|
||||
"patch": "partial_update",
|
||||
"delete": "destroy",
|
||||
}
|
||||
),
|
||||
name="issue-comments-project-board",
|
||||
),
|
||||
path(
|
||||
"public/workspaces/<str:slug>/project-boards/<uuid:project_id>/issues/<uuid:issue_id>/reactions/",
|
||||
IssueReactionPublicViewSet.as_view(
|
||||
{
|
||||
"get": "list",
|
||||
"post": "create",
|
||||
}
|
||||
),
|
||||
name="issue-reactions-project-board",
|
||||
),
|
||||
path(
|
||||
"public/workspaces/<str:slug>/project-boards/<uuid:project_id>/issues/<uuid:issue_id>/reactions/<str:reaction_code>/",
|
||||
IssueReactionPublicViewSet.as_view(
|
||||
{
|
||||
"delete": "destroy",
|
||||
}
|
||||
),
|
||||
name="issue-reactions-project-board",
|
||||
),
|
||||
path(
|
||||
"public/workspaces/<str:slug>/project-boards/<uuid:project_id>/comments/<uuid:comment_id>/reactions/",
|
||||
CommentReactionPublicViewSet.as_view(
|
||||
{
|
||||
"get": "list",
|
||||
"post": "create",
|
||||
}
|
||||
),
|
||||
name="comment-reactions-project-board",
|
||||
),
|
||||
path(
|
||||
"public/workspaces/<str:slug>/project-boards/<uuid:project_id>/comments/<uuid:comment_id>/reactions/<str:reaction_code>/",
|
||||
CommentReactionPublicViewSet.as_view(
|
||||
{
|
||||
"delete": "destroy",
|
||||
}
|
||||
),
|
||||
name="comment-reactions-project-board",
|
||||
),
|
||||
path(
|
||||
"public/workspaces/<str:slug>/project-boards/<uuid:project_id>/inboxes/<uuid:inbox_id>/inbox-issues/",
|
||||
InboxIssuePublicViewSet.as_view(
|
||||
{
|
||||
"get": "list",
|
||||
"post": "create",
|
||||
}
|
||||
),
|
||||
name="inbox-issue",
|
||||
),
|
||||
path(
|
||||
"public/workspaces/<str:slug>/project-boards/<uuid:project_id>/inboxes/<uuid:inbox_id>/inbox-issues/<uuid:pk>/",
|
||||
InboxIssuePublicViewSet.as_view(
|
||||
{
|
||||
"get": "retrieve",
|
||||
"patch": "partial_update",
|
||||
"delete": "destroy",
|
||||
}
|
||||
),
|
||||
name="inbox-issue",
|
||||
),
|
||||
path(
|
||||
"public/workspaces/<str:slug>/project-boards/<uuid:project_id>/issues/<uuid:issue_id>/votes/",
|
||||
IssueVotePublicViewSet.as_view(
|
||||
{
|
||||
"get": "list",
|
||||
"post": "create",
|
||||
"delete": "destroy",
|
||||
}
|
||||
),
|
||||
name="issue-vote-project-board",
|
||||
),
|
||||
path(
|
||||
"public/workspaces/<str:slug>/project-boards/",
|
||||
WorkspaceProjectDeployBoardEndpoint.as_view(),
|
||||
name="workspace-project-boards",
|
||||
),
|
||||
]
|
5
apiserver/plane/app/apps.py
Normal file
5
apiserver/plane/app/apps.py
Normal file
@ -0,0 +1,5 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class AppConfig(AppConfig):
|
||||
name = "plane.app"
|
@ -2,7 +2,7 @@
|
||||
from .base import BaseSerializer
|
||||
|
||||
from plane.db.models import Estimate, EstimatePoint
|
||||
from plane.api.serializers import WorkspaceLiteSerializer, ProjectLiteSerializer
|
||||
from plane.app.serializers import WorkspaceLiteSerializer, ProjectLiteSerializer
|
||||
|
||||
|
||||
class EstimateSerializer(BaseSerializer):
|
@ -1,5 +1,5 @@
|
||||
# Module imports
|
||||
from plane.api.serializers import BaseSerializer
|
||||
from plane.app.serializers import BaseSerializer
|
||||
from plane.db.models import Integration, WorkspaceIntegration
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
# Module imports
|
||||
from plane.api.serializers import BaseSerializer
|
||||
from plane.app.serializers import BaseSerializer
|
||||
from plane.db.models import (
|
||||
GithubIssueSync,
|
||||
GithubRepository,
|
@ -1,5 +1,5 @@
|
||||
# Module imports
|
||||
from plane.api.serializers import BaseSerializer
|
||||
from plane.app.serializers import BaseSerializer
|
||||
from plane.db.models import SlackProjectSync
|
||||
|
||||
|
@ -3,8 +3,8 @@ from rest_framework import serializers
|
||||
|
||||
# Module imports
|
||||
from .base import BaseSerializer, DynamicBaseSerializer
|
||||
from plane.api.serializers.workspace import WorkSpaceSerializer, WorkspaceLiteSerializer
|
||||
from plane.api.serializers.user import UserLiteSerializer, UserAdminLiteSerializer
|
||||
from plane.app.serializers.workspace import WorkspaceLiteSerializer
|
||||
from plane.app.serializers.user import UserLiteSerializer, UserAdminLiteSerializer
|
||||
from plane.db.models import (
|
||||
Project,
|
||||
ProjectMember,
|
@ -1,7 +1,6 @@
|
||||
# Module imports
|
||||
from .base import BaseSerializer
|
||||
from .workspace import WorkspaceLiteSerializer
|
||||
from .project import ProjectLiteSerializer
|
||||
|
||||
|
||||
from plane.db.models import State
|
||||
|
@ -13,7 +13,6 @@ from .module import urlpatterns as module_urls
|
||||
from .notification import urlpatterns as notification_urls
|
||||
from .page import urlpatterns as page_urls
|
||||
from .project import urlpatterns as project_urls
|
||||
from .public_board import urlpatterns as public_board_urls
|
||||
from .search import urlpatterns as search_urls
|
||||
from .state import urlpatterns as state_urls
|
||||
from .user import urlpatterns as user_urls
|
||||
@ -43,7 +42,6 @@ urlpatterns = [
|
||||
*notification_urls,
|
||||
*page_urls,
|
||||
*project_urls,
|
||||
*public_board_urls,
|
||||
*search_urls,
|
||||
*state_urls,
|
||||
*user_urls,
|
@ -1,7 +1,7 @@
|
||||
from django.urls import path
|
||||
|
||||
|
||||
from plane.api.views import (
|
||||
from plane.app.views import (
|
||||
AnalyticsEndpoint,
|
||||
AnalyticViewViewset,
|
||||
SavedAnalyticEndpoint,
|
@ -1,5 +1,5 @@
|
||||
from django.urls import path
|
||||
from plane.api.views import ApiTokenEndpoint
|
||||
from plane.app.views import ApiTokenEndpoint
|
||||
|
||||
urlpatterns = [
|
||||
# API Tokens
|
@ -1,7 +1,7 @@
|
||||
from django.urls import path
|
||||
|
||||
|
||||
from plane.api.views import (
|
||||
from plane.app.views import (
|
||||
FileAssetEndpoint,
|
||||
UserAssetsEndpoint,
|
||||
)
|
@ -3,7 +3,7 @@ from django.urls import path
|
||||
from rest_framework_simplejwt.views import TokenRefreshView
|
||||
|
||||
|
||||
from plane.api.views import (
|
||||
from plane.app.views import (
|
||||
# Authentication
|
||||
SignUpEndpoint,
|
||||
SignInEndpoint,
|
@ -1,7 +1,7 @@
|
||||
from django.urls import path
|
||||
|
||||
|
||||
from plane.api.views import ConfigurationEndpoint
|
||||
from plane.app.views import ConfigurationEndpoint
|
||||
|
||||
urlpatterns = [
|
||||
path(
|
@ -1,7 +1,7 @@
|
||||
from django.urls import path
|
||||
|
||||
|
||||
from plane.api.views import (
|
||||
from plane.app.views import (
|
||||
CycleViewSet,
|
||||
CycleIssueViewSet,
|
||||
CycleDateCheckEndpoint,
|
@ -1,7 +1,7 @@
|
||||
from django.urls import path
|
||||
|
||||
|
||||
from plane.api.views import (
|
||||
from plane.app.views import (
|
||||
ProjectEstimatePointEndpoint,
|
||||
BulkEstimatePointEndpoint,
|
||||
)
|
@ -1,9 +1,9 @@
|
||||
from django.urls import path
|
||||
|
||||
|
||||
from plane.api.views import UnsplashEndpoint
|
||||
from plane.api.views import ReleaseNotesEndpoint
|
||||
from plane.api.views import GPTIntegrationEndpoint
|
||||
from plane.app.views import UnsplashEndpoint
|
||||
from plane.app.views import ReleaseNotesEndpoint
|
||||
from plane.app.views import GPTIntegrationEndpoint
|
||||
|
||||
|
||||
urlpatterns = [
|
@ -1,7 +1,7 @@
|
||||
from django.urls import path
|
||||
|
||||
|
||||
from plane.api.views import (
|
||||
from plane.app.views import (
|
||||
ServiceIssueImportSummaryEndpoint,
|
||||
ImportServiceEndpoint,
|
||||
UpdateServiceImportStatusEndpoint,
|
@ -1,7 +1,7 @@
|
||||
from django.urls import path
|
||||
|
||||
|
||||
from plane.api.views import (
|
||||
from plane.app.views import (
|
||||
InboxViewSet,
|
||||
InboxIssueViewSet,
|
||||
)
|
@ -1,7 +1,7 @@
|
||||
from django.urls import path
|
||||
|
||||
|
||||
from plane.api.views import (
|
||||
from plane.app.views import (
|
||||
IntegrationViewSet,
|
||||
WorkspaceIntegrationViewSet,
|
||||
GithubRepositoriesEndpoint,
|
@ -1,7 +1,7 @@
|
||||
from django.urls import path
|
||||
|
||||
|
||||
from plane.api.views import (
|
||||
from plane.app.views import (
|
||||
IssueViewSet,
|
||||
IssueListEndpoint,
|
||||
IssueListGroupedEndpoint,
|
@ -1,7 +1,7 @@
|
||||
from django.urls import path
|
||||
|
||||
|
||||
from plane.api.views import (
|
||||
from plane.app.views import (
|
||||
ModuleViewSet,
|
||||
ModuleIssueViewSet,
|
||||
ModuleLinkViewSet,
|
@ -1,7 +1,7 @@
|
||||
from django.urls import path
|
||||
|
||||
|
||||
from plane.api.views import (
|
||||
from plane.app.views import (
|
||||
NotificationViewSet,
|
||||
UnreadNotificationEndpoint,
|
||||
MarkAllReadNotificationViewSet,
|
@ -1,7 +1,7 @@
|
||||
from django.urls import path
|
||||
|
||||
|
||||
from plane.api.views import (
|
||||
from plane.app.views import (
|
||||
PageViewSet,
|
||||
PageFavoriteViewSet,
|
||||
PageLogEndpoint,
|
@ -1,6 +1,6 @@
|
||||
from django.urls import path
|
||||
|
||||
from plane.api.views import (
|
||||
from plane.app.views import (
|
||||
ProjectViewSet,
|
||||
ProjectInvitationsViewset,
|
||||
ProjectMemberViewSet,
|
||||
@ -10,8 +10,9 @@ from plane.api.views import (
|
||||
ProjectUserViewsEndpoint,
|
||||
ProjectIdentifierEndpoint,
|
||||
ProjectFavoritesViewSet,
|
||||
ProjectPublicCoverImagesEndpoint,
|
||||
UserProjectInvitationsViewset,
|
||||
ProjectPublicCoverImagesEndpoint,
|
||||
ProjectDeployBoardViewSet,
|
||||
)
|
||||
|
||||
|
||||
@ -147,4 +148,25 @@ urlpatterns = [
|
||||
ProjectPublicCoverImagesEndpoint.as_view(),
|
||||
name="project-covers",
|
||||
),
|
||||
path(
|
||||
"workspaces/<str:slug>/projects/<uuid:project_id>/project-deploy-boards/",
|
||||
ProjectDeployBoardViewSet.as_view(
|
||||
{
|
||||
"get": "list",
|
||||
"post": "create",
|
||||
}
|
||||
),
|
||||
name="project-deploy-board",
|
||||
),
|
||||
path(
|
||||
"workspaces/<str:slug>/projects/<uuid:project_id>/project-deploy-boards/<uuid:pk>/",
|
||||
ProjectDeployBoardViewSet.as_view(
|
||||
{
|
||||
"get": "retrieve",
|
||||
"patch": "partial_update",
|
||||
"delete": "destroy",
|
||||
}
|
||||
),
|
||||
name="project-deploy-board",
|
||||
),
|
||||
]
|
@ -1,7 +1,7 @@
|
||||
from django.urls import path
|
||||
|
||||
|
||||
from plane.api.views import (
|
||||
from plane.app.views import (
|
||||
GlobalSearchEndpoint,
|
||||
IssueSearchEndpoint,
|
||||
)
|
@ -1,7 +1,7 @@
|
||||
from django.urls import path
|
||||
|
||||
|
||||
from plane.api.views import StateViewSet
|
||||
from plane.app.views import StateViewSet
|
||||
|
||||
|
||||
urlpatterns = [
|
@ -1,6 +1,6 @@
|
||||
from django.urls import path
|
||||
|
||||
from plane.api.views import (
|
||||
from plane.app.views import (
|
||||
## User
|
||||
UserEndpoint,
|
||||
UpdateUserOnBoardedEndpoint,
|
@ -1,7 +1,7 @@
|
||||
from django.urls import path
|
||||
|
||||
|
||||
from plane.api.views import (
|
||||
from plane.app.views import (
|
||||
IssueViewViewSet,
|
||||
GlobalViewViewSet,
|
||||
GlobalViewIssuesViewSet,
|
@ -1,6 +1,6 @@
|
||||
from django.urls import path
|
||||
|
||||
from plane.api.views import (
|
||||
from plane.app.views import (
|
||||
WebhookEndpoint,
|
||||
WebhookLogsEndpoint,
|
||||
WebhookSecretRegenerateEndpoint,
|
@ -1,7 +1,7 @@
|
||||
from django.urls import path
|
||||
|
||||
|
||||
from plane.api.views import (
|
||||
from plane.app.views import (
|
||||
UserWorkspaceInvitationsViewSet,
|
||||
WorkSpaceViewSet,
|
||||
WorkspaceJoinEndpoint,
|
@ -4,7 +4,7 @@ from rest_framework_simplejwt.views import TokenRefreshView
|
||||
|
||||
# Create your urls here.
|
||||
|
||||
from plane.api.views import (
|
||||
from plane.app.views import (
|
||||
# Authentication
|
||||
SignUpEndpoint,
|
||||
SignInEndpoint,
|
@ -9,10 +9,8 @@ from .project import (
|
||||
ProjectUserViewsEndpoint,
|
||||
ProjectMemberUserEndpoint,
|
||||
ProjectFavoritesViewSet,
|
||||
ProjectDeployBoardViewSet,
|
||||
ProjectDeployBoardPublicSettingsEndpoint,
|
||||
WorkspaceProjectDeployBoardEndpoint,
|
||||
ProjectPublicCoverImagesEndpoint,
|
||||
ProjectDeployBoardViewSet,
|
||||
)
|
||||
from .user import (
|
||||
UserEndpoint,
|
||||
@ -80,15 +78,9 @@ from .issue import (
|
||||
IssueAttachmentEndpoint,
|
||||
IssueArchiveViewSet,
|
||||
IssueSubscriberViewSet,
|
||||
IssueCommentPublicViewSet,
|
||||
CommentReactionViewSet,
|
||||
IssueReactionViewSet,
|
||||
IssueReactionPublicViewSet,
|
||||
CommentReactionPublicViewSet,
|
||||
IssueVotePublicViewSet,
|
||||
IssueRelationViewSet,
|
||||
IssueRetrievePublicEndpoint,
|
||||
ProjectIssuesPublicEndpoint,
|
||||
IssueDraftViewSet,
|
||||
)
|
||||
|
||||
@ -156,7 +148,7 @@ from .estimate import (
|
||||
BulkEstimatePointEndpoint,
|
||||
)
|
||||
|
||||
from .inbox import InboxViewSet, InboxIssueViewSet, InboxIssuePublicViewSet
|
||||
from .inbox import InboxViewSet, InboxIssueViewSet
|
||||
|
||||
from .analytic import (
|
||||
AnalyticsEndpoint,
|
@ -5,13 +5,12 @@ from django.db.models.functions import ExtractMonth
|
||||
# Third party imports
|
||||
from rest_framework import status
|
||||
from rest_framework.response import Response
|
||||
from sentry_sdk import capture_exception
|
||||
|
||||
# Module imports
|
||||
from plane.api.views import BaseAPIView, BaseViewSet
|
||||
from plane.api.permissions import WorkSpaceAdminPermission
|
||||
from plane.app.views import BaseAPIView, BaseViewSet
|
||||
from plane.app.permissions import WorkSpaceAdminPermission
|
||||
from plane.db.models import Issue, AnalyticView, Workspace, State, Label
|
||||
from plane.api.serializers import AnalyticViewSerializer
|
||||
from plane.app.serializers import AnalyticViewSerializer
|
||||
from plane.utils.analytics_plot import build_graph_plot
|
||||
from plane.bgtasks.analytic_plot_export import analytic_export_task
|
||||
from plane.utils.issue_filters import issue_filters
|
@ -8,8 +8,8 @@ from rest_framework import status
|
||||
# Module import
|
||||
from .base import BaseAPIView
|
||||
from plane.db.models import APIToken, Workspace
|
||||
from plane.api.serializers import APITokenSerializer, APITokenReadSerializer
|
||||
from plane.api.permissions import WorkspaceOwnerPermission
|
||||
from plane.app.serializers import APITokenSerializer, APITokenReadSerializer
|
||||
from plane.app.permissions import WorkspaceOwnerPermission
|
||||
|
||||
|
||||
class ApiTokenEndpoint(BaseAPIView):
|
@ -2,12 +2,11 @@
|
||||
from rest_framework import status
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.parsers import MultiPartParser, FormParser
|
||||
from sentry_sdk import capture_exception
|
||||
from django.conf import settings
|
||||
|
||||
# Module imports
|
||||
from .base import BaseAPIView
|
||||
from plane.db.models import FileAsset, Workspace
|
||||
from plane.api.serializers import FileAssetSerializer
|
||||
from plane.app.serializers import FileAssetSerializer
|
||||
|
||||
|
||||
class FileAssetEndpoint(BaseAPIView):
|
@ -21,7 +21,7 @@ from sentry_sdk import capture_exception
|
||||
|
||||
## Module imports
|
||||
from . import BaseAPIView
|
||||
from plane.api.serializers import (
|
||||
from plane.app.serializers import (
|
||||
ChangePasswordSerializer,
|
||||
ResetPasswordSerializer,
|
||||
)
|
@ -5,6 +5,7 @@ import string
|
||||
import json
|
||||
import requests
|
||||
from requests.exceptions import RequestException
|
||||
|
||||
# Django imports
|
||||
from django.utils import timezone
|
||||
from django.core.exceptions import ValidationError
|
@ -8,7 +8,6 @@ from django.conf import settings
|
||||
from rest_framework.permissions import AllowAny
|
||||
from rest_framework import status
|
||||
from rest_framework.response import Response
|
||||
from sentry_sdk import capture_exception
|
||||
|
||||
# Module imports
|
||||
from .base import BaseAPIView
|
@ -20,18 +20,17 @@ from django.views.decorators.gzip import gzip_page
|
||||
# Third party imports
|
||||
from rest_framework.response import Response
|
||||
from rest_framework import status
|
||||
from sentry_sdk import capture_exception
|
||||
|
||||
# Module imports
|
||||
from . import BaseViewSet, BaseAPIView, WebhookMixin
|
||||
from plane.api.serializers import (
|
||||
from plane.app.serializers import (
|
||||
CycleSerializer,
|
||||
CycleIssueSerializer,
|
||||
CycleFavoriteSerializer,
|
||||
IssueStateSerializer,
|
||||
CycleWriteSerializer,
|
||||
)
|
||||
from plane.api.permissions import ProjectEntityPermission
|
||||
from plane.app.permissions import ProjectEntityPermission
|
||||
from plane.db.models import (
|
||||
User,
|
||||
Cycle,
|
@ -1,13 +1,12 @@
|
||||
# Third party imports
|
||||
from rest_framework.response import Response
|
||||
from rest_framework import status
|
||||
from sentry_sdk import capture_exception
|
||||
|
||||
# Module imports
|
||||
from .base import BaseViewSet, BaseAPIView
|
||||
from plane.api.permissions import ProjectEntityPermission
|
||||
from plane.app.permissions import ProjectEntityPermission
|
||||
from plane.db.models import Project, Estimate, EstimatePoint
|
||||
from plane.api.serializers import (
|
||||
from plane.app.serializers import (
|
||||
EstimateSerializer,
|
||||
EstimatePointSerializer,
|
||||
EstimateReadSerializer,
|
@ -1,15 +1,14 @@
|
||||
# Third Party imports
|
||||
from rest_framework.response import Response
|
||||
from rest_framework import status
|
||||
from sentry_sdk import capture_exception
|
||||
|
||||
# Module imports
|
||||
from . import BaseAPIView
|
||||
from plane.api.permissions import WorkSpaceAdminPermission
|
||||
from plane.app.permissions import WorkSpaceAdminPermission
|
||||
from plane.bgtasks.export_task import issue_export_task
|
||||
from plane.db.models import Project, ExporterHistory, Workspace
|
||||
|
||||
from plane.api.serializers import ExporterHistorySerializer
|
||||
from plane.app.serializers import ExporterHistorySerializer
|
||||
|
||||
|
||||
class ExportIssuesEndpoint(BaseAPIView):
|
@ -5,17 +5,15 @@ import requests
|
||||
from openai import OpenAI
|
||||
from rest_framework.response import Response
|
||||
from rest_framework import status
|
||||
from rest_framework.permissions import AllowAny
|
||||
from sentry_sdk import capture_exception
|
||||
|
||||
# Django imports
|
||||
from django.conf import settings
|
||||
|
||||
# Module imports
|
||||
from .base import BaseAPIView
|
||||
from plane.api.permissions import ProjectEntityPermission
|
||||
from plane.app.permissions import ProjectEntityPermission
|
||||
from plane.db.models import Workspace, Project
|
||||
from plane.api.serializers import ProjectLiteSerializer, WorkspaceLiteSerializer
|
||||
from plane.app.serializers import ProjectLiteSerializer, WorkspaceLiteSerializer
|
||||
from plane.utils.integrations.github import get_release_notes
|
||||
from plane.license.models import InstanceConfiguration
|
||||
from plane.license.utils.instance_value import get_configuration_value
|
@ -4,13 +4,12 @@ import uuid
|
||||
# Third party imports
|
||||
from rest_framework import status
|
||||
from rest_framework.response import Response
|
||||
from sentry_sdk import capture_exception
|
||||
|
||||
# Django imports
|
||||
from django.db.models import Max, Q
|
||||
|
||||
# Module imports
|
||||
from plane.api.views import BaseAPIView
|
||||
from plane.app.views import BaseAPIView
|
||||
from plane.db.models import (
|
||||
WorkspaceIntegration,
|
||||
Importer,
|
||||
@ -30,7 +29,7 @@ from plane.db.models import (
|
||||
ModuleIssue,
|
||||
Label,
|
||||
)
|
||||
from plane.api.serializers import (
|
||||
from plane.app.serializers import (
|
||||
ImporterSerializer,
|
||||
IssueFlatSerializer,
|
||||
ModuleSerializer,
|
||||
@ -39,7 +38,7 @@ from plane.utils.integrations.github import get_github_repo_details
|
||||
from plane.utils.importers.jira import jira_project_issue_summary
|
||||
from plane.bgtasks.importer_task import service_importer
|
||||
from plane.utils.html_processor import strip_tags
|
||||
from plane.api.permissions import WorkSpaceAdminPermission
|
||||
from plane.app.permissions import WorkSpaceAdminPermission
|
||||
|
||||
|
||||
class ServiceIssueImportSummaryEndpoint(BaseAPIView):
|
@ -9,11 +9,10 @@ from django.core.serializers.json import DjangoJSONEncoder
|
||||
# Third party imports
|
||||
from rest_framework import status
|
||||
from rest_framework.response import Response
|
||||
from sentry_sdk import capture_exception
|
||||
|
||||
# Module imports
|
||||
from .base import BaseViewSet
|
||||
from plane.api.permissions import ProjectBasePermission, ProjectLitePermission
|
||||
from plane.app.permissions import ProjectBasePermission, ProjectLitePermission
|
||||
from plane.db.models import (
|
||||
Inbox,
|
||||
InboxIssue,
|
||||
@ -22,9 +21,8 @@ from plane.db.models import (
|
||||
IssueLink,
|
||||
IssueAttachment,
|
||||
ProjectMember,
|
||||
ProjectDeployBoard,
|
||||
)
|
||||
from plane.api.serializers import (
|
||||
from plane.app.serializers import (
|
||||
IssueSerializer,
|
||||
InboxSerializer,
|
||||
InboxIssueSerializer,
|
||||
@ -359,253 +357,3 @@ class InboxIssueViewSet(BaseViewSet):
|
||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
|
||||
class InboxIssuePublicViewSet(BaseViewSet):
|
||||
serializer_class = InboxIssueSerializer
|
||||
model = InboxIssue
|
||||
|
||||
filterset_fields = [
|
||||
"status",
|
||||
]
|
||||
|
||||
def get_queryset(self):
|
||||
project_deploy_board = ProjectDeployBoard.objects.get(
|
||||
workspace__slug=self.kwargs.get("slug"),
|
||||
project_id=self.kwargs.get("project_id"),
|
||||
)
|
||||
if project_deploy_board is not None:
|
||||
return self.filter_queryset(
|
||||
super()
|
||||
.get_queryset()
|
||||
.filter(
|
||||
Q(snoozed_till__gte=timezone.now()) | Q(snoozed_till__isnull=True),
|
||||
project_id=self.kwargs.get("project_id"),
|
||||
workspace__slug=self.kwargs.get("slug"),
|
||||
inbox_id=self.kwargs.get("inbox_id"),
|
||||
)
|
||||
.select_related("issue", "workspace", "project")
|
||||
)
|
||||
return InboxIssue.objects.none()
|
||||
|
||||
def list(self, request, slug, project_id, inbox_id):
|
||||
project_deploy_board = ProjectDeployBoard.objects.get(
|
||||
workspace__slug=slug, project_id=project_id
|
||||
)
|
||||
if project_deploy_board.inbox is None:
|
||||
return Response(
|
||||
{"error": "Inbox is not enabled for this Project Board"},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
||||
filters = issue_filters(request.query_params, "GET")
|
||||
issues = (
|
||||
Issue.objects.filter(
|
||||
issue_inbox__inbox_id=inbox_id,
|
||||
workspace__slug=slug,
|
||||
project_id=project_id,
|
||||
)
|
||||
.filter(**filters)
|
||||
.annotate(bridge_id=F("issue_inbox__id"))
|
||||
.select_related("workspace", "project", "state", "parent")
|
||||
.prefetch_related("assignees", "labels")
|
||||
.order_by("issue_inbox__snoozed_till", "issue_inbox__status")
|
||||
.annotate(
|
||||
sub_issues_count=Issue.issue_objects.filter(parent=OuterRef("id"))
|
||||
.order_by()
|
||||
.annotate(count=Func(F("id"), function="Count"))
|
||||
.values("count")
|
||||
)
|
||||
.annotate(
|
||||
link_count=IssueLink.objects.filter(issue=OuterRef("id"))
|
||||
.order_by()
|
||||
.annotate(count=Func(F("id"), function="Count"))
|
||||
.values("count")
|
||||
)
|
||||
.annotate(
|
||||
attachment_count=IssueAttachment.objects.filter(issue=OuterRef("id"))
|
||||
.order_by()
|
||||
.annotate(count=Func(F("id"), function="Count"))
|
||||
.values("count")
|
||||
)
|
||||
.prefetch_related(
|
||||
Prefetch(
|
||||
"issue_inbox",
|
||||
queryset=InboxIssue.objects.only(
|
||||
"status", "duplicate_to", "snoozed_till", "source"
|
||||
),
|
||||
)
|
||||
)
|
||||
)
|
||||
issues_data = IssueStateInboxSerializer(issues, many=True).data
|
||||
return Response(
|
||||
issues_data,
|
||||
status=status.HTTP_200_OK,
|
||||
)
|
||||
|
||||
def create(self, request, slug, project_id, inbox_id):
|
||||
project_deploy_board = ProjectDeployBoard.objects.get(
|
||||
workspace__slug=slug, project_id=project_id
|
||||
)
|
||||
if project_deploy_board.inbox is None:
|
||||
return Response(
|
||||
{"error": "Inbox is not enabled for this Project Board"},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
||||
if not request.data.get("issue", {}).get("name", False):
|
||||
return Response(
|
||||
{"error": "Name is required"}, status=status.HTTP_400_BAD_REQUEST
|
||||
)
|
||||
|
||||
# Check for valid priority
|
||||
if not request.data.get("issue", {}).get("priority", "none") in [
|
||||
"low",
|
||||
"medium",
|
||||
"high",
|
||||
"urgent",
|
||||
"none",
|
||||
]:
|
||||
return Response(
|
||||
{"error": "Invalid priority"}, status=status.HTTP_400_BAD_REQUEST
|
||||
)
|
||||
|
||||
# Create or get state
|
||||
state, _ = State.objects.get_or_create(
|
||||
name="Triage",
|
||||
group="backlog",
|
||||
description="Default state for managing all Inbox Issues",
|
||||
project_id=project_id,
|
||||
color="#ff7700",
|
||||
)
|
||||
|
||||
# create an issue
|
||||
issue = Issue.objects.create(
|
||||
name=request.data.get("issue", {}).get("name"),
|
||||
description=request.data.get("issue", {}).get("description", {}),
|
||||
description_html=request.data.get("issue", {}).get(
|
||||
"description_html", "<p></p>"
|
||||
),
|
||||
priority=request.data.get("issue", {}).get("priority", "low"),
|
||||
project_id=project_id,
|
||||
state=state,
|
||||
)
|
||||
|
||||
# Create an Issue Activity
|
||||
issue_activity.delay(
|
||||
type="issue.activity.created",
|
||||
requested_data=json.dumps(request.data, cls=DjangoJSONEncoder),
|
||||
actor_id=str(request.user.id),
|
||||
issue_id=str(issue.id),
|
||||
project_id=str(project_id),
|
||||
current_instance=None,
|
||||
epoch=int(timezone.now().timestamp()),
|
||||
)
|
||||
# create an inbox issue
|
||||
InboxIssue.objects.create(
|
||||
inbox_id=inbox_id,
|
||||
project_id=project_id,
|
||||
issue=issue,
|
||||
source=request.data.get("source", "in-app"),
|
||||
)
|
||||
|
||||
serializer = IssueStateInboxSerializer(issue)
|
||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||
|
||||
def partial_update(self, request, slug, project_id, inbox_id, pk):
|
||||
project_deploy_board = ProjectDeployBoard.objects.get(
|
||||
workspace__slug=slug, project_id=project_id
|
||||
)
|
||||
if project_deploy_board.inbox is None:
|
||||
return Response(
|
||||
{"error": "Inbox is not enabled for this Project Board"},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
||||
inbox_issue = InboxIssue.objects.get(
|
||||
pk=pk, workspace__slug=slug, project_id=project_id, inbox_id=inbox_id
|
||||
)
|
||||
# Get the project member
|
||||
if str(inbox_issue.created_by_id) != str(request.user.id):
|
||||
return Response(
|
||||
{"error": "You cannot edit inbox issues"},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
||||
# Get issue data
|
||||
issue_data = request.data.pop("issue", False)
|
||||
|
||||
issue = Issue.objects.get(
|
||||
pk=inbox_issue.issue_id, workspace__slug=slug, project_id=project_id
|
||||
)
|
||||
# viewers and guests since only viewers and guests
|
||||
issue_data = {
|
||||
"name": issue_data.get("name", issue.name),
|
||||
"description_html": issue_data.get(
|
||||
"description_html", issue.description_html
|
||||
),
|
||||
"description": issue_data.get("description", issue.description),
|
||||
}
|
||||
|
||||
issue_serializer = IssueCreateSerializer(issue, data=issue_data, partial=True)
|
||||
|
||||
if issue_serializer.is_valid():
|
||||
current_instance = issue
|
||||
# Log all the updates
|
||||
requested_data = json.dumps(issue_data, cls=DjangoJSONEncoder)
|
||||
if issue is not None:
|
||||
issue_activity.delay(
|
||||
type="issue.activity.updated",
|
||||
requested_data=requested_data,
|
||||
actor_id=str(request.user.id),
|
||||
issue_id=str(issue.id),
|
||||
project_id=str(project_id),
|
||||
current_instance=json.dumps(
|
||||
IssueSerializer(current_instance).data,
|
||||
cls=DjangoJSONEncoder,
|
||||
),
|
||||
epoch=int(timezone.now().timestamp()),
|
||||
)
|
||||
issue_serializer.save()
|
||||
return Response(issue_serializer.data, status=status.HTTP_200_OK)
|
||||
return Response(issue_serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
def retrieve(self, request, slug, project_id, inbox_id, pk):
|
||||
project_deploy_board = ProjectDeployBoard.objects.get(
|
||||
workspace__slug=slug, project_id=project_id
|
||||
)
|
||||
if project_deploy_board.inbox is None:
|
||||
return Response(
|
||||
{"error": "Inbox is not enabled for this Project Board"},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
||||
inbox_issue = InboxIssue.objects.get(
|
||||
pk=pk, workspace__slug=slug, project_id=project_id, inbox_id=inbox_id
|
||||
)
|
||||
issue = Issue.objects.get(
|
||||
pk=inbox_issue.issue_id, workspace__slug=slug, project_id=project_id
|
||||
)
|
||||
serializer = IssueStateInboxSerializer(issue)
|
||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||
|
||||
def destroy(self, request, slug, project_id, inbox_id, pk):
|
||||
project_deploy_board = ProjectDeployBoard.objects.get(
|
||||
workspace__slug=slug, project_id=project_id
|
||||
)
|
||||
if project_deploy_board.inbox is None:
|
||||
return Response(
|
||||
{"error": "Inbox is not enabled for this Project Board"},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
||||
inbox_issue = InboxIssue.objects.get(
|
||||
pk=pk, workspace__slug=slug, project_id=project_id, inbox_id=inbox_id
|
||||
)
|
||||
|
||||
if str(inbox_issue.created_by_id) != str(request.user.id):
|
||||
return Response(
|
||||
{"error": "You cannot delete inbox issue"},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
||||
inbox_issue.delete()
|
||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
@ -10,7 +10,7 @@ from rest_framework import status
|
||||
from sentry_sdk import capture_exception
|
||||
|
||||
# Module imports
|
||||
from plane.api.views import BaseViewSet
|
||||
from plane.app.views import BaseViewSet
|
||||
from plane.db.models import (
|
||||
Integration,
|
||||
WorkspaceIntegration,
|
||||
@ -19,12 +19,12 @@ from plane.db.models import (
|
||||
WorkspaceMember,
|
||||
APIToken,
|
||||
)
|
||||
from plane.api.serializers import IntegrationSerializer, WorkspaceIntegrationSerializer
|
||||
from plane.app.serializers import IntegrationSerializer, WorkspaceIntegrationSerializer
|
||||
from plane.utils.integrations.github import (
|
||||
get_github_metadata,
|
||||
delete_github_installation,
|
||||
)
|
||||
from plane.api.permissions import WorkSpaceAdminPermission
|
||||
from plane.app.permissions import WorkSpaceAdminPermission
|
||||
from plane.utils.integrations.slack import slack_oauth
|
||||
|
||||
class IntegrationViewSet(BaseViewSet):
|
@ -4,7 +4,7 @@ from rest_framework.response import Response
|
||||
from sentry_sdk import capture_exception
|
||||
|
||||
# Module imports
|
||||
from plane.api.views import BaseViewSet, BaseAPIView
|
||||
from plane.app.views import BaseViewSet, BaseAPIView
|
||||
from plane.db.models import (
|
||||
GithubIssueSync,
|
||||
GithubRepositorySync,
|
||||
@ -15,13 +15,13 @@ from plane.db.models import (
|
||||
GithubCommentSync,
|
||||
Project,
|
||||
)
|
||||
from plane.api.serializers import (
|
||||
from plane.app.serializers import (
|
||||
GithubIssueSyncSerializer,
|
||||
GithubRepositorySyncSerializer,
|
||||
GithubCommentSyncSerializer,
|
||||
)
|
||||
from plane.utils.integrations.github import get_github_repos
|
||||
from plane.api.permissions import ProjectBasePermission, ProjectEntityPermission
|
||||
from plane.app.permissions import ProjectBasePermission, ProjectEntityPermission
|
||||
|
||||
|
||||
class GithubRepositoriesEndpoint(BaseAPIView):
|
@ -7,10 +7,10 @@ from rest_framework.response import Response
|
||||
from sentry_sdk import capture_exception
|
||||
|
||||
# Module imports
|
||||
from plane.api.views import BaseViewSet, BaseAPIView
|
||||
from plane.app.views import BaseViewSet, BaseAPIView
|
||||
from plane.db.models import SlackProjectSync, WorkspaceIntegration, ProjectMember
|
||||
from plane.api.serializers import SlackProjectSyncSerializer
|
||||
from plane.api.permissions import ProjectBasePermission, ProjectEntityPermission
|
||||
from plane.app.serializers import SlackProjectSyncSerializer
|
||||
from plane.app.permissions import ProjectBasePermission, ProjectEntityPermission
|
||||
from plane.utils.integrations.slack import slack_oauth
|
||||
|
||||
|
@ -29,12 +29,10 @@ from django.db import IntegrityError
|
||||
from rest_framework.response import Response
|
||||
from rest_framework import status
|
||||
from rest_framework.parsers import MultiPartParser, FormParser
|
||||
from rest_framework.permissions import AllowAny, IsAuthenticated
|
||||
from sentry_sdk import capture_exception
|
||||
|
||||
# Module imports
|
||||
from . import BaseViewSet, BaseAPIView, WebhookMixin
|
||||
from plane.api.serializers import (
|
||||
from plane.app.serializers import (
|
||||
IssueCreateSerializer,
|
||||
IssueActivitySerializer,
|
||||
IssueCommentSerializer,
|
||||
@ -54,7 +52,7 @@ from plane.api.serializers import (
|
||||
RelatedIssueSerializer,
|
||||
IssuePublicSerializer,
|
||||
)
|
||||
from plane.api.permissions import (
|
||||
from plane.app.permissions import (
|
||||
ProjectEntityPermission,
|
||||
WorkSpaceAdminPermission,
|
||||
ProjectMemberPermission,
|
||||
@ -1462,432 +1460,6 @@ class CommentReactionViewSet(BaseViewSet):
|
||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
|
||||
class IssueCommentPublicViewSet(BaseViewSet):
|
||||
serializer_class = IssueCommentSerializer
|
||||
model = IssueComment
|
||||
|
||||
filterset_fields = [
|
||||
"issue__id",
|
||||
"workspace__id",
|
||||
]
|
||||
|
||||
def get_permissions(self):
|
||||
if self.action in ["list", "retrieve"]:
|
||||
self.permission_classes = [
|
||||
AllowAny,
|
||||
]
|
||||
else:
|
||||
self.permission_classes = [
|
||||
IsAuthenticated,
|
||||
]
|
||||
|
||||
return super(IssueCommentPublicViewSet, self).get_permissions()
|
||||
|
||||
def get_queryset(self):
|
||||
try:
|
||||
project_deploy_board = ProjectDeployBoard.objects.get(
|
||||
workspace__slug=self.kwargs.get("slug"),
|
||||
project_id=self.kwargs.get("project_id"),
|
||||
)
|
||||
if project_deploy_board.comments:
|
||||
return self.filter_queryset(
|
||||
super()
|
||||
.get_queryset()
|
||||
.filter(workspace__slug=self.kwargs.get("slug"))
|
||||
.filter(issue_id=self.kwargs.get("issue_id"))
|
||||
.filter(access="EXTERNAL")
|
||||
.select_related("project")
|
||||
.select_related("workspace")
|
||||
.select_related("issue")
|
||||
.annotate(
|
||||
is_member=Exists(
|
||||
ProjectMember.objects.filter(
|
||||
workspace__slug=self.kwargs.get("slug"),
|
||||
project_id=self.kwargs.get("project_id"),
|
||||
member_id=self.request.user.id,
|
||||
is_active=True,
|
||||
)
|
||||
)
|
||||
)
|
||||
.distinct()
|
||||
).order_by("created_at")
|
||||
return IssueComment.objects.none()
|
||||
except ProjectDeployBoard.DoesNotExist:
|
||||
return IssueComment.objects.none()
|
||||
|
||||
def create(self, request, slug, project_id, issue_id):
|
||||
project_deploy_board = ProjectDeployBoard.objects.get(
|
||||
workspace__slug=slug, project_id=project_id
|
||||
)
|
||||
|
||||
if not project_deploy_board.comments:
|
||||
return Response(
|
||||
{"error": "Comments are not enabled for this project"},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
||||
serializer = IssueCommentSerializer(data=request.data)
|
||||
if serializer.is_valid():
|
||||
serializer.save(
|
||||
project_id=project_id,
|
||||
issue_id=issue_id,
|
||||
actor=request.user,
|
||||
access="EXTERNAL",
|
||||
)
|
||||
issue_activity.delay(
|
||||
type="comment.activity.created",
|
||||
requested_data=json.dumps(serializer.data, cls=DjangoJSONEncoder),
|
||||
actor_id=str(request.user.id),
|
||||
issue_id=str(issue_id),
|
||||
project_id=str(project_id),
|
||||
current_instance=None,
|
||||
epoch=int(timezone.now().timestamp()),
|
||||
)
|
||||
if not ProjectMember.objects.filter(
|
||||
project_id=project_id,
|
||||
member=request.user,
|
||||
is_active=True,
|
||||
).exists():
|
||||
# Add the user for workspace tracking
|
||||
_ = ProjectPublicMember.objects.get_or_create(
|
||||
project_id=project_id,
|
||||
member=request.user,
|
||||
)
|
||||
|
||||
return Response(serializer.data, status=status.HTTP_201_CREATED)
|
||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
def partial_update(self, request, slug, project_id, issue_id, pk):
|
||||
project_deploy_board = ProjectDeployBoard.objects.get(
|
||||
workspace__slug=slug, project_id=project_id
|
||||
)
|
||||
|
||||
if not project_deploy_board.comments:
|
||||
return Response(
|
||||
{"error": "Comments are not enabled for this project"},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
comment = IssueComment.objects.get(
|
||||
workspace__slug=slug, pk=pk, actor=request.user
|
||||
)
|
||||
serializer = IssueCommentSerializer(comment, data=request.data, partial=True)
|
||||
if serializer.is_valid():
|
||||
serializer.save()
|
||||
issue_activity.delay(
|
||||
type="comment.activity.updated",
|
||||
requested_data=json.dumps(request.data, cls=DjangoJSONEncoder),
|
||||
actor_id=str(request.user.id),
|
||||
issue_id=str(issue_id),
|
||||
project_id=str(project_id),
|
||||
current_instance=json.dumps(
|
||||
IssueCommentSerializer(comment).data,
|
||||
cls=DjangoJSONEncoder,
|
||||
),
|
||||
epoch=int(timezone.now().timestamp()),
|
||||
)
|
||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
def destroy(self, request, slug, project_id, issue_id, pk):
|
||||
project_deploy_board = ProjectDeployBoard.objects.get(
|
||||
workspace__slug=slug, project_id=project_id
|
||||
)
|
||||
|
||||
if not project_deploy_board.comments:
|
||||
return Response(
|
||||
{"error": "Comments are not enabled for this project"},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
comment = IssueComment.objects.get(
|
||||
workspace__slug=slug, pk=pk, project_id=project_id, actor=request.user
|
||||
)
|
||||
issue_activity.delay(
|
||||
type="comment.activity.deleted",
|
||||
requested_data=json.dumps({"comment_id": str(pk)}),
|
||||
actor_id=str(request.user.id),
|
||||
issue_id=str(issue_id),
|
||||
project_id=str(project_id),
|
||||
current_instance=json.dumps(
|
||||
IssueCommentSerializer(comment).data,
|
||||
cls=DjangoJSONEncoder,
|
||||
),
|
||||
epoch=int(timezone.now().timestamp()),
|
||||
)
|
||||
comment.delete()
|
||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
|
||||
class IssueReactionPublicViewSet(BaseViewSet):
|
||||
serializer_class = IssueReactionSerializer
|
||||
model = IssueReaction
|
||||
|
||||
def get_queryset(self):
|
||||
try:
|
||||
project_deploy_board = ProjectDeployBoard.objects.get(
|
||||
workspace__slug=self.kwargs.get("slug"),
|
||||
project_id=self.kwargs.get("project_id"),
|
||||
)
|
||||
if project_deploy_board.reactions:
|
||||
return (
|
||||
super()
|
||||
.get_queryset()
|
||||
.filter(workspace__slug=self.kwargs.get("slug"))
|
||||
.filter(project_id=self.kwargs.get("project_id"))
|
||||
.filter(issue_id=self.kwargs.get("issue_id"))
|
||||
.order_by("-created_at")
|
||||
.distinct()
|
||||
)
|
||||
return IssueReaction.objects.none()
|
||||
except ProjectDeployBoard.DoesNotExist:
|
||||
return IssueReaction.objects.none()
|
||||
|
||||
def create(self, request, slug, project_id, issue_id):
|
||||
project_deploy_board = ProjectDeployBoard.objects.get(
|
||||
workspace__slug=slug, project_id=project_id
|
||||
)
|
||||
|
||||
if not project_deploy_board.reactions:
|
||||
return Response(
|
||||
{"error": "Reactions are not enabled for this project board"},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
||||
serializer = IssueReactionSerializer(data=request.data)
|
||||
if serializer.is_valid():
|
||||
serializer.save(
|
||||
project_id=project_id, issue_id=issue_id, actor=request.user
|
||||
)
|
||||
if not ProjectMember.objects.filter(
|
||||
project_id=project_id,
|
||||
member=request.user,
|
||||
is_active=True,
|
||||
).exists():
|
||||
# Add the user for workspace tracking
|
||||
_ = ProjectPublicMember.objects.get_or_create(
|
||||
project_id=project_id,
|
||||
member=request.user,
|
||||
)
|
||||
issue_activity.delay(
|
||||
type="issue_reaction.activity.created",
|
||||
requested_data=json.dumps(self.request.data, cls=DjangoJSONEncoder),
|
||||
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()),
|
||||
)
|
||||
return Response(serializer.data, status=status.HTTP_201_CREATED)
|
||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
def destroy(self, request, slug, project_id, issue_id, reaction_code):
|
||||
project_deploy_board = ProjectDeployBoard.objects.get(
|
||||
workspace__slug=slug, project_id=project_id
|
||||
)
|
||||
|
||||
if not project_deploy_board.reactions:
|
||||
return Response(
|
||||
{"error": "Reactions are not enabled for this project board"},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
issue_reaction = IssueReaction.objects.get(
|
||||
workspace__slug=slug,
|
||||
issue_id=issue_id,
|
||||
reaction=reaction_code,
|
||||
actor=request.user,
|
||||
)
|
||||
issue_activity.delay(
|
||||
type="issue_reaction.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=json.dumps(
|
||||
{
|
||||
"reaction": str(reaction_code),
|
||||
"identifier": str(issue_reaction.id),
|
||||
}
|
||||
),
|
||||
epoch=int(timezone.now().timestamp()),
|
||||
)
|
||||
issue_reaction.delete()
|
||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
|
||||
class CommentReactionPublicViewSet(BaseViewSet):
|
||||
serializer_class = CommentReactionSerializer
|
||||
model = CommentReaction
|
||||
|
||||
def get_queryset(self):
|
||||
try:
|
||||
project_deploy_board = ProjectDeployBoard.objects.get(
|
||||
workspace__slug=self.kwargs.get("slug"),
|
||||
project_id=self.kwargs.get("project_id"),
|
||||
)
|
||||
if project_deploy_board.reactions:
|
||||
return (
|
||||
super()
|
||||
.get_queryset()
|
||||
.filter(workspace__slug=self.kwargs.get("slug"))
|
||||
.filter(project_id=self.kwargs.get("project_id"))
|
||||
.filter(comment_id=self.kwargs.get("comment_id"))
|
||||
.order_by("-created_at")
|
||||
.distinct()
|
||||
)
|
||||
return CommentReaction.objects.none()
|
||||
except ProjectDeployBoard.DoesNotExist:
|
||||
return CommentReaction.objects.none()
|
||||
|
||||
def create(self, request, slug, project_id, comment_id):
|
||||
project_deploy_board = ProjectDeployBoard.objects.get(
|
||||
workspace__slug=slug, project_id=project_id
|
||||
)
|
||||
|
||||
if not project_deploy_board.reactions:
|
||||
return Response(
|
||||
{"error": "Reactions are not enabled for this board"},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
||||
serializer = CommentReactionSerializer(data=request.data)
|
||||
if serializer.is_valid():
|
||||
serializer.save(
|
||||
project_id=project_id, comment_id=comment_id, actor=request.user
|
||||
)
|
||||
if not ProjectMember.objects.filter(
|
||||
project_id=project_id,
|
||||
member=request.user,
|
||||
is_active=True,
|
||||
).exists():
|
||||
# Add the user for workspace tracking
|
||||
_ = ProjectPublicMember.objects.get_or_create(
|
||||
project_id=project_id,
|
||||
member=request.user,
|
||||
)
|
||||
issue_activity.delay(
|
||||
type="comment_reaction.activity.created",
|
||||
requested_data=json.dumps(self.request.data, cls=DjangoJSONEncoder),
|
||||
actor_id=str(self.request.user.id),
|
||||
issue_id=None,
|
||||
project_id=str(self.kwargs.get("project_id", None)),
|
||||
current_instance=None,
|
||||
epoch=int(timezone.now().timestamp()),
|
||||
)
|
||||
return Response(serializer.data, status=status.HTTP_201_CREATED)
|
||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
def destroy(self, request, slug, project_id, comment_id, reaction_code):
|
||||
project_deploy_board = ProjectDeployBoard.objects.get(
|
||||
workspace__slug=slug, project_id=project_id
|
||||
)
|
||||
if not project_deploy_board.reactions:
|
||||
return Response(
|
||||
{"error": "Reactions are not enabled for this board"},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
||||
comment_reaction = CommentReaction.objects.get(
|
||||
project_id=project_id,
|
||||
workspace__slug=slug,
|
||||
comment_id=comment_id,
|
||||
reaction=reaction_code,
|
||||
actor=request.user,
|
||||
)
|
||||
issue_activity.delay(
|
||||
type="comment_reaction.activity.deleted",
|
||||
requested_data=None,
|
||||
actor_id=str(self.request.user.id),
|
||||
issue_id=None,
|
||||
project_id=str(self.kwargs.get("project_id", None)),
|
||||
current_instance=json.dumps(
|
||||
{
|
||||
"reaction": str(reaction_code),
|
||||
"identifier": str(comment_reaction.id),
|
||||
"comment_id": str(comment_id),
|
||||
}
|
||||
),
|
||||
epoch=int(timezone.now().timestamp()),
|
||||
)
|
||||
comment_reaction.delete()
|
||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
|
||||
class IssueVotePublicViewSet(BaseViewSet):
|
||||
model = IssueVote
|
||||
serializer_class = IssueVoteSerializer
|
||||
|
||||
def get_queryset(self):
|
||||
try:
|
||||
project_deploy_board = ProjectDeployBoard.objects.get(
|
||||
workspace__slug=self.kwargs.get("slug"),
|
||||
project_id=self.kwargs.get("project_id"),
|
||||
)
|
||||
if project_deploy_board.votes:
|
||||
return (
|
||||
super()
|
||||
.get_queryset()
|
||||
.filter(issue_id=self.kwargs.get("issue_id"))
|
||||
.filter(workspace__slug=self.kwargs.get("slug"))
|
||||
.filter(project_id=self.kwargs.get("project_id"))
|
||||
)
|
||||
return IssueVote.objects.none()
|
||||
except ProjectDeployBoard.DoesNotExist:
|
||||
return IssueVote.objects.none()
|
||||
|
||||
def create(self, request, slug, project_id, issue_id):
|
||||
issue_vote, _ = IssueVote.objects.get_or_create(
|
||||
actor_id=request.user.id,
|
||||
project_id=project_id,
|
||||
issue_id=issue_id,
|
||||
)
|
||||
# Add the user for workspace tracking
|
||||
if not ProjectMember.objects.filter(
|
||||
project_id=project_id,
|
||||
member=request.user,
|
||||
is_active=True,
|
||||
).exists():
|
||||
_ = ProjectPublicMember.objects.get_or_create(
|
||||
project_id=project_id,
|
||||
member=request.user,
|
||||
)
|
||||
issue_vote.vote = request.data.get("vote", 1)
|
||||
issue_vote.save()
|
||||
issue_activity.delay(
|
||||
type="issue_vote.activity.created",
|
||||
requested_data=json.dumps(self.request.data, cls=DjangoJSONEncoder),
|
||||
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()),
|
||||
)
|
||||
serializer = IssueVoteSerializer(issue_vote)
|
||||
return Response(serializer.data, status=status.HTTP_201_CREATED)
|
||||
|
||||
def destroy(self, request, slug, project_id, issue_id):
|
||||
issue_vote = IssueVote.objects.get(
|
||||
workspace__slug=slug,
|
||||
project_id=project_id,
|
||||
issue_id=issue_id,
|
||||
actor_id=request.user.id,
|
||||
)
|
||||
issue_activity.delay(
|
||||
type="issue_vote.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=json.dumps(
|
||||
{
|
||||
"vote": str(issue_vote.vote),
|
||||
"identifier": str(issue_vote.id),
|
||||
}
|
||||
),
|
||||
epoch=int(timezone.now().timestamp()),
|
||||
)
|
||||
issue_vote.delete()
|
||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
|
||||
class IssueRelationViewSet(BaseViewSet):
|
||||
serializer_class = IssueRelationSerializer
|
||||
model = IssueRelation
|
||||
@ -1973,182 +1545,6 @@ class IssueRelationViewSet(BaseViewSet):
|
||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
|
||||
class IssueRetrievePublicEndpoint(BaseAPIView):
|
||||
permission_classes = [
|
||||
AllowAny,
|
||||
]
|
||||
|
||||
def get(self, request, slug, project_id, issue_id):
|
||||
issue = Issue.objects.get(
|
||||
workspace__slug=slug, project_id=project_id, pk=issue_id
|
||||
)
|
||||
serializer = IssuePublicSerializer(issue)
|
||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||
|
||||
|
||||
class ProjectIssuesPublicEndpoint(BaseAPIView):
|
||||
permission_classes = [
|
||||
AllowAny,
|
||||
]
|
||||
|
||||
def get(self, request, slug, project_id):
|
||||
project_deploy_board = ProjectDeployBoard.objects.get(
|
||||
workspace__slug=slug, project_id=project_id
|
||||
)
|
||||
|
||||
filters = issue_filters(request.query_params, "GET")
|
||||
|
||||
# Custom ordering for priority and state
|
||||
priority_order = ["urgent", "high", "medium", "low", "none"]
|
||||
state_order = ["backlog", "unstarted", "started", "completed", "cancelled"]
|
||||
|
||||
order_by_param = request.GET.get("order_by", "-created_at")
|
||||
|
||||
issue_queryset = (
|
||||
Issue.issue_objects.annotate(
|
||||
sub_issues_count=Issue.issue_objects.filter(parent=OuterRef("id"))
|
||||
.order_by()
|
||||
.annotate(count=Func(F("id"), function="Count"))
|
||||
.values("count")
|
||||
)
|
||||
.filter(project_id=project_id)
|
||||
.filter(workspace__slug=slug)
|
||||
.select_related("project", "workspace", "state", "parent")
|
||||
.prefetch_related("assignees", "labels")
|
||||
.prefetch_related(
|
||||
Prefetch(
|
||||
"issue_reactions",
|
||||
queryset=IssueReaction.objects.select_related("actor"),
|
||||
)
|
||||
)
|
||||
.prefetch_related(
|
||||
Prefetch(
|
||||
"votes",
|
||||
queryset=IssueVote.objects.select_related("actor"),
|
||||
)
|
||||
)
|
||||
.filter(**filters)
|
||||
.annotate(cycle_id=F("issue_cycle__cycle_id"))
|
||||
.annotate(module_id=F("issue_module__module_id"))
|
||||
.annotate(
|
||||
link_count=IssueLink.objects.filter(issue=OuterRef("id"))
|
||||
.order_by()
|
||||
.annotate(count=Func(F("id"), function="Count"))
|
||||
.values("count")
|
||||
)
|
||||
.annotate(
|
||||
attachment_count=IssueAttachment.objects.filter(issue=OuterRef("id"))
|
||||
.order_by()
|
||||
.annotate(count=Func(F("id"), function="Count"))
|
||||
.values("count")
|
||||
)
|
||||
)
|
||||
|
||||
# Priority Ordering
|
||||
if order_by_param == "priority" or order_by_param == "-priority":
|
||||
priority_order = (
|
||||
priority_order if order_by_param == "priority" else priority_order[::-1]
|
||||
)
|
||||
issue_queryset = issue_queryset.annotate(
|
||||
priority_order=Case(
|
||||
*[
|
||||
When(priority=p, then=Value(i))
|
||||
for i, p in enumerate(priority_order)
|
||||
],
|
||||
output_field=CharField(),
|
||||
)
|
||||
).order_by("priority_order")
|
||||
|
||||
# State Ordering
|
||||
elif order_by_param in [
|
||||
"state__name",
|
||||
"state__group",
|
||||
"-state__name",
|
||||
"-state__group",
|
||||
]:
|
||||
state_order = (
|
||||
state_order
|
||||
if order_by_param in ["state__name", "state__group"]
|
||||
else state_order[::-1]
|
||||
)
|
||||
issue_queryset = issue_queryset.annotate(
|
||||
state_order=Case(
|
||||
*[
|
||||
When(state__group=state_group, then=Value(i))
|
||||
for i, state_group in enumerate(state_order)
|
||||
],
|
||||
default=Value(len(state_order)),
|
||||
output_field=CharField(),
|
||||
)
|
||||
).order_by("state_order")
|
||||
# assignee and label ordering
|
||||
elif order_by_param in [
|
||||
"labels__name",
|
||||
"-labels__name",
|
||||
"assignees__first_name",
|
||||
"-assignees__first_name",
|
||||
]:
|
||||
issue_queryset = issue_queryset.annotate(
|
||||
max_values=Max(
|
||||
order_by_param[1::]
|
||||
if order_by_param.startswith("-")
|
||||
else order_by_param
|
||||
)
|
||||
).order_by(
|
||||
"-max_values" if order_by_param.startswith("-") else "max_values"
|
||||
)
|
||||
else:
|
||||
issue_queryset = issue_queryset.order_by(order_by_param)
|
||||
|
||||
issues = IssuePublicSerializer(issue_queryset, many=True).data
|
||||
|
||||
state_group_order = [
|
||||
"backlog",
|
||||
"unstarted",
|
||||
"started",
|
||||
"completed",
|
||||
"cancelled",
|
||||
]
|
||||
|
||||
states = (
|
||||
State.objects.filter(
|
||||
~Q(name="Triage"),
|
||||
workspace__slug=slug,
|
||||
project_id=project_id,
|
||||
)
|
||||
.annotate(
|
||||
custom_order=Case(
|
||||
*[
|
||||
When(group=value, then=Value(index))
|
||||
for index, value in enumerate(state_group_order)
|
||||
],
|
||||
default=Value(len(state_group_order)),
|
||||
output_field=IntegerField(),
|
||||
),
|
||||
)
|
||||
.values("name", "group", "color", "id")
|
||||
.order_by("custom_order", "sequence")
|
||||
)
|
||||
|
||||
labels = Label.objects.filter(
|
||||
workspace__slug=slug, project_id=project_id
|
||||
).values("id", "name", "color", "parent")
|
||||
|
||||
## Grouping the results
|
||||
group_by = request.GET.get("group_by", False)
|
||||
if group_by:
|
||||
issues = group_results(issues, group_by)
|
||||
|
||||
return Response(
|
||||
{
|
||||
"issues": issues,
|
||||
"states": states,
|
||||
"labels": labels,
|
||||
},
|
||||
status=status.HTTP_200_OK,
|
||||
)
|
||||
|
||||
|
||||
class IssueDraftViewSet(BaseViewSet):
|
||||
permission_classes = [
|
||||
ProjectEntityPermission,
|
@ -3,7 +3,6 @@ import json
|
||||
|
||||
# Django Imports
|
||||
from django.utils import timezone
|
||||
from django.db import IntegrityError
|
||||
from django.db.models import Prefetch, F, OuterRef, Func, Exists, Count, Q
|
||||
from django.core import serializers
|
||||
from django.utils.decorators import method_decorator
|
||||
@ -12,11 +11,10 @@ from django.views.decorators.gzip import gzip_page
|
||||
# Third party imports
|
||||
from rest_framework.response import Response
|
||||
from rest_framework import status
|
||||
from sentry_sdk import capture_exception
|
||||
|
||||
# Module imports
|
||||
from . import BaseViewSet, BaseAPIView, WebhookMixin
|
||||
from plane.api.serializers import (
|
||||
from plane.app.serializers import (
|
||||
ModuleWriteSerializer,
|
||||
ModuleSerializer,
|
||||
ModuleIssueSerializer,
|
||||
@ -24,7 +22,7 @@ from plane.api.serializers import (
|
||||
ModuleFavoriteSerializer,
|
||||
IssueStateSerializer,
|
||||
)
|
||||
from plane.api.permissions import ProjectEntityPermission
|
||||
from plane.app.permissions import ProjectEntityPermission
|
||||
from plane.db.models import (
|
||||
Module,
|
||||
ModuleIssue,
|
@ -5,7 +5,6 @@ from django.utils import timezone
|
||||
# Third party imports
|
||||
from rest_framework import status
|
||||
from rest_framework.response import Response
|
||||
from sentry_sdk import capture_exception
|
||||
from plane.utils.paginator import BasePaginator
|
||||
|
||||
# Module imports
|
||||
@ -17,7 +16,7 @@ from plane.db.models import (
|
||||
Issue,
|
||||
WorkspaceMember,
|
||||
)
|
||||
from plane.api.serializers import NotificationSerializer
|
||||
from plane.app.serializers import NotificationSerializer
|
||||
|
||||
|
||||
class NotificationViewSet(BaseViewSet, BasePaginator):
|
@ -29,7 +29,6 @@ from plane.db.models import (
|
||||
ProjectMemberInvite,
|
||||
ProjectMember,
|
||||
)
|
||||
from plane.api.serializers import UserSerializer
|
||||
from .base import BaseAPIView
|
||||
|
||||
|
@ -3,26 +3,18 @@ from datetime import timedelta, date, datetime
|
||||
|
||||
# Django imports
|
||||
from django.db import connection
|
||||
from django.db.models import Exists, OuterRef, Q, Prefetch
|
||||
from django.db.models import Exists, OuterRef, Q
|
||||
from django.utils import timezone
|
||||
from django.utils.decorators import method_decorator
|
||||
from django.views.decorators.gzip import gzip_page
|
||||
from django.db.models import (
|
||||
OuterRef,
|
||||
Func,
|
||||
F,
|
||||
Q,
|
||||
Exists,
|
||||
)
|
||||
|
||||
# Third party imports
|
||||
from rest_framework import status
|
||||
from rest_framework.response import Response
|
||||
from sentry_sdk import capture_exception
|
||||
|
||||
# Module imports
|
||||
from .base import BaseViewSet, BaseAPIView
|
||||
from plane.api.permissions import ProjectEntityPermission
|
||||
from plane.app.permissions import ProjectEntityPermission
|
||||
from plane.db.models import (
|
||||
Page,
|
||||
PageFavorite,
|
||||
@ -31,7 +23,7 @@ from plane.db.models import (
|
||||
IssueActivity,
|
||||
PageLog,
|
||||
)
|
||||
from plane.api.serializers import (
|
||||
from plane.app.serializers import (
|
||||
PageSerializer,
|
||||
PageFavoriteSerializer,
|
||||
PageLogSerializer,
|
||||
@ -87,15 +79,10 @@ class PageViewSet(BaseViewSet):
|
||||
.annotate(is_favorite=Exists(subquery))
|
||||
.order_by(self.request.GET.get("order_by", "-created_at"))
|
||||
.prefetch_related("labels")
|
||||
.order_by("-is_favorite","-created_at")
|
||||
.order_by("-is_favorite", "-created_at")
|
||||
.distinct()
|
||||
)
|
||||
|
||||
def perform_create(self, serializer):
|
||||
serializer.save(
|
||||
project_id=self.kwargs.get("project_id"), owned_by=self.request.user
|
||||
)
|
||||
|
||||
def create(self, request, slug, project_id):
|
||||
serializer = PageSerializer(
|
||||
data=request.data,
|
||||
@ -148,10 +135,8 @@ class PageViewSet(BaseViewSet):
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
||||
def lock(self, request, slug, project_id, pk):
|
||||
page = Page.objects.filter(
|
||||
pk=pk, workspace__slug=slug, project_id=project_id
|
||||
)
|
||||
def lock(self, request, slug, project_id, page_id):
|
||||
page = Page.objects.get(pk=page_id, workspace__slug=slug, project_id=project_id)
|
||||
|
||||
# only the owner can lock the page
|
||||
if request.user.id != page.owned_by_id:
|
||||
@ -163,8 +148,8 @@ class PageViewSet(BaseViewSet):
|
||||
page.save()
|
||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
def unlock(self, request, slug, project_id, pk):
|
||||
page = Page.objects.get(pk=pk, workspace__slug=slug, project_id=project_id)
|
||||
def unlock(self, request, slug, project_id, page_id):
|
||||
page = Page.objects.get(pk=page_id, workspace__slug=slug, project_id=project_id)
|
||||
|
||||
# only the owner can unlock the page
|
||||
if request.user.id != page.owned_by_id:
|
||||
@ -242,27 +227,31 @@ class PageViewSet(BaseViewSet):
|
||||
)
|
||||
|
||||
def archive(self, request, slug, project_id, page_id):
|
||||
_ = Page.objects.get(
|
||||
project_id=project_id,
|
||||
owned_by_id=request.user.id,
|
||||
workspace__slug=slug,
|
||||
pk=page_id,
|
||||
)
|
||||
page = Page.objects.get(pk=page_id, workspace__slug=slug, project_id=project_id)
|
||||
|
||||
if page.owned_by_id != request.user.id:
|
||||
return Response(
|
||||
{"error": "Only the owner of the page can archive a page"},
|
||||
status=status.HTTP_204_NO_CONTENT,
|
||||
)
|
||||
|
||||
unarchive_archive_page_and_descendants(page_id, datetime.now())
|
||||
|
||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
def unarchive(self, request, slug, project_id, page_id):
|
||||
page = Page.objects.get(
|
||||
project_id=project_id,
|
||||
owned_by_id=request.user.id,
|
||||
workspace__slug=slug,
|
||||
pk=page_id,
|
||||
)
|
||||
page = Page.objects.get(pk=page_id, workspace__slug=slug, project_id=project_id)
|
||||
|
||||
page.parent = None
|
||||
page.save()
|
||||
if page.owned_by_id != request.user.id:
|
||||
return Response(
|
||||
{"error": "Only the owner of the page can unarchive a page"},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
||||
# if parent page is archived then the page will be un archived breaking the hierarchy
|
||||
if page.parent_id and page.parent.archived_at:
|
||||
page.parent = None
|
||||
page.save(update_fields=['parent'])
|
||||
|
||||
unarchive_archive_page_and_descendants(page_id, None)
|
||||
|
||||
@ -275,20 +264,13 @@ class PageViewSet(BaseViewSet):
|
||||
workspace__slug=slug,
|
||||
)
|
||||
.filter(archived_at__isnull=False)
|
||||
.filter(parent_id__isnull=True)
|
||||
)
|
||||
|
||||
if not pages:
|
||||
return Response(
|
||||
{"error": "No pages found"}, status=status.HTTP_400_BAD_REQUEST
|
||||
)
|
||||
|
||||
return Response(
|
||||
PageSerializer(pages, many=True).data, status=status.HTTP_200_OK
|
||||
)
|
||||
|
||||
|
||||
|
||||
class PageFavoriteViewSet(BaseViewSet):
|
||||
permission_classes = [
|
||||
ProjectEntityPermission,
|
||||
@ -410,11 +392,9 @@ class SubPagesEndpoint(BaseAPIView):
|
||||
workspace__slug=slug,
|
||||
entity_name__in=["forward_link", "back_link"],
|
||||
)
|
||||
.filter(archived_at__isnull=True)
|
||||
.select_related("project")
|
||||
.select_related("workspace")
|
||||
)
|
||||
return Response(
|
||||
SubPageSerializer(pages, many=True).data, status=status.HTTP_200_OK
|
||||
)
|
||||
|
@ -27,7 +27,7 @@ from rest_framework.permissions import AllowAny
|
||||
|
||||
# Module imports
|
||||
from .base import BaseViewSet, BaseAPIView, WebhookMixin
|
||||
from plane.api.serializers import (
|
||||
from plane.app.serializers import (
|
||||
ProjectSerializer,
|
||||
ProjectListSerializer,
|
||||
ProjectMemberSerializer,
|
||||
@ -38,12 +38,9 @@ from plane.api.serializers import (
|
||||
ProjectMemberAdminSerializer,
|
||||
)
|
||||
|
||||
from plane.api.permissions import (
|
||||
WorkspaceUserPermission,
|
||||
from plane.app.permissions import (
|
||||
ProjectBasePermission,
|
||||
ProjectEntityPermission,
|
||||
ProjectMemberPermission,
|
||||
ProjectLitePermission,
|
||||
)
|
||||
|
||||
from plane.db.models import (
|
||||
@ -965,6 +962,37 @@ class ProjectFavoritesViewSet(BaseViewSet):
|
||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
|
||||
class ProjectPublicCoverImagesEndpoint(BaseAPIView):
|
||||
permission_classes = [
|
||||
AllowAny,
|
||||
]
|
||||
|
||||
def get(self, request):
|
||||
files = []
|
||||
s3 = boto3.client(
|
||||
"s3",
|
||||
aws_access_key_id=settings.AWS_ACCESS_KEY_ID,
|
||||
aws_secret_access_key=settings.AWS_SECRET_ACCESS_KEY,
|
||||
)
|
||||
params = {
|
||||
"Bucket": settings.AWS_S3_BUCKET_NAME,
|
||||
"Prefix": "static/project-cover/",
|
||||
}
|
||||
|
||||
response = s3.list_objects_v2(**params)
|
||||
# Extracting file keys from the response
|
||||
if "Contents" in response:
|
||||
for content in response["Contents"]:
|
||||
if not content["Key"].endswith(
|
||||
"/"
|
||||
): # This line ensures we're only getting files, not "sub-folders"
|
||||
files.append(
|
||||
f"https://{settings.AWS_S3_BUCKET_NAME}.s3.{settings.AWS_REGION}.amazonaws.com/{content['Key']}"
|
||||
)
|
||||
|
||||
return Response(files, status=status.HTTP_200_OK)
|
||||
|
||||
|
||||
class ProjectDeployBoardViewSet(BaseViewSet):
|
||||
permission_classes = [
|
||||
ProjectMemberPermission,
|
||||
@ -1013,76 +1041,3 @@ class ProjectDeployBoardViewSet(BaseViewSet):
|
||||
|
||||
serializer = ProjectDeployBoardSerializer(project_deploy_board)
|
||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||
|
||||
|
||||
class ProjectDeployBoardPublicSettingsEndpoint(BaseAPIView):
|
||||
permission_classes = [
|
||||
AllowAny,
|
||||
]
|
||||
|
||||
def get(self, request, slug, project_id):
|
||||
project_deploy_board = ProjectDeployBoard.objects.get(
|
||||
workspace__slug=slug, project_id=project_id
|
||||
)
|
||||
serializer = ProjectDeployBoardSerializer(project_deploy_board)
|
||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||
|
||||
|
||||
class WorkspaceProjectDeployBoardEndpoint(BaseAPIView):
|
||||
permission_classes = [
|
||||
AllowAny,
|
||||
]
|
||||
|
||||
def get(self, request, slug):
|
||||
projects = (
|
||||
Project.objects.filter(workspace__slug=slug)
|
||||
.annotate(
|
||||
is_public=Exists(
|
||||
ProjectDeployBoard.objects.filter(
|
||||
workspace__slug=slug, project_id=OuterRef("pk")
|
||||
)
|
||||
)
|
||||
)
|
||||
.filter(is_public=True)
|
||||
).values(
|
||||
"id",
|
||||
"identifier",
|
||||
"name",
|
||||
"description",
|
||||
"emoji",
|
||||
"icon_prop",
|
||||
"cover_image",
|
||||
)
|
||||
|
||||
return Response(projects, status=status.HTTP_200_OK)
|
||||
|
||||
|
||||
class ProjectPublicCoverImagesEndpoint(BaseAPIView):
|
||||
permission_classes = [
|
||||
AllowAny,
|
||||
]
|
||||
|
||||
def get(self, request):
|
||||
files = []
|
||||
s3 = boto3.client(
|
||||
"s3",
|
||||
aws_access_key_id=settings.AWS_ACCESS_KEY_ID,
|
||||
aws_secret_access_key=settings.AWS_SECRET_ACCESS_KEY,
|
||||
)
|
||||
params = {
|
||||
"Bucket": settings.AWS_S3_BUCKET_NAME,
|
||||
"Prefix": "static/project-cover/",
|
||||
}
|
||||
|
||||
response = s3.list_objects_v2(**params)
|
||||
# Extracting file keys from the response
|
||||
if "Contents" in response:
|
||||
for content in response["Contents"]:
|
||||
if not content["Key"].endswith(
|
||||
"/"
|
||||
): # This line ensures we're only getting files, not "sub-folders"
|
||||
files.append(
|
||||
f"https://{settings.AWS_S3_BUCKET_NAME}.s3.{settings.AWS_REGION}.amazonaws.com/{content['Key']}"
|
||||
)
|
||||
|
||||
return Response(files, status=status.HTTP_200_OK)
|
@ -7,7 +7,6 @@ from django.db.models import Q
|
||||
# Third party imports
|
||||
from rest_framework import status
|
||||
from rest_framework.response import Response
|
||||
from sentry_sdk import capture_exception
|
||||
|
||||
# Module imports
|
||||
from .base import BaseAPIView
|
@ -7,12 +7,11 @@ from django.db.models import Q
|
||||
# Third party imports
|
||||
from rest_framework.response import Response
|
||||
from rest_framework import status
|
||||
from sentry_sdk import capture_exception
|
||||
|
||||
# Module imports
|
||||
from . import BaseViewSet, BaseAPIView
|
||||
from plane.api.serializers import StateSerializer
|
||||
from plane.api.permissions import ProjectEntityPermission
|
||||
from . import BaseViewSet
|
||||
from plane.app.serializers import StateSerializer
|
||||
from plane.app.permissions import ProjectEntityPermission
|
||||
from plane.db.models import State, Issue
|
||||
|
||||
|
@ -2,17 +2,16 @@
|
||||
from rest_framework.response import Response
|
||||
from rest_framework import status
|
||||
|
||||
from sentry_sdk import capture_exception
|
||||
|
||||
# Module imports
|
||||
from plane.api.serializers import (
|
||||
from plane.app.serializers import (
|
||||
UserSerializer,
|
||||
IssueActivitySerializer,
|
||||
UserMeSerializer,
|
||||
UserMeSettingsSerializer,
|
||||
)
|
||||
|
||||
from plane.api.views.base import BaseViewSet, BaseAPIView
|
||||
from plane.app.views.base import BaseViewSet, BaseAPIView
|
||||
from plane.db.models import User, IssueActivity, WorkspaceMember
|
||||
from plane.license.models import Instance, InstanceAdmin
|
||||
from plane.utils.paginator import BasePaginator
|
@ -18,17 +18,16 @@ from django.db.models import Prefetch, OuterRef, Exists
|
||||
# Third party imports
|
||||
from rest_framework.response import Response
|
||||
from rest_framework import status
|
||||
from sentry_sdk import capture_exception
|
||||
|
||||
# Module imports
|
||||
from . import BaseViewSet, BaseAPIView
|
||||
from plane.api.serializers import (
|
||||
from . import BaseViewSet
|
||||
from plane.app.serializers import (
|
||||
GlobalViewSerializer,
|
||||
IssueViewSerializer,
|
||||
IssueLiteSerializer,
|
||||
IssueViewFavoriteSerializer,
|
||||
)
|
||||
from plane.api.permissions import WorkspaceEntityPermission, ProjectEntityPermission
|
||||
from plane.app.permissions import WorkspaceEntityPermission, ProjectEntityPermission
|
||||
from plane.db.models import (
|
||||
Workspace,
|
||||
GlobalView,
|
@ -9,8 +9,8 @@ from rest_framework.response import Response
|
||||
from plane.db.models import Webhook, WebhookLog, Workspace
|
||||
from plane.db.models.webhook import generate_token
|
||||
from .base import BaseAPIView
|
||||
from plane.api.permissions import WorkspaceOwnerPermission
|
||||
from plane.api.serializers import WebhookSerializer, WebhookLogSerializer
|
||||
from plane.app.permissions import WorkspaceOwnerPermission
|
||||
from plane.app.serializers import WebhookSerializer, WebhookLogSerializer
|
||||
|
||||
|
||||
class WebhookEndpoint(BaseAPIView):
|
@ -29,10 +29,10 @@ from django.db.models.fields import DateField
|
||||
# Third party modules
|
||||
from rest_framework import status
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.permissions import AllowAny, IsAuthenticated
|
||||
from rest_framework.permissions import AllowAny
|
||||
|
||||
# Module imports
|
||||
from plane.api.serializers import (
|
||||
from plane.app.serializers import (
|
||||
WorkSpaceSerializer,
|
||||
WorkSpaceMemberSerializer,
|
||||
TeamSerializer,
|
||||
@ -45,7 +45,7 @@ from plane.api.serializers import (
|
||||
WorkspaceMemberAdminSerializer,
|
||||
WorkspaceMemberMeSerializer,
|
||||
)
|
||||
from plane.api.views.base import BaseAPIView
|
||||
from plane.app.views.base import BaseAPIView
|
||||
from . import BaseViewSet
|
||||
from plane.db.models import (
|
||||
User,
|
||||
@ -65,7 +65,7 @@ from plane.db.models import (
|
||||
CycleIssue,
|
||||
IssueReaction,
|
||||
)
|
||||
from plane.api.permissions import (
|
||||
from plane.app.permissions import (
|
||||
WorkSpaceBasePermission,
|
||||
WorkSpaceAdminPermission,
|
||||
WorkspaceEntityPermission,
|
@ -13,7 +13,7 @@ from celery import shared_task
|
||||
from sentry_sdk import capture_exception
|
||||
|
||||
# Module imports
|
||||
from plane.api.serializers import ImporterSerializer
|
||||
from plane.app.serializers import ImporterSerializer
|
||||
from plane.db.models import (
|
||||
Importer,
|
||||
WorkspaceMember,
|
||||
|
@ -21,14 +21,11 @@ from plane.db.models import (
|
||||
State,
|
||||
Cycle,
|
||||
Module,
|
||||
IssueSubscriber,
|
||||
Notification,
|
||||
IssueAssignee,
|
||||
IssueReaction,
|
||||
CommentReaction,
|
||||
IssueComment,
|
||||
)
|
||||
from plane.api.serializers import IssueActivitySerializer
|
||||
from plane.app.serializers import IssueActivitySerializer
|
||||
from plane.bgtasks.notification_task import notifications
|
||||
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
# Module imports
|
||||
from plane.license.models import Instance, InstanceAdmin, InstanceConfiguration
|
||||
from plane.api.serializers import BaseSerializer
|
||||
from plane.api.serializers import UserAdminLiteSerializer
|
||||
from plane.app.serializers import BaseSerializer
|
||||
from plane.app.serializers import UserAdminLiteSerializer
|
||||
|
||||
|
||||
class InstanceSerializer(BaseSerializer):
|
||||
|
@ -11,7 +11,7 @@ from rest_framework import status
|
||||
from rest_framework.response import Response
|
||||
|
||||
# Module imports
|
||||
from plane.api.views import BaseAPIView
|
||||
from plane.app.views import BaseAPIView
|
||||
from plane.license.models import Instance, InstanceAdmin, InstanceConfiguration
|
||||
from plane.license.api.serializers import (
|
||||
InstanceSerializer,
|
||||
|
@ -34,7 +34,8 @@ INSTALLED_APPS = [
|
||||
"django.contrib.sessions",
|
||||
# Inhouse apps
|
||||
"plane.analytics",
|
||||
"plane.api",
|
||||
"plane.app",
|
||||
"plane.space",
|
||||
"plane.bgtasks",
|
||||
"plane.db",
|
||||
"plane.utils",
|
||||
|
0
apiserver/plane/space/__init__.py
Normal file
0
apiserver/plane/space/__init__.py
Normal file
5
apiserver/plane/space/apps.py
Normal file
5
apiserver/plane/space/apps.py
Normal file
@ -0,0 +1,5 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class SpaceConfig(AppConfig):
|
||||
name = "plane.space"
|
5
apiserver/plane/space/serializer/__init__.py
Normal file
5
apiserver/plane/space/serializer/__init__.py
Normal file
@ -0,0 +1,5 @@
|
||||
from .user import UserLiteSerializer
|
||||
|
||||
from .issue import LabelLiteSerializer, StateLiteSerializer
|
||||
|
||||
from .state import StateSerializer, StateLiteSerializer
|
58
apiserver/plane/space/serializer/base.py
Normal file
58
apiserver/plane/space/serializer/base.py
Normal file
@ -0,0 +1,58 @@
|
||||
from rest_framework import serializers
|
||||
|
||||
|
||||
class BaseSerializer(serializers.ModelSerializer):
|
||||
id = serializers.PrimaryKeyRelatedField(read_only=True)
|
||||
|
||||
class DynamicBaseSerializer(BaseSerializer):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
# If 'fields' is provided in the arguments, remove it and store it separately.
|
||||
# This is done so as not to pass this custom argument up to the superclass.
|
||||
fields = kwargs.pop("fields", None)
|
||||
|
||||
# Call the initialization of the superclass.
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
# If 'fields' was provided, filter the fields of the serializer accordingly.
|
||||
if fields is not None:
|
||||
self.fields = self._filter_fields(fields)
|
||||
|
||||
def _filter_fields(self, fields):
|
||||
"""
|
||||
Adjust the serializer's fields based on the provided 'fields' list.
|
||||
|
||||
:param fields: List or dictionary specifying which fields to include in the serializer.
|
||||
:return: The updated fields for the serializer.
|
||||
"""
|
||||
# Check each field_name in the provided fields.
|
||||
for field_name in fields:
|
||||
# If the field is a dictionary (indicating nested fields),
|
||||
# loop through its keys and values.
|
||||
if isinstance(field_name, dict):
|
||||
for key, value in field_name.items():
|
||||
# If the value of this nested field is a list,
|
||||
# perform a recursive filter on it.
|
||||
if isinstance(value, list):
|
||||
self._filter_fields(self.fields[key], value)
|
||||
|
||||
# Create a list to store allowed fields.
|
||||
allowed = []
|
||||
for item in fields:
|
||||
# If the item is a string, it directly represents a field's name.
|
||||
if isinstance(item, str):
|
||||
allowed.append(item)
|
||||
# If the item is a dictionary, it represents a nested field.
|
||||
# Add the key of this dictionary to the allowed list.
|
||||
elif isinstance(item, dict):
|
||||
allowed.append(list(item.keys())[0])
|
||||
|
||||
# Convert the current serializer's fields and the allowed fields to sets.
|
||||
existing = set(self.fields)
|
||||
allowed = set(allowed)
|
||||
|
||||
# Remove fields from the serializer that aren't in the 'allowed' list.
|
||||
for field_name in (existing - allowed):
|
||||
self.fields.pop(field_name)
|
||||
|
||||
return self.fields
|
18
apiserver/plane/space/serializer/cycle.py
Normal file
18
apiserver/plane/space/serializer/cycle.py
Normal file
@ -0,0 +1,18 @@
|
||||
# Module imports
|
||||
from .base import BaseSerializer
|
||||
from plane.db.models import (
|
||||
Cycle,
|
||||
)
|
||||
|
||||
class CycleBaseSerializer(BaseSerializer):
|
||||
class Meta:
|
||||
model = Cycle
|
||||
fields = "__all__"
|
||||
read_only_fields = [
|
||||
"workspace",
|
||||
"project",
|
||||
"created_by",
|
||||
"updated_by",
|
||||
"created_at",
|
||||
"updated_at",
|
||||
]
|
47
apiserver/plane/space/serializer/inbox.py
Normal file
47
apiserver/plane/space/serializer/inbox.py
Normal file
@ -0,0 +1,47 @@
|
||||
# Third Party imports
|
||||
from rest_framework import serializers
|
||||
|
||||
# Module imports
|
||||
from .base import BaseSerializer
|
||||
from .user import UserLiteSerializer
|
||||
from .state import StateLiteSerializer
|
||||
from .project import ProjectLiteSerializer
|
||||
from .issue import IssueFlatSerializer, LabelLiteSerializer
|
||||
from plane.db.models import (
|
||||
Issue,
|
||||
InboxIssue,
|
||||
)
|
||||
|
||||
|
||||
class InboxIssueSerializer(BaseSerializer):
|
||||
issue_detail = IssueFlatSerializer(source="issue", read_only=True)
|
||||
project_detail = ProjectLiteSerializer(source="project", read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = InboxIssue
|
||||
fields = "__all__"
|
||||
read_only_fields = [
|
||||
"project",
|
||||
"workspace",
|
||||
]
|
||||
|
||||
|
||||
class InboxIssueLiteSerializer(BaseSerializer):
|
||||
class Meta:
|
||||
model = InboxIssue
|
||||
fields = ["id", "status", "duplicate_to", "snoozed_till", "source"]
|
||||
read_only_fields = fields
|
||||
|
||||
|
||||
class IssueStateInboxSerializer(BaseSerializer):
|
||||
state_detail = StateLiteSerializer(read_only=True, source="state")
|
||||
project_detail = ProjectLiteSerializer(read_only=True, source="project")
|
||||
label_details = LabelLiteSerializer(read_only=True, source="labels", many=True)
|
||||
assignee_details = UserLiteSerializer(read_only=True, source="assignees", many=True)
|
||||
sub_issues_count = serializers.IntegerField(read_only=True)
|
||||
bridge_id = serializers.UUIDField(read_only=True)
|
||||
issue_inbox = InboxIssueLiteSerializer(read_only=True, many=True)
|
||||
|
||||
class Meta:
|
||||
model = Issue
|
||||
fields = "__all__"
|
506
apiserver/plane/space/serializer/issue.py
Normal file
506
apiserver/plane/space/serializer/issue.py
Normal file
@ -0,0 +1,506 @@
|
||||
|
||||
# Django imports
|
||||
from django.utils import timezone
|
||||
|
||||
# Third Party imports
|
||||
from rest_framework import serializers
|
||||
|
||||
# Module imports
|
||||
from .base import BaseSerializer
|
||||
from .user import UserLiteSerializer
|
||||
from .state import StateSerializer, StateLiteSerializer
|
||||
from .project import ProjectLiteSerializer
|
||||
from .cycle import CycleBaseSerializer
|
||||
from .module import ModuleBaseSerializer
|
||||
from .workspace import WorkspaceLiteSerializer
|
||||
from plane.db.models import (
|
||||
User,
|
||||
Issue,
|
||||
IssueComment,
|
||||
IssueAssignee,
|
||||
IssueLabel,
|
||||
Label,
|
||||
CycleIssue,
|
||||
ModuleIssue,
|
||||
IssueLink,
|
||||
IssueAttachment,
|
||||
IssueReaction,
|
||||
CommentReaction,
|
||||
IssueVote,
|
||||
IssueRelation,
|
||||
)
|
||||
|
||||
|
||||
class IssueStateFlatSerializer(BaseSerializer):
|
||||
state_detail = StateLiteSerializer(read_only=True, source="state")
|
||||
project_detail = ProjectLiteSerializer(read_only=True, source="project")
|
||||
|
||||
class Meta:
|
||||
model = Issue
|
||||
fields = [
|
||||
"id",
|
||||
"sequence_id",
|
||||
"name",
|
||||
"state_detail",
|
||||
"project_detail",
|
||||
]
|
||||
|
||||
|
||||
class LabelSerializer(BaseSerializer):
|
||||
workspace_detail = WorkspaceLiteSerializer(source="workspace", read_only=True)
|
||||
project_detail = ProjectLiteSerializer(source="project", read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = Label
|
||||
fields = "__all__"
|
||||
read_only_fields = [
|
||||
"workspace",
|
||||
"project",
|
||||
]
|
||||
|
||||
|
||||
class IssueProjectLiteSerializer(BaseSerializer):
|
||||
project_detail = ProjectLiteSerializer(source="project", read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = Issue
|
||||
fields = [
|
||||
"id",
|
||||
"project_detail",
|
||||
"name",
|
||||
"sequence_id",
|
||||
]
|
||||
read_only_fields = fields
|
||||
|
||||
|
||||
class IssueRelationSerializer(BaseSerializer):
|
||||
issue_detail = IssueProjectLiteSerializer(read_only=True, source="related_issue")
|
||||
|
||||
class Meta:
|
||||
model = IssueRelation
|
||||
fields = [
|
||||
"issue_detail",
|
||||
"relation_type",
|
||||
"related_issue",
|
||||
"issue",
|
||||
"id"
|
||||
]
|
||||
read_only_fields = [
|
||||
"workspace",
|
||||
"project",
|
||||
]
|
||||
|
||||
class RelatedIssueSerializer(BaseSerializer):
|
||||
issue_detail = IssueProjectLiteSerializer(read_only=True, source="issue")
|
||||
|
||||
class Meta:
|
||||
model = IssueRelation
|
||||
fields = [
|
||||
"issue_detail",
|
||||
"relation_type",
|
||||
"related_issue",
|
||||
"issue",
|
||||
"id"
|
||||
]
|
||||
read_only_fields = [
|
||||
"workspace",
|
||||
"project",
|
||||
]
|
||||
|
||||
|
||||
class IssueCycleDetailSerializer(BaseSerializer):
|
||||
cycle_detail = CycleBaseSerializer(read_only=True, source="cycle")
|
||||
|
||||
class Meta:
|
||||
model = CycleIssue
|
||||
fields = "__all__"
|
||||
read_only_fields = [
|
||||
"workspace",
|
||||
"project",
|
||||
"created_by",
|
||||
"updated_by",
|
||||
"created_at",
|
||||
"updated_at",
|
||||
]
|
||||
|
||||
|
||||
class IssueModuleDetailSerializer(BaseSerializer):
|
||||
module_detail = ModuleBaseSerializer(read_only=True, source="module")
|
||||
|
||||
class Meta:
|
||||
model = ModuleIssue
|
||||
fields = "__all__"
|
||||
read_only_fields = [
|
||||
"workspace",
|
||||
"project",
|
||||
"created_by",
|
||||
"updated_by",
|
||||
"created_at",
|
||||
"updated_at",
|
||||
]
|
||||
|
||||
|
||||
class IssueLinkSerializer(BaseSerializer):
|
||||
created_by_detail = UserLiteSerializer(read_only=True, source="created_by")
|
||||
|
||||
class Meta:
|
||||
model = IssueLink
|
||||
fields = "__all__"
|
||||
read_only_fields = [
|
||||
"workspace",
|
||||
"project",
|
||||
"created_by",
|
||||
"updated_by",
|
||||
"created_at",
|
||||
"updated_at",
|
||||
"issue",
|
||||
]
|
||||
|
||||
# Validation if url already exists
|
||||
def create(self, validated_data):
|
||||
if IssueLink.objects.filter(
|
||||
url=validated_data.get("url"), issue_id=validated_data.get("issue_id")
|
||||
).exists():
|
||||
raise serializers.ValidationError(
|
||||
{"error": "URL already exists for this Issue"}
|
||||
)
|
||||
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",
|
||||
]
|
||||
|
||||
|
||||
class IssueReactionSerializer(BaseSerializer):
|
||||
|
||||
actor_detail = UserLiteSerializer(read_only=True, source="actor")
|
||||
|
||||
class Meta:
|
||||
model = IssueReaction
|
||||
fields = "__all__"
|
||||
read_only_fields = [
|
||||
"workspace",
|
||||
"project",
|
||||
"issue",
|
||||
"actor",
|
||||
]
|
||||
|
||||
|
||||
class IssueSerializer(BaseSerializer):
|
||||
project_detail = ProjectLiteSerializer(read_only=True, source="project")
|
||||
state_detail = StateSerializer(read_only=True, source="state")
|
||||
parent_detail = IssueStateFlatSerializer(read_only=True, source="parent")
|
||||
label_details = LabelSerializer(read_only=True, source="labels", many=True)
|
||||
assignee_details = UserLiteSerializer(read_only=True, source="assignees", many=True)
|
||||
related_issues = IssueRelationSerializer(read_only=True, source="issue_relation", many=True)
|
||||
issue_relations = RelatedIssueSerializer(read_only=True, source="issue_related", many=True)
|
||||
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)
|
||||
issue_reactions = IssueReactionSerializer(read_only=True, many=True)
|
||||
|
||||
class Meta:
|
||||
model = Issue
|
||||
fields = "__all__"
|
||||
read_only_fields = [
|
||||
"workspace",
|
||||
"project",
|
||||
"created_by",
|
||||
"updated_by",
|
||||
"created_at",
|
||||
"updated_at",
|
||||
]
|
||||
|
||||
|
||||
class IssueFlatSerializer(BaseSerializer):
|
||||
## Contain only flat fields
|
||||
|
||||
class Meta:
|
||||
model = Issue
|
||||
fields = [
|
||||
"id",
|
||||
"name",
|
||||
"description",
|
||||
"description_html",
|
||||
"priority",
|
||||
"start_date",
|
||||
"target_date",
|
||||
"sequence_id",
|
||||
"sort_order",
|
||||
"is_draft",
|
||||
]
|
||||
|
||||
|
||||
class CommentReactionLiteSerializer(BaseSerializer):
|
||||
actor_detail = UserLiteSerializer(read_only=True, source="actor")
|
||||
|
||||
class Meta:
|
||||
model = CommentReaction
|
||||
fields = [
|
||||
"id",
|
||||
"reaction",
|
||||
"comment",
|
||||
"actor_detail",
|
||||
]
|
||||
|
||||
|
||||
class IssueCommentSerializer(BaseSerializer):
|
||||
actor_detail = UserLiteSerializer(read_only=True, source="actor")
|
||||
issue_detail = IssueFlatSerializer(read_only=True, source="issue")
|
||||
project_detail = ProjectLiteSerializer(read_only=True, source="project")
|
||||
workspace_detail = WorkspaceLiteSerializer(read_only=True, source="workspace")
|
||||
comment_reactions = CommentReactionLiteSerializer(read_only=True, many=True)
|
||||
is_member = serializers.BooleanField(read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = IssueComment
|
||||
fields = "__all__"
|
||||
read_only_fields = [
|
||||
"workspace",
|
||||
"project",
|
||||
"issue",
|
||||
"created_by",
|
||||
"updated_by",
|
||||
"created_at",
|
||||
"updated_at",
|
||||
]
|
||||
|
||||
|
||||
##TODO: Find a better way to write this serializer
|
||||
## Find a better approach to save manytomany?
|
||||
class IssueCreateSerializer(BaseSerializer):
|
||||
state_detail = StateSerializer(read_only=True, source="state")
|
||||
created_by_detail = UserLiteSerializer(read_only=True, source="created_by")
|
||||
project_detail = ProjectLiteSerializer(read_only=True, source="project")
|
||||
workspace_detail = WorkspaceLiteSerializer(read_only=True, source="workspace")
|
||||
|
||||
assignees = serializers.ListField(
|
||||
child=serializers.PrimaryKeyRelatedField(queryset=User.objects.all()),
|
||||
write_only=True,
|
||||
required=False,
|
||||
)
|
||||
|
||||
labels = serializers.ListField(
|
||||
child=serializers.PrimaryKeyRelatedField(queryset=Label.objects.all()),
|
||||
write_only=True,
|
||||
required=False,
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = Issue
|
||||
fields = "__all__"
|
||||
read_only_fields = [
|
||||
"workspace",
|
||||
"project",
|
||||
"created_by",
|
||||
"updated_by",
|
||||
"created_at",
|
||||
"updated_at",
|
||||
]
|
||||
|
||||
def to_representation(self, instance):
|
||||
data = super().to_representation(instance)
|
||||
data['assignees'] = [str(assignee.id) for assignee in instance.assignees.all()]
|
||||
data['labels'] = [str(label.id) for label in instance.labels.all()]
|
||||
return data
|
||||
|
||||
def validate(self, data):
|
||||
if (
|
||||
data.get("start_date", None) is not None
|
||||
and data.get("target_date", None) is not None
|
||||
and data.get("start_date", None) > data.get("target_date", None)
|
||||
):
|
||||
raise serializers.ValidationError("Start date cannot exceed target date")
|
||||
return data
|
||||
|
||||
def create(self, validated_data):
|
||||
assignees = validated_data.pop("assignees", None)
|
||||
labels = validated_data.pop("labels", None)
|
||||
|
||||
project_id = self.context["project_id"]
|
||||
workspace_id = self.context["workspace_id"]
|
||||
default_assignee_id = self.context["default_assignee_id"]
|
||||
|
||||
issue = Issue.objects.create(**validated_data, project_id=project_id)
|
||||
|
||||
# Issue Audit Users
|
||||
created_by_id = issue.created_by_id
|
||||
updated_by_id = issue.updated_by_id
|
||||
|
||||
if assignees is not None and len(assignees):
|
||||
IssueAssignee.objects.bulk_create(
|
||||
[
|
||||
IssueAssignee(
|
||||
assignee=user,
|
||||
issue=issue,
|
||||
project_id=project_id,
|
||||
workspace_id=workspace_id,
|
||||
created_by_id=created_by_id,
|
||||
updated_by_id=updated_by_id,
|
||||
)
|
||||
for user in assignees
|
||||
],
|
||||
batch_size=10,
|
||||
)
|
||||
else:
|
||||
# Then assign it to default assignee
|
||||
if default_assignee_id is not None:
|
||||
IssueAssignee.objects.create(
|
||||
assignee_id=default_assignee_id,
|
||||
issue=issue,
|
||||
project_id=project_id,
|
||||
workspace_id=workspace_id,
|
||||
created_by_id=created_by_id,
|
||||
updated_by_id=updated_by_id,
|
||||
)
|
||||
|
||||
if labels is not None and len(labels):
|
||||
IssueLabel.objects.bulk_create(
|
||||
[
|
||||
IssueLabel(
|
||||
label=label,
|
||||
issue=issue,
|
||||
project_id=project_id,
|
||||
workspace_id=workspace_id,
|
||||
created_by_id=created_by_id,
|
||||
updated_by_id=updated_by_id,
|
||||
)
|
||||
for label in labels
|
||||
],
|
||||
batch_size=10,
|
||||
)
|
||||
|
||||
return issue
|
||||
|
||||
def update(self, instance, validated_data):
|
||||
assignees = validated_data.pop("assignees", None)
|
||||
labels = validated_data.pop("labels", None)
|
||||
|
||||
# Related models
|
||||
project_id = instance.project_id
|
||||
workspace_id = instance.workspace_id
|
||||
created_by_id = instance.created_by_id
|
||||
updated_by_id = instance.updated_by_id
|
||||
|
||||
if assignees is not None:
|
||||
IssueAssignee.objects.filter(issue=instance).delete()
|
||||
IssueAssignee.objects.bulk_create(
|
||||
[
|
||||
IssueAssignee(
|
||||
assignee=user,
|
||||
issue=instance,
|
||||
project_id=project_id,
|
||||
workspace_id=workspace_id,
|
||||
created_by_id=created_by_id,
|
||||
updated_by_id=updated_by_id,
|
||||
)
|
||||
for user in assignees
|
||||
],
|
||||
batch_size=10,
|
||||
)
|
||||
|
||||
if labels is not None:
|
||||
IssueLabel.objects.filter(issue=instance).delete()
|
||||
IssueLabel.objects.bulk_create(
|
||||
[
|
||||
IssueLabel(
|
||||
label=label,
|
||||
issue=instance,
|
||||
project_id=project_id,
|
||||
workspace_id=workspace_id,
|
||||
created_by_id=created_by_id,
|
||||
updated_by_id=updated_by_id,
|
||||
)
|
||||
for label in labels
|
||||
],
|
||||
batch_size=10,
|
||||
)
|
||||
|
||||
# Time updation occues even when other related models are updated
|
||||
instance.updated_at = timezone.now()
|
||||
return super().update(instance, validated_data)
|
||||
|
||||
|
||||
class IssueReactionSerializer(BaseSerializer):
|
||||
|
||||
actor_detail = UserLiteSerializer(read_only=True, source="actor")
|
||||
|
||||
class Meta:
|
||||
model = IssueReaction
|
||||
fields = "__all__"
|
||||
read_only_fields = [
|
||||
"workspace",
|
||||
"project",
|
||||
"issue",
|
||||
"actor",
|
||||
]
|
||||
|
||||
|
||||
class CommentReactionSerializer(BaseSerializer):
|
||||
class Meta:
|
||||
model = CommentReaction
|
||||
fields = "__all__"
|
||||
read_only_fields = ["workspace", "project", "comment", "actor"]
|
||||
|
||||
|
||||
class IssueVoteSerializer(BaseSerializer):
|
||||
|
||||
actor_detail = UserLiteSerializer(read_only=True, source="actor")
|
||||
|
||||
class Meta:
|
||||
model = IssueVote
|
||||
fields = ["issue", "vote", "workspace", "project", "actor", "actor_detail"]
|
||||
read_only_fields = fields
|
||||
|
||||
|
||||
class IssuePublicSerializer(BaseSerializer):
|
||||
project_detail = ProjectLiteSerializer(read_only=True, source="project")
|
||||
state_detail = StateLiteSerializer(read_only=True, source="state")
|
||||
reactions = IssueReactionSerializer(read_only=True, many=True, source="issue_reactions")
|
||||
votes = IssueVoteSerializer(read_only=True, many=True)
|
||||
|
||||
class Meta:
|
||||
model = Issue
|
||||
fields = [
|
||||
"id",
|
||||
"name",
|
||||
"description_html",
|
||||
"sequence_id",
|
||||
"state",
|
||||
"state_detail",
|
||||
"project",
|
||||
"project_detail",
|
||||
"workspace",
|
||||
"priority",
|
||||
"target_date",
|
||||
"reactions",
|
||||
"votes",
|
||||
]
|
||||
read_only_fields = fields
|
||||
|
||||
|
||||
class LabelLiteSerializer(BaseSerializer):
|
||||
class Meta:
|
||||
model = Label
|
||||
fields = [
|
||||
"id",
|
||||
"name",
|
||||
"color",
|
||||
]
|
||||
|
||||
|
||||
|
||||
|
18
apiserver/plane/space/serializer/module.py
Normal file
18
apiserver/plane/space/serializer/module.py
Normal file
@ -0,0 +1,18 @@
|
||||
# Module imports
|
||||
from .base import BaseSerializer
|
||||
from plane.db.models import (
|
||||
Module,
|
||||
)
|
||||
|
||||
class ModuleBaseSerializer(BaseSerializer):
|
||||
class Meta:
|
||||
model = Module
|
||||
fields = "__all__"
|
||||
read_only_fields = [
|
||||
"workspace",
|
||||
"project",
|
||||
"created_by",
|
||||
"updated_by",
|
||||
"created_at",
|
||||
"updated_at",
|
||||
]
|
20
apiserver/plane/space/serializer/project.py
Normal file
20
apiserver/plane/space/serializer/project.py
Normal file
@ -0,0 +1,20 @@
|
||||
# Module imports
|
||||
from .base import BaseSerializer
|
||||
from plane.db.models import (
|
||||
Project,
|
||||
)
|
||||
|
||||
|
||||
class ProjectLiteSerializer(BaseSerializer):
|
||||
class Meta:
|
||||
model = Project
|
||||
fields = [
|
||||
"id",
|
||||
"identifier",
|
||||
"name",
|
||||
"cover_image",
|
||||
"icon_prop",
|
||||
"emoji",
|
||||
"description",
|
||||
]
|
||||
read_only_fields = fields
|
28
apiserver/plane/space/serializer/state.py
Normal file
28
apiserver/plane/space/serializer/state.py
Normal file
@ -0,0 +1,28 @@
|
||||
# Module imports
|
||||
from .base import BaseSerializer
|
||||
from plane.db.models import (
|
||||
State,
|
||||
)
|
||||
|
||||
|
||||
class StateSerializer(BaseSerializer):
|
||||
|
||||
class Meta:
|
||||
model = State
|
||||
fields = "__all__"
|
||||
read_only_fields = [
|
||||
"workspace",
|
||||
"project",
|
||||
]
|
||||
|
||||
|
||||
class StateLiteSerializer(BaseSerializer):
|
||||
class Meta:
|
||||
model = State
|
||||
fields = [
|
||||
"id",
|
||||
"name",
|
||||
"color",
|
||||
"group",
|
||||
]
|
||||
read_only_fields = fields
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user