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 .base import BaseSerializer
|
||||||
|
|
||||||
from plane.db.models import Estimate, EstimatePoint
|
from plane.db.models import Estimate, EstimatePoint
|
||||||
from plane.api.serializers import WorkspaceLiteSerializer, ProjectLiteSerializer
|
from plane.app.serializers import WorkspaceLiteSerializer, ProjectLiteSerializer
|
||||||
|
|
||||||
|
|
||||||
class EstimateSerializer(BaseSerializer):
|
class EstimateSerializer(BaseSerializer):
|
@ -1,5 +1,5 @@
|
|||||||
# Module imports
|
# Module imports
|
||||||
from plane.api.serializers import BaseSerializer
|
from plane.app.serializers import BaseSerializer
|
||||||
from plane.db.models import Integration, WorkspaceIntegration
|
from plane.db.models import Integration, WorkspaceIntegration
|
||||||
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
|||||||
# Module imports
|
# Module imports
|
||||||
from plane.api.serializers import BaseSerializer
|
from plane.app.serializers import BaseSerializer
|
||||||
from plane.db.models import (
|
from plane.db.models import (
|
||||||
GithubIssueSync,
|
GithubIssueSync,
|
||||||
GithubRepository,
|
GithubRepository,
|
@ -1,5 +1,5 @@
|
|||||||
# Module imports
|
# Module imports
|
||||||
from plane.api.serializers import BaseSerializer
|
from plane.app.serializers import BaseSerializer
|
||||||
from plane.db.models import SlackProjectSync
|
from plane.db.models import SlackProjectSync
|
||||||
|
|
||||||
|
|
@ -3,8 +3,8 @@ from rest_framework import serializers
|
|||||||
|
|
||||||
# Module imports
|
# Module imports
|
||||||
from .base import BaseSerializer, DynamicBaseSerializer
|
from .base import BaseSerializer, DynamicBaseSerializer
|
||||||
from plane.api.serializers.workspace import WorkSpaceSerializer, WorkspaceLiteSerializer
|
from plane.app.serializers.workspace import WorkspaceLiteSerializer
|
||||||
from plane.api.serializers.user import UserLiteSerializer, UserAdminLiteSerializer
|
from plane.app.serializers.user import UserLiteSerializer, UserAdminLiteSerializer
|
||||||
from plane.db.models import (
|
from plane.db.models import (
|
||||||
Project,
|
Project,
|
||||||
ProjectMember,
|
ProjectMember,
|
@ -1,7 +1,6 @@
|
|||||||
# Module imports
|
# Module imports
|
||||||
from .base import BaseSerializer
|
from .base import BaseSerializer
|
||||||
from .workspace import WorkspaceLiteSerializer
|
|
||||||
from .project import ProjectLiteSerializer
|
|
||||||
|
|
||||||
from plane.db.models import State
|
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 .notification import urlpatterns as notification_urls
|
||||||
from .page import urlpatterns as page_urls
|
from .page import urlpatterns as page_urls
|
||||||
from .project import urlpatterns as project_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 .search import urlpatterns as search_urls
|
||||||
from .state import urlpatterns as state_urls
|
from .state import urlpatterns as state_urls
|
||||||
from .user import urlpatterns as user_urls
|
from .user import urlpatterns as user_urls
|
||||||
@ -43,7 +42,6 @@ urlpatterns = [
|
|||||||
*notification_urls,
|
*notification_urls,
|
||||||
*page_urls,
|
*page_urls,
|
||||||
*project_urls,
|
*project_urls,
|
||||||
*public_board_urls,
|
|
||||||
*search_urls,
|
*search_urls,
|
||||||
*state_urls,
|
*state_urls,
|
||||||
*user_urls,
|
*user_urls,
|
@ -1,7 +1,7 @@
|
|||||||
from django.urls import path
|
from django.urls import path
|
||||||
|
|
||||||
|
|
||||||
from plane.api.views import (
|
from plane.app.views import (
|
||||||
AnalyticsEndpoint,
|
AnalyticsEndpoint,
|
||||||
AnalyticViewViewset,
|
AnalyticViewViewset,
|
||||||
SavedAnalyticEndpoint,
|
SavedAnalyticEndpoint,
|
@ -1,5 +1,5 @@
|
|||||||
from django.urls import path
|
from django.urls import path
|
||||||
from plane.api.views import ApiTokenEndpoint
|
from plane.app.views import ApiTokenEndpoint
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
# API Tokens
|
# API Tokens
|
@ -1,7 +1,7 @@
|
|||||||
from django.urls import path
|
from django.urls import path
|
||||||
|
|
||||||
|
|
||||||
from plane.api.views import (
|
from plane.app.views import (
|
||||||
FileAssetEndpoint,
|
FileAssetEndpoint,
|
||||||
UserAssetsEndpoint,
|
UserAssetsEndpoint,
|
||||||
)
|
)
|
@ -3,7 +3,7 @@ from django.urls import path
|
|||||||
from rest_framework_simplejwt.views import TokenRefreshView
|
from rest_framework_simplejwt.views import TokenRefreshView
|
||||||
|
|
||||||
|
|
||||||
from plane.api.views import (
|
from plane.app.views import (
|
||||||
# Authentication
|
# Authentication
|
||||||
SignUpEndpoint,
|
SignUpEndpoint,
|
||||||
SignInEndpoint,
|
SignInEndpoint,
|
@ -1,7 +1,7 @@
|
|||||||
from django.urls import path
|
from django.urls import path
|
||||||
|
|
||||||
|
|
||||||
from plane.api.views import ConfigurationEndpoint
|
from plane.app.views import ConfigurationEndpoint
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path(
|
path(
|
@ -1,7 +1,7 @@
|
|||||||
from django.urls import path
|
from django.urls import path
|
||||||
|
|
||||||
|
|
||||||
from plane.api.views import (
|
from plane.app.views import (
|
||||||
CycleViewSet,
|
CycleViewSet,
|
||||||
CycleIssueViewSet,
|
CycleIssueViewSet,
|
||||||
CycleDateCheckEndpoint,
|
CycleDateCheckEndpoint,
|
@ -1,7 +1,7 @@
|
|||||||
from django.urls import path
|
from django.urls import path
|
||||||
|
|
||||||
|
|
||||||
from plane.api.views import (
|
from plane.app.views import (
|
||||||
ProjectEstimatePointEndpoint,
|
ProjectEstimatePointEndpoint,
|
||||||
BulkEstimatePointEndpoint,
|
BulkEstimatePointEndpoint,
|
||||||
)
|
)
|
@ -1,9 +1,9 @@
|
|||||||
from django.urls import path
|
from django.urls import path
|
||||||
|
|
||||||
|
|
||||||
from plane.api.views import UnsplashEndpoint
|
from plane.app.views import UnsplashEndpoint
|
||||||
from plane.api.views import ReleaseNotesEndpoint
|
from plane.app.views import ReleaseNotesEndpoint
|
||||||
from plane.api.views import GPTIntegrationEndpoint
|
from plane.app.views import GPTIntegrationEndpoint
|
||||||
|
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
@ -1,7 +1,7 @@
|
|||||||
from django.urls import path
|
from django.urls import path
|
||||||
|
|
||||||
|
|
||||||
from plane.api.views import (
|
from plane.app.views import (
|
||||||
ServiceIssueImportSummaryEndpoint,
|
ServiceIssueImportSummaryEndpoint,
|
||||||
ImportServiceEndpoint,
|
ImportServiceEndpoint,
|
||||||
UpdateServiceImportStatusEndpoint,
|
UpdateServiceImportStatusEndpoint,
|
@ -1,7 +1,7 @@
|
|||||||
from django.urls import path
|
from django.urls import path
|
||||||
|
|
||||||
|
|
||||||
from plane.api.views import (
|
from plane.app.views import (
|
||||||
InboxViewSet,
|
InboxViewSet,
|
||||||
InboxIssueViewSet,
|
InboxIssueViewSet,
|
||||||
)
|
)
|
@ -1,7 +1,7 @@
|
|||||||
from django.urls import path
|
from django.urls import path
|
||||||
|
|
||||||
|
|
||||||
from plane.api.views import (
|
from plane.app.views import (
|
||||||
IntegrationViewSet,
|
IntegrationViewSet,
|
||||||
WorkspaceIntegrationViewSet,
|
WorkspaceIntegrationViewSet,
|
||||||
GithubRepositoriesEndpoint,
|
GithubRepositoriesEndpoint,
|
@ -1,7 +1,7 @@
|
|||||||
from django.urls import path
|
from django.urls import path
|
||||||
|
|
||||||
|
|
||||||
from plane.api.views import (
|
from plane.app.views import (
|
||||||
IssueViewSet,
|
IssueViewSet,
|
||||||
IssueListEndpoint,
|
IssueListEndpoint,
|
||||||
IssueListGroupedEndpoint,
|
IssueListGroupedEndpoint,
|
@ -1,7 +1,7 @@
|
|||||||
from django.urls import path
|
from django.urls import path
|
||||||
|
|
||||||
|
|
||||||
from plane.api.views import (
|
from plane.app.views import (
|
||||||
ModuleViewSet,
|
ModuleViewSet,
|
||||||
ModuleIssueViewSet,
|
ModuleIssueViewSet,
|
||||||
ModuleLinkViewSet,
|
ModuleLinkViewSet,
|
@ -1,7 +1,7 @@
|
|||||||
from django.urls import path
|
from django.urls import path
|
||||||
|
|
||||||
|
|
||||||
from plane.api.views import (
|
from plane.app.views import (
|
||||||
NotificationViewSet,
|
NotificationViewSet,
|
||||||
UnreadNotificationEndpoint,
|
UnreadNotificationEndpoint,
|
||||||
MarkAllReadNotificationViewSet,
|
MarkAllReadNotificationViewSet,
|
@ -1,7 +1,7 @@
|
|||||||
from django.urls import path
|
from django.urls import path
|
||||||
|
|
||||||
|
|
||||||
from plane.api.views import (
|
from plane.app.views import (
|
||||||
PageViewSet,
|
PageViewSet,
|
||||||
PageFavoriteViewSet,
|
PageFavoriteViewSet,
|
||||||
PageLogEndpoint,
|
PageLogEndpoint,
|
@ -1,6 +1,6 @@
|
|||||||
from django.urls import path
|
from django.urls import path
|
||||||
|
|
||||||
from plane.api.views import (
|
from plane.app.views import (
|
||||||
ProjectViewSet,
|
ProjectViewSet,
|
||||||
ProjectInvitationsViewset,
|
ProjectInvitationsViewset,
|
||||||
ProjectMemberViewSet,
|
ProjectMemberViewSet,
|
||||||
@ -10,8 +10,9 @@ from plane.api.views import (
|
|||||||
ProjectUserViewsEndpoint,
|
ProjectUserViewsEndpoint,
|
||||||
ProjectIdentifierEndpoint,
|
ProjectIdentifierEndpoint,
|
||||||
ProjectFavoritesViewSet,
|
ProjectFavoritesViewSet,
|
||||||
ProjectPublicCoverImagesEndpoint,
|
|
||||||
UserProjectInvitationsViewset,
|
UserProjectInvitationsViewset,
|
||||||
|
ProjectPublicCoverImagesEndpoint,
|
||||||
|
ProjectDeployBoardViewSet,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -147,4 +148,25 @@ urlpatterns = [
|
|||||||
ProjectPublicCoverImagesEndpoint.as_view(),
|
ProjectPublicCoverImagesEndpoint.as_view(),
|
||||||
name="project-covers",
|
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 django.urls import path
|
||||||
|
|
||||||
|
|
||||||
from plane.api.views import (
|
from plane.app.views import (
|
||||||
GlobalSearchEndpoint,
|
GlobalSearchEndpoint,
|
||||||
IssueSearchEndpoint,
|
IssueSearchEndpoint,
|
||||||
)
|
)
|
@ -1,7 +1,7 @@
|
|||||||
from django.urls import path
|
from django.urls import path
|
||||||
|
|
||||||
|
|
||||||
from plane.api.views import StateViewSet
|
from plane.app.views import StateViewSet
|
||||||
|
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
@ -1,6 +1,6 @@
|
|||||||
from django.urls import path
|
from django.urls import path
|
||||||
|
|
||||||
from plane.api.views import (
|
from plane.app.views import (
|
||||||
## User
|
## User
|
||||||
UserEndpoint,
|
UserEndpoint,
|
||||||
UpdateUserOnBoardedEndpoint,
|
UpdateUserOnBoardedEndpoint,
|
@ -1,7 +1,7 @@
|
|||||||
from django.urls import path
|
from django.urls import path
|
||||||
|
|
||||||
|
|
||||||
from plane.api.views import (
|
from plane.app.views import (
|
||||||
IssueViewViewSet,
|
IssueViewViewSet,
|
||||||
GlobalViewViewSet,
|
GlobalViewViewSet,
|
||||||
GlobalViewIssuesViewSet,
|
GlobalViewIssuesViewSet,
|
@ -1,6 +1,6 @@
|
|||||||
from django.urls import path
|
from django.urls import path
|
||||||
|
|
||||||
from plane.api.views import (
|
from plane.app.views import (
|
||||||
WebhookEndpoint,
|
WebhookEndpoint,
|
||||||
WebhookLogsEndpoint,
|
WebhookLogsEndpoint,
|
||||||
WebhookSecretRegenerateEndpoint,
|
WebhookSecretRegenerateEndpoint,
|
@ -1,7 +1,7 @@
|
|||||||
from django.urls import path
|
from django.urls import path
|
||||||
|
|
||||||
|
|
||||||
from plane.api.views import (
|
from plane.app.views import (
|
||||||
UserWorkspaceInvitationsViewSet,
|
UserWorkspaceInvitationsViewSet,
|
||||||
WorkSpaceViewSet,
|
WorkSpaceViewSet,
|
||||||
WorkspaceJoinEndpoint,
|
WorkspaceJoinEndpoint,
|
@ -4,7 +4,7 @@ from rest_framework_simplejwt.views import TokenRefreshView
|
|||||||
|
|
||||||
# Create your urls here.
|
# Create your urls here.
|
||||||
|
|
||||||
from plane.api.views import (
|
from plane.app.views import (
|
||||||
# Authentication
|
# Authentication
|
||||||
SignUpEndpoint,
|
SignUpEndpoint,
|
||||||
SignInEndpoint,
|
SignInEndpoint,
|
@ -9,10 +9,8 @@ from .project import (
|
|||||||
ProjectUserViewsEndpoint,
|
ProjectUserViewsEndpoint,
|
||||||
ProjectMemberUserEndpoint,
|
ProjectMemberUserEndpoint,
|
||||||
ProjectFavoritesViewSet,
|
ProjectFavoritesViewSet,
|
||||||
ProjectDeployBoardViewSet,
|
|
||||||
ProjectDeployBoardPublicSettingsEndpoint,
|
|
||||||
WorkspaceProjectDeployBoardEndpoint,
|
|
||||||
ProjectPublicCoverImagesEndpoint,
|
ProjectPublicCoverImagesEndpoint,
|
||||||
|
ProjectDeployBoardViewSet,
|
||||||
)
|
)
|
||||||
from .user import (
|
from .user import (
|
||||||
UserEndpoint,
|
UserEndpoint,
|
||||||
@ -80,15 +78,9 @@ from .issue import (
|
|||||||
IssueAttachmentEndpoint,
|
IssueAttachmentEndpoint,
|
||||||
IssueArchiveViewSet,
|
IssueArchiveViewSet,
|
||||||
IssueSubscriberViewSet,
|
IssueSubscriberViewSet,
|
||||||
IssueCommentPublicViewSet,
|
|
||||||
CommentReactionViewSet,
|
CommentReactionViewSet,
|
||||||
IssueReactionViewSet,
|
IssueReactionViewSet,
|
||||||
IssueReactionPublicViewSet,
|
|
||||||
CommentReactionPublicViewSet,
|
|
||||||
IssueVotePublicViewSet,
|
|
||||||
IssueRelationViewSet,
|
IssueRelationViewSet,
|
||||||
IssueRetrievePublicEndpoint,
|
|
||||||
ProjectIssuesPublicEndpoint,
|
|
||||||
IssueDraftViewSet,
|
IssueDraftViewSet,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -156,7 +148,7 @@ from .estimate import (
|
|||||||
BulkEstimatePointEndpoint,
|
BulkEstimatePointEndpoint,
|
||||||
)
|
)
|
||||||
|
|
||||||
from .inbox import InboxViewSet, InboxIssueViewSet, InboxIssuePublicViewSet
|
from .inbox import InboxViewSet, InboxIssueViewSet
|
||||||
|
|
||||||
from .analytic import (
|
from .analytic import (
|
||||||
AnalyticsEndpoint,
|
AnalyticsEndpoint,
|
@ -5,13 +5,12 @@ from django.db.models.functions import ExtractMonth
|
|||||||
# Third party imports
|
# Third party imports
|
||||||
from rest_framework import status
|
from rest_framework import status
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from sentry_sdk import capture_exception
|
|
||||||
|
|
||||||
# Module imports
|
# Module imports
|
||||||
from plane.api.views import BaseAPIView, BaseViewSet
|
from plane.app.views import BaseAPIView, BaseViewSet
|
||||||
from plane.api.permissions import WorkSpaceAdminPermission
|
from plane.app.permissions import WorkSpaceAdminPermission
|
||||||
from plane.db.models import Issue, AnalyticView, Workspace, State, Label
|
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.utils.analytics_plot import build_graph_plot
|
||||||
from plane.bgtasks.analytic_plot_export import analytic_export_task
|
from plane.bgtasks.analytic_plot_export import analytic_export_task
|
||||||
from plane.utils.issue_filters import issue_filters
|
from plane.utils.issue_filters import issue_filters
|
@ -8,8 +8,8 @@ from rest_framework import status
|
|||||||
# Module import
|
# Module import
|
||||||
from .base import BaseAPIView
|
from .base import BaseAPIView
|
||||||
from plane.db.models import APIToken, Workspace
|
from plane.db.models import APIToken, Workspace
|
||||||
from plane.api.serializers import APITokenSerializer, APITokenReadSerializer
|
from plane.app.serializers import APITokenSerializer, APITokenReadSerializer
|
||||||
from plane.api.permissions import WorkspaceOwnerPermission
|
from plane.app.permissions import WorkspaceOwnerPermission
|
||||||
|
|
||||||
|
|
||||||
class ApiTokenEndpoint(BaseAPIView):
|
class ApiTokenEndpoint(BaseAPIView):
|
@ -2,12 +2,11 @@
|
|||||||
from rest_framework import status
|
from rest_framework import status
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from rest_framework.parsers import MultiPartParser, FormParser
|
from rest_framework.parsers import MultiPartParser, FormParser
|
||||||
from sentry_sdk import capture_exception
|
|
||||||
from django.conf import settings
|
|
||||||
# Module imports
|
# Module imports
|
||||||
from .base import BaseAPIView
|
from .base import BaseAPIView
|
||||||
from plane.db.models import FileAsset, Workspace
|
from plane.db.models import FileAsset, Workspace
|
||||||
from plane.api.serializers import FileAssetSerializer
|
from plane.app.serializers import FileAssetSerializer
|
||||||
|
|
||||||
|
|
||||||
class FileAssetEndpoint(BaseAPIView):
|
class FileAssetEndpoint(BaseAPIView):
|
@ -21,7 +21,7 @@ from sentry_sdk import capture_exception
|
|||||||
|
|
||||||
## Module imports
|
## Module imports
|
||||||
from . import BaseAPIView
|
from . import BaseAPIView
|
||||||
from plane.api.serializers import (
|
from plane.app.serializers import (
|
||||||
ChangePasswordSerializer,
|
ChangePasswordSerializer,
|
||||||
ResetPasswordSerializer,
|
ResetPasswordSerializer,
|
||||||
)
|
)
|
@ -5,6 +5,7 @@ import string
|
|||||||
import json
|
import json
|
||||||
import requests
|
import requests
|
||||||
from requests.exceptions import RequestException
|
from requests.exceptions import RequestException
|
||||||
|
|
||||||
# Django imports
|
# Django imports
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
@ -8,7 +8,6 @@ from django.conf import settings
|
|||||||
from rest_framework.permissions import AllowAny
|
from rest_framework.permissions import AllowAny
|
||||||
from rest_framework import status
|
from rest_framework import status
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from sentry_sdk import capture_exception
|
|
||||||
|
|
||||||
# Module imports
|
# Module imports
|
||||||
from .base import BaseAPIView
|
from .base import BaseAPIView
|
@ -20,18 +20,17 @@ from django.views.decorators.gzip import gzip_page
|
|||||||
# Third party imports
|
# Third party imports
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from rest_framework import status
|
from rest_framework import status
|
||||||
from sentry_sdk import capture_exception
|
|
||||||
|
|
||||||
# Module imports
|
# Module imports
|
||||||
from . import BaseViewSet, BaseAPIView, WebhookMixin
|
from . import BaseViewSet, BaseAPIView, WebhookMixin
|
||||||
from plane.api.serializers import (
|
from plane.app.serializers import (
|
||||||
CycleSerializer,
|
CycleSerializer,
|
||||||
CycleIssueSerializer,
|
CycleIssueSerializer,
|
||||||
CycleFavoriteSerializer,
|
CycleFavoriteSerializer,
|
||||||
IssueStateSerializer,
|
IssueStateSerializer,
|
||||||
CycleWriteSerializer,
|
CycleWriteSerializer,
|
||||||
)
|
)
|
||||||
from plane.api.permissions import ProjectEntityPermission
|
from plane.app.permissions import ProjectEntityPermission
|
||||||
from plane.db.models import (
|
from plane.db.models import (
|
||||||
User,
|
User,
|
||||||
Cycle,
|
Cycle,
|
@ -1,13 +1,12 @@
|
|||||||
# Third party imports
|
# Third party imports
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from rest_framework import status
|
from rest_framework import status
|
||||||
from sentry_sdk import capture_exception
|
|
||||||
|
|
||||||
# Module imports
|
# Module imports
|
||||||
from .base import BaseViewSet, BaseAPIView
|
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.db.models import Project, Estimate, EstimatePoint
|
||||||
from plane.api.serializers import (
|
from plane.app.serializers import (
|
||||||
EstimateSerializer,
|
EstimateSerializer,
|
||||||
EstimatePointSerializer,
|
EstimatePointSerializer,
|
||||||
EstimateReadSerializer,
|
EstimateReadSerializer,
|
@ -1,15 +1,14 @@
|
|||||||
# Third Party imports
|
# Third Party imports
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from rest_framework import status
|
from rest_framework import status
|
||||||
from sentry_sdk import capture_exception
|
|
||||||
|
|
||||||
# Module imports
|
# Module imports
|
||||||
from . import BaseAPIView
|
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.bgtasks.export_task import issue_export_task
|
||||||
from plane.db.models import Project, ExporterHistory, Workspace
|
from plane.db.models import Project, ExporterHistory, Workspace
|
||||||
|
|
||||||
from plane.api.serializers import ExporterHistorySerializer
|
from plane.app.serializers import ExporterHistorySerializer
|
||||||
|
|
||||||
|
|
||||||
class ExportIssuesEndpoint(BaseAPIView):
|
class ExportIssuesEndpoint(BaseAPIView):
|
@ -5,17 +5,15 @@ import requests
|
|||||||
from openai import OpenAI
|
from openai import OpenAI
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from rest_framework import status
|
from rest_framework import status
|
||||||
from rest_framework.permissions import AllowAny
|
|
||||||
from sentry_sdk import capture_exception
|
|
||||||
|
|
||||||
# Django imports
|
# Django imports
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
|
||||||
# Module imports
|
# Module imports
|
||||||
from .base import BaseAPIView
|
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.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.utils.integrations.github import get_release_notes
|
||||||
from plane.license.models import InstanceConfiguration
|
from plane.license.models import InstanceConfiguration
|
||||||
from plane.license.utils.instance_value import get_configuration_value
|
from plane.license.utils.instance_value import get_configuration_value
|
@ -4,13 +4,12 @@ import uuid
|
|||||||
# Third party imports
|
# Third party imports
|
||||||
from rest_framework import status
|
from rest_framework import status
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from sentry_sdk import capture_exception
|
|
||||||
|
|
||||||
# Django imports
|
# Django imports
|
||||||
from django.db.models import Max, Q
|
from django.db.models import Max, Q
|
||||||
|
|
||||||
# Module imports
|
# Module imports
|
||||||
from plane.api.views import BaseAPIView
|
from plane.app.views import BaseAPIView
|
||||||
from plane.db.models import (
|
from plane.db.models import (
|
||||||
WorkspaceIntegration,
|
WorkspaceIntegration,
|
||||||
Importer,
|
Importer,
|
||||||
@ -30,7 +29,7 @@ from plane.db.models import (
|
|||||||
ModuleIssue,
|
ModuleIssue,
|
||||||
Label,
|
Label,
|
||||||
)
|
)
|
||||||
from plane.api.serializers import (
|
from plane.app.serializers import (
|
||||||
ImporterSerializer,
|
ImporterSerializer,
|
||||||
IssueFlatSerializer,
|
IssueFlatSerializer,
|
||||||
ModuleSerializer,
|
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.utils.importers.jira import jira_project_issue_summary
|
||||||
from plane.bgtasks.importer_task import service_importer
|
from plane.bgtasks.importer_task import service_importer
|
||||||
from plane.utils.html_processor import strip_tags
|
from plane.utils.html_processor import strip_tags
|
||||||
from plane.api.permissions import WorkSpaceAdminPermission
|
from plane.app.permissions import WorkSpaceAdminPermission
|
||||||
|
|
||||||
|
|
||||||
class ServiceIssueImportSummaryEndpoint(BaseAPIView):
|
class ServiceIssueImportSummaryEndpoint(BaseAPIView):
|
@ -9,11 +9,10 @@ from django.core.serializers.json import DjangoJSONEncoder
|
|||||||
# Third party imports
|
# Third party imports
|
||||||
from rest_framework import status
|
from rest_framework import status
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from sentry_sdk import capture_exception
|
|
||||||
|
|
||||||
# Module imports
|
# Module imports
|
||||||
from .base import BaseViewSet
|
from .base import BaseViewSet
|
||||||
from plane.api.permissions import ProjectBasePermission, ProjectLitePermission
|
from plane.app.permissions import ProjectBasePermission, ProjectLitePermission
|
||||||
from plane.db.models import (
|
from plane.db.models import (
|
||||||
Inbox,
|
Inbox,
|
||||||
InboxIssue,
|
InboxIssue,
|
||||||
@ -22,9 +21,8 @@ from plane.db.models import (
|
|||||||
IssueLink,
|
IssueLink,
|
||||||
IssueAttachment,
|
IssueAttachment,
|
||||||
ProjectMember,
|
ProjectMember,
|
||||||
ProjectDeployBoard,
|
|
||||||
)
|
)
|
||||||
from plane.api.serializers import (
|
from plane.app.serializers import (
|
||||||
IssueSerializer,
|
IssueSerializer,
|
||||||
InboxSerializer,
|
InboxSerializer,
|
||||||
InboxIssueSerializer,
|
InboxIssueSerializer,
|
||||||
@ -359,253 +357,3 @@ class InboxIssueViewSet(BaseViewSet):
|
|||||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
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
|
from sentry_sdk import capture_exception
|
||||||
|
|
||||||
# Module imports
|
# Module imports
|
||||||
from plane.api.views import BaseViewSet
|
from plane.app.views import BaseViewSet
|
||||||
from plane.db.models import (
|
from plane.db.models import (
|
||||||
Integration,
|
Integration,
|
||||||
WorkspaceIntegration,
|
WorkspaceIntegration,
|
||||||
@ -19,12 +19,12 @@ from plane.db.models import (
|
|||||||
WorkspaceMember,
|
WorkspaceMember,
|
||||||
APIToken,
|
APIToken,
|
||||||
)
|
)
|
||||||
from plane.api.serializers import IntegrationSerializer, WorkspaceIntegrationSerializer
|
from plane.app.serializers import IntegrationSerializer, WorkspaceIntegrationSerializer
|
||||||
from plane.utils.integrations.github import (
|
from plane.utils.integrations.github import (
|
||||||
get_github_metadata,
|
get_github_metadata,
|
||||||
delete_github_installation,
|
delete_github_installation,
|
||||||
)
|
)
|
||||||
from plane.api.permissions import WorkSpaceAdminPermission
|
from plane.app.permissions import WorkSpaceAdminPermission
|
||||||
from plane.utils.integrations.slack import slack_oauth
|
from plane.utils.integrations.slack import slack_oauth
|
||||||
|
|
||||||
class IntegrationViewSet(BaseViewSet):
|
class IntegrationViewSet(BaseViewSet):
|
@ -4,7 +4,7 @@ from rest_framework.response import Response
|
|||||||
from sentry_sdk import capture_exception
|
from sentry_sdk import capture_exception
|
||||||
|
|
||||||
# Module imports
|
# Module imports
|
||||||
from plane.api.views import BaseViewSet, BaseAPIView
|
from plane.app.views import BaseViewSet, BaseAPIView
|
||||||
from plane.db.models import (
|
from plane.db.models import (
|
||||||
GithubIssueSync,
|
GithubIssueSync,
|
||||||
GithubRepositorySync,
|
GithubRepositorySync,
|
||||||
@ -15,13 +15,13 @@ from plane.db.models import (
|
|||||||
GithubCommentSync,
|
GithubCommentSync,
|
||||||
Project,
|
Project,
|
||||||
)
|
)
|
||||||
from plane.api.serializers import (
|
from plane.app.serializers import (
|
||||||
GithubIssueSyncSerializer,
|
GithubIssueSyncSerializer,
|
||||||
GithubRepositorySyncSerializer,
|
GithubRepositorySyncSerializer,
|
||||||
GithubCommentSyncSerializer,
|
GithubCommentSyncSerializer,
|
||||||
)
|
)
|
||||||
from plane.utils.integrations.github import get_github_repos
|
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):
|
class GithubRepositoriesEndpoint(BaseAPIView):
|
@ -7,10 +7,10 @@ from rest_framework.response import Response
|
|||||||
from sentry_sdk import capture_exception
|
from sentry_sdk import capture_exception
|
||||||
|
|
||||||
# Module imports
|
# 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.db.models import SlackProjectSync, WorkspaceIntegration, ProjectMember
|
||||||
from plane.api.serializers import SlackProjectSyncSerializer
|
from plane.app.serializers import SlackProjectSyncSerializer
|
||||||
from plane.api.permissions import ProjectBasePermission, ProjectEntityPermission
|
from plane.app.permissions import ProjectBasePermission, ProjectEntityPermission
|
||||||
from plane.utils.integrations.slack import slack_oauth
|
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.response import Response
|
||||||
from rest_framework import status
|
from rest_framework import status
|
||||||
from rest_framework.parsers import MultiPartParser, FormParser
|
from rest_framework.parsers import MultiPartParser, FormParser
|
||||||
from rest_framework.permissions import AllowAny, IsAuthenticated
|
|
||||||
from sentry_sdk import capture_exception
|
|
||||||
|
|
||||||
# Module imports
|
# Module imports
|
||||||
from . import BaseViewSet, BaseAPIView, WebhookMixin
|
from . import BaseViewSet, BaseAPIView, WebhookMixin
|
||||||
from plane.api.serializers import (
|
from plane.app.serializers import (
|
||||||
IssueCreateSerializer,
|
IssueCreateSerializer,
|
||||||
IssueActivitySerializer,
|
IssueActivitySerializer,
|
||||||
IssueCommentSerializer,
|
IssueCommentSerializer,
|
||||||
@ -54,7 +52,7 @@ from plane.api.serializers import (
|
|||||||
RelatedIssueSerializer,
|
RelatedIssueSerializer,
|
||||||
IssuePublicSerializer,
|
IssuePublicSerializer,
|
||||||
)
|
)
|
||||||
from plane.api.permissions import (
|
from plane.app.permissions import (
|
||||||
ProjectEntityPermission,
|
ProjectEntityPermission,
|
||||||
WorkSpaceAdminPermission,
|
WorkSpaceAdminPermission,
|
||||||
ProjectMemberPermission,
|
ProjectMemberPermission,
|
||||||
@ -1462,432 +1460,6 @@ class CommentReactionViewSet(BaseViewSet):
|
|||||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
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):
|
class IssueRelationViewSet(BaseViewSet):
|
||||||
serializer_class = IssueRelationSerializer
|
serializer_class = IssueRelationSerializer
|
||||||
model = IssueRelation
|
model = IssueRelation
|
||||||
@ -1973,182 +1545,6 @@ class IssueRelationViewSet(BaseViewSet):
|
|||||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
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):
|
class IssueDraftViewSet(BaseViewSet):
|
||||||
permission_classes = [
|
permission_classes = [
|
||||||
ProjectEntityPermission,
|
ProjectEntityPermission,
|
@ -3,7 +3,6 @@ import json
|
|||||||
|
|
||||||
# Django Imports
|
# Django Imports
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from django.db import IntegrityError
|
|
||||||
from django.db.models import Prefetch, F, OuterRef, Func, Exists, Count, Q
|
from django.db.models import Prefetch, F, OuterRef, Func, Exists, Count, Q
|
||||||
from django.core import serializers
|
from django.core import serializers
|
||||||
from django.utils.decorators import method_decorator
|
from django.utils.decorators import method_decorator
|
||||||
@ -12,11 +11,10 @@ from django.views.decorators.gzip import gzip_page
|
|||||||
# Third party imports
|
# Third party imports
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from rest_framework import status
|
from rest_framework import status
|
||||||
from sentry_sdk import capture_exception
|
|
||||||
|
|
||||||
# Module imports
|
# Module imports
|
||||||
from . import BaseViewSet, BaseAPIView, WebhookMixin
|
from . import BaseViewSet, BaseAPIView, WebhookMixin
|
||||||
from plane.api.serializers import (
|
from plane.app.serializers import (
|
||||||
ModuleWriteSerializer,
|
ModuleWriteSerializer,
|
||||||
ModuleSerializer,
|
ModuleSerializer,
|
||||||
ModuleIssueSerializer,
|
ModuleIssueSerializer,
|
||||||
@ -24,7 +22,7 @@ from plane.api.serializers import (
|
|||||||
ModuleFavoriteSerializer,
|
ModuleFavoriteSerializer,
|
||||||
IssueStateSerializer,
|
IssueStateSerializer,
|
||||||
)
|
)
|
||||||
from plane.api.permissions import ProjectEntityPermission
|
from plane.app.permissions import ProjectEntityPermission
|
||||||
from plane.db.models import (
|
from plane.db.models import (
|
||||||
Module,
|
Module,
|
||||||
ModuleIssue,
|
ModuleIssue,
|
@ -5,7 +5,6 @@ from django.utils import timezone
|
|||||||
# Third party imports
|
# Third party imports
|
||||||
from rest_framework import status
|
from rest_framework import status
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from sentry_sdk import capture_exception
|
|
||||||
from plane.utils.paginator import BasePaginator
|
from plane.utils.paginator import BasePaginator
|
||||||
|
|
||||||
# Module imports
|
# Module imports
|
||||||
@ -17,7 +16,7 @@ from plane.db.models import (
|
|||||||
Issue,
|
Issue,
|
||||||
WorkspaceMember,
|
WorkspaceMember,
|
||||||
)
|
)
|
||||||
from plane.api.serializers import NotificationSerializer
|
from plane.app.serializers import NotificationSerializer
|
||||||
|
|
||||||
|
|
||||||
class NotificationViewSet(BaseViewSet, BasePaginator):
|
class NotificationViewSet(BaseViewSet, BasePaginator):
|
@ -29,7 +29,6 @@ from plane.db.models import (
|
|||||||
ProjectMemberInvite,
|
ProjectMemberInvite,
|
||||||
ProjectMember,
|
ProjectMember,
|
||||||
)
|
)
|
||||||
from plane.api.serializers import UserSerializer
|
|
||||||
from .base import BaseAPIView
|
from .base import BaseAPIView
|
||||||
|
|
||||||
|
|
@ -3,26 +3,18 @@ from datetime import timedelta, date, datetime
|
|||||||
|
|
||||||
# Django imports
|
# Django imports
|
||||||
from django.db import connection
|
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 import timezone
|
||||||
from django.utils.decorators import method_decorator
|
from django.utils.decorators import method_decorator
|
||||||
from django.views.decorators.gzip import gzip_page
|
from django.views.decorators.gzip import gzip_page
|
||||||
from django.db.models import (
|
|
||||||
OuterRef,
|
|
||||||
Func,
|
|
||||||
F,
|
|
||||||
Q,
|
|
||||||
Exists,
|
|
||||||
)
|
|
||||||
|
|
||||||
# Third party imports
|
# Third party imports
|
||||||
from rest_framework import status
|
from rest_framework import status
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from sentry_sdk import capture_exception
|
|
||||||
|
|
||||||
# Module imports
|
# Module imports
|
||||||
from .base import BaseViewSet, BaseAPIView
|
from .base import BaseViewSet, BaseAPIView
|
||||||
from plane.api.permissions import ProjectEntityPermission
|
from plane.app.permissions import ProjectEntityPermission
|
||||||
from plane.db.models import (
|
from plane.db.models import (
|
||||||
Page,
|
Page,
|
||||||
PageFavorite,
|
PageFavorite,
|
||||||
@ -31,7 +23,7 @@ from plane.db.models import (
|
|||||||
IssueActivity,
|
IssueActivity,
|
||||||
PageLog,
|
PageLog,
|
||||||
)
|
)
|
||||||
from plane.api.serializers import (
|
from plane.app.serializers import (
|
||||||
PageSerializer,
|
PageSerializer,
|
||||||
PageFavoriteSerializer,
|
PageFavoriteSerializer,
|
||||||
PageLogSerializer,
|
PageLogSerializer,
|
||||||
@ -87,15 +79,10 @@ class PageViewSet(BaseViewSet):
|
|||||||
.annotate(is_favorite=Exists(subquery))
|
.annotate(is_favorite=Exists(subquery))
|
||||||
.order_by(self.request.GET.get("order_by", "-created_at"))
|
.order_by(self.request.GET.get("order_by", "-created_at"))
|
||||||
.prefetch_related("labels")
|
.prefetch_related("labels")
|
||||||
.order_by("-is_favorite","-created_at")
|
.order_by("-is_favorite", "-created_at")
|
||||||
.distinct()
|
.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):
|
def create(self, request, slug, project_id):
|
||||||
serializer = PageSerializer(
|
serializer = PageSerializer(
|
||||||
data=request.data,
|
data=request.data,
|
||||||
@ -148,10 +135,8 @@ class PageViewSet(BaseViewSet):
|
|||||||
status=status.HTTP_400_BAD_REQUEST,
|
status=status.HTTP_400_BAD_REQUEST,
|
||||||
)
|
)
|
||||||
|
|
||||||
def lock(self, request, slug, project_id, pk):
|
def lock(self, request, slug, project_id, page_id):
|
||||||
page = Page.objects.filter(
|
page = Page.objects.get(pk=page_id, workspace__slug=slug, project_id=project_id)
|
||||||
pk=pk, workspace__slug=slug, project_id=project_id
|
|
||||||
)
|
|
||||||
|
|
||||||
# only the owner can lock the page
|
# only the owner can lock the page
|
||||||
if request.user.id != page.owned_by_id:
|
if request.user.id != page.owned_by_id:
|
||||||
@ -163,8 +148,8 @@ class PageViewSet(BaseViewSet):
|
|||||||
page.save()
|
page.save()
|
||||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
def unlock(self, request, slug, project_id, pk):
|
def unlock(self, request, slug, project_id, page_id):
|
||||||
page = Page.objects.get(pk=pk, workspace__slug=slug, project_id=project_id)
|
page = Page.objects.get(pk=page_id, workspace__slug=slug, project_id=project_id)
|
||||||
|
|
||||||
# only the owner can unlock the page
|
# only the owner can unlock the page
|
||||||
if request.user.id != page.owned_by_id:
|
if request.user.id != page.owned_by_id:
|
||||||
@ -242,27 +227,31 @@ class PageViewSet(BaseViewSet):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def archive(self, request, slug, project_id, page_id):
|
def archive(self, request, slug, project_id, page_id):
|
||||||
_ = Page.objects.get(
|
page = Page.objects.get(pk=page_id, workspace__slug=slug, project_id=project_id)
|
||||||
project_id=project_id,
|
|
||||||
owned_by_id=request.user.id,
|
if page.owned_by_id != request.user.id:
|
||||||
workspace__slug=slug,
|
return Response(
|
||||||
pk=page_id,
|
{"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())
|
unarchive_archive_page_and_descendants(page_id, datetime.now())
|
||||||
|
|
||||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
def unarchive(self, request, slug, project_id, page_id):
|
def unarchive(self, request, slug, project_id, page_id):
|
||||||
page = Page.objects.get(
|
page = Page.objects.get(pk=page_id, workspace__slug=slug, project_id=project_id)
|
||||||
project_id=project_id,
|
|
||||||
owned_by_id=request.user.id,
|
|
||||||
workspace__slug=slug,
|
|
||||||
pk=page_id,
|
|
||||||
)
|
|
||||||
|
|
||||||
page.parent = None
|
if page.owned_by_id != request.user.id:
|
||||||
page.save()
|
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)
|
unarchive_archive_page_and_descendants(page_id, None)
|
||||||
|
|
||||||
@ -275,20 +264,13 @@ class PageViewSet(BaseViewSet):
|
|||||||
workspace__slug=slug,
|
workspace__slug=slug,
|
||||||
)
|
)
|
||||||
.filter(archived_at__isnull=False)
|
.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(
|
return Response(
|
||||||
PageSerializer(pages, many=True).data, status=status.HTTP_200_OK
|
PageSerializer(pages, many=True).data, status=status.HTTP_200_OK
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class PageFavoriteViewSet(BaseViewSet):
|
class PageFavoriteViewSet(BaseViewSet):
|
||||||
permission_classes = [
|
permission_classes = [
|
||||||
ProjectEntityPermission,
|
ProjectEntityPermission,
|
||||||
@ -410,11 +392,9 @@ class SubPagesEndpoint(BaseAPIView):
|
|||||||
workspace__slug=slug,
|
workspace__slug=slug,
|
||||||
entity_name__in=["forward_link", "back_link"],
|
entity_name__in=["forward_link", "back_link"],
|
||||||
)
|
)
|
||||||
.filter(archived_at__isnull=True)
|
|
||||||
.select_related("project")
|
.select_related("project")
|
||||||
.select_related("workspace")
|
.select_related("workspace")
|
||||||
)
|
)
|
||||||
return Response(
|
return Response(
|
||||||
SubPageSerializer(pages, many=True).data, status=status.HTTP_200_OK
|
SubPageSerializer(pages, many=True).data, status=status.HTTP_200_OK
|
||||||
)
|
)
|
||||||
|
|
@ -27,7 +27,7 @@ from rest_framework.permissions import AllowAny
|
|||||||
|
|
||||||
# Module imports
|
# Module imports
|
||||||
from .base import BaseViewSet, BaseAPIView, WebhookMixin
|
from .base import BaseViewSet, BaseAPIView, WebhookMixin
|
||||||
from plane.api.serializers import (
|
from plane.app.serializers import (
|
||||||
ProjectSerializer,
|
ProjectSerializer,
|
||||||
ProjectListSerializer,
|
ProjectListSerializer,
|
||||||
ProjectMemberSerializer,
|
ProjectMemberSerializer,
|
||||||
@ -38,12 +38,9 @@ from plane.api.serializers import (
|
|||||||
ProjectMemberAdminSerializer,
|
ProjectMemberAdminSerializer,
|
||||||
)
|
)
|
||||||
|
|
||||||
from plane.api.permissions import (
|
from plane.app.permissions import (
|
||||||
WorkspaceUserPermission,
|
|
||||||
ProjectBasePermission,
|
ProjectBasePermission,
|
||||||
ProjectEntityPermission,
|
|
||||||
ProjectMemberPermission,
|
ProjectMemberPermission,
|
||||||
ProjectLitePermission,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
from plane.db.models import (
|
from plane.db.models import (
|
||||||
@ -965,6 +962,37 @@ class ProjectFavoritesViewSet(BaseViewSet):
|
|||||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
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):
|
class ProjectDeployBoardViewSet(BaseViewSet):
|
||||||
permission_classes = [
|
permission_classes = [
|
||||||
ProjectMemberPermission,
|
ProjectMemberPermission,
|
||||||
@ -1012,77 +1040,4 @@ class ProjectDeployBoardViewSet(BaseViewSet):
|
|||||||
project_deploy_board.save()
|
project_deploy_board.save()
|
||||||
|
|
||||||
serializer = ProjectDeployBoardSerializer(project_deploy_board)
|
serializer = ProjectDeployBoardSerializer(project_deploy_board)
|
||||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
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
|
# Third party imports
|
||||||
from rest_framework import status
|
from rest_framework import status
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from sentry_sdk import capture_exception
|
|
||||||
|
|
||||||
# Module imports
|
# Module imports
|
||||||
from .base import BaseAPIView
|
from .base import BaseAPIView
|
@ -7,12 +7,11 @@ from django.db.models import Q
|
|||||||
# Third party imports
|
# Third party imports
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from rest_framework import status
|
from rest_framework import status
|
||||||
from sentry_sdk import capture_exception
|
|
||||||
|
|
||||||
# Module imports
|
# Module imports
|
||||||
from . import BaseViewSet, BaseAPIView
|
from . import BaseViewSet
|
||||||
from plane.api.serializers import StateSerializer
|
from plane.app.serializers import StateSerializer
|
||||||
from plane.api.permissions import ProjectEntityPermission
|
from plane.app.permissions import ProjectEntityPermission
|
||||||
from plane.db.models import State, Issue
|
from plane.db.models import State, Issue
|
||||||
|
|
||||||
|
|
@ -2,17 +2,16 @@
|
|||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from rest_framework import status
|
from rest_framework import status
|
||||||
|
|
||||||
from sentry_sdk import capture_exception
|
|
||||||
|
|
||||||
# Module imports
|
# Module imports
|
||||||
from plane.api.serializers import (
|
from plane.app.serializers import (
|
||||||
UserSerializer,
|
UserSerializer,
|
||||||
IssueActivitySerializer,
|
IssueActivitySerializer,
|
||||||
UserMeSerializer,
|
UserMeSerializer,
|
||||||
UserMeSettingsSerializer,
|
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.db.models import User, IssueActivity, WorkspaceMember
|
||||||
from plane.license.models import Instance, InstanceAdmin
|
from plane.license.models import Instance, InstanceAdmin
|
||||||
from plane.utils.paginator import BasePaginator
|
from plane.utils.paginator import BasePaginator
|
@ -18,17 +18,16 @@ from django.db.models import Prefetch, OuterRef, Exists
|
|||||||
# Third party imports
|
# Third party imports
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from rest_framework import status
|
from rest_framework import status
|
||||||
from sentry_sdk import capture_exception
|
|
||||||
|
|
||||||
# Module imports
|
# Module imports
|
||||||
from . import BaseViewSet, BaseAPIView
|
from . import BaseViewSet
|
||||||
from plane.api.serializers import (
|
from plane.app.serializers import (
|
||||||
GlobalViewSerializer,
|
GlobalViewSerializer,
|
||||||
IssueViewSerializer,
|
IssueViewSerializer,
|
||||||
IssueLiteSerializer,
|
IssueLiteSerializer,
|
||||||
IssueViewFavoriteSerializer,
|
IssueViewFavoriteSerializer,
|
||||||
)
|
)
|
||||||
from plane.api.permissions import WorkspaceEntityPermission, ProjectEntityPermission
|
from plane.app.permissions import WorkspaceEntityPermission, ProjectEntityPermission
|
||||||
from plane.db.models import (
|
from plane.db.models import (
|
||||||
Workspace,
|
Workspace,
|
||||||
GlobalView,
|
GlobalView,
|
@ -9,8 +9,8 @@ from rest_framework.response import Response
|
|||||||
from plane.db.models import Webhook, WebhookLog, Workspace
|
from plane.db.models import Webhook, WebhookLog, Workspace
|
||||||
from plane.db.models.webhook import generate_token
|
from plane.db.models.webhook import generate_token
|
||||||
from .base import BaseAPIView
|
from .base import BaseAPIView
|
||||||
from plane.api.permissions import WorkspaceOwnerPermission
|
from plane.app.permissions import WorkspaceOwnerPermission
|
||||||
from plane.api.serializers import WebhookSerializer, WebhookLogSerializer
|
from plane.app.serializers import WebhookSerializer, WebhookLogSerializer
|
||||||
|
|
||||||
|
|
||||||
class WebhookEndpoint(BaseAPIView):
|
class WebhookEndpoint(BaseAPIView):
|
@ -29,10 +29,10 @@ from django.db.models.fields import DateField
|
|||||||
# Third party modules
|
# Third party modules
|
||||||
from rest_framework import status
|
from rest_framework import status
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from rest_framework.permissions import AllowAny, IsAuthenticated
|
from rest_framework.permissions import AllowAny
|
||||||
|
|
||||||
# Module imports
|
# Module imports
|
||||||
from plane.api.serializers import (
|
from plane.app.serializers import (
|
||||||
WorkSpaceSerializer,
|
WorkSpaceSerializer,
|
||||||
WorkSpaceMemberSerializer,
|
WorkSpaceMemberSerializer,
|
||||||
TeamSerializer,
|
TeamSerializer,
|
||||||
@ -45,7 +45,7 @@ from plane.api.serializers import (
|
|||||||
WorkspaceMemberAdminSerializer,
|
WorkspaceMemberAdminSerializer,
|
||||||
WorkspaceMemberMeSerializer,
|
WorkspaceMemberMeSerializer,
|
||||||
)
|
)
|
||||||
from plane.api.views.base import BaseAPIView
|
from plane.app.views.base import BaseAPIView
|
||||||
from . import BaseViewSet
|
from . import BaseViewSet
|
||||||
from plane.db.models import (
|
from plane.db.models import (
|
||||||
User,
|
User,
|
||||||
@ -65,7 +65,7 @@ from plane.db.models import (
|
|||||||
CycleIssue,
|
CycleIssue,
|
||||||
IssueReaction,
|
IssueReaction,
|
||||||
)
|
)
|
||||||
from plane.api.permissions import (
|
from plane.app.permissions import (
|
||||||
WorkSpaceBasePermission,
|
WorkSpaceBasePermission,
|
||||||
WorkSpaceAdminPermission,
|
WorkSpaceAdminPermission,
|
||||||
WorkspaceEntityPermission,
|
WorkspaceEntityPermission,
|
@ -13,7 +13,7 @@ from celery import shared_task
|
|||||||
from sentry_sdk import capture_exception
|
from sentry_sdk import capture_exception
|
||||||
|
|
||||||
# Module imports
|
# Module imports
|
||||||
from plane.api.serializers import ImporterSerializer
|
from plane.app.serializers import ImporterSerializer
|
||||||
from plane.db.models import (
|
from plane.db.models import (
|
||||||
Importer,
|
Importer,
|
||||||
WorkspaceMember,
|
WorkspaceMember,
|
||||||
|
@ -21,14 +21,11 @@ from plane.db.models import (
|
|||||||
State,
|
State,
|
||||||
Cycle,
|
Cycle,
|
||||||
Module,
|
Module,
|
||||||
IssueSubscriber,
|
|
||||||
Notification,
|
|
||||||
IssueAssignee,
|
|
||||||
IssueReaction,
|
IssueReaction,
|
||||||
CommentReaction,
|
CommentReaction,
|
||||||
IssueComment,
|
IssueComment,
|
||||||
)
|
)
|
||||||
from plane.api.serializers import IssueActivitySerializer
|
from plane.app.serializers import IssueActivitySerializer
|
||||||
from plane.bgtasks.notification_task import notifications
|
from plane.bgtasks.notification_task import notifications
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
# Module imports
|
# Module imports
|
||||||
from plane.license.models import Instance, InstanceAdmin, InstanceConfiguration
|
from plane.license.models import Instance, InstanceAdmin, InstanceConfiguration
|
||||||
from plane.api.serializers import BaseSerializer
|
from plane.app.serializers import BaseSerializer
|
||||||
from plane.api.serializers import UserAdminLiteSerializer
|
from plane.app.serializers import UserAdminLiteSerializer
|
||||||
|
|
||||||
|
|
||||||
class InstanceSerializer(BaseSerializer):
|
class InstanceSerializer(BaseSerializer):
|
||||||
|
@ -11,7 +11,7 @@ from rest_framework import status
|
|||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
|
|
||||||
# Module imports
|
# 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.models import Instance, InstanceAdmin, InstanceConfiguration
|
||||||
from plane.license.api.serializers import (
|
from plane.license.api.serializers import (
|
||||||
InstanceSerializer,
|
InstanceSerializer,
|
||||||
|
@ -34,7 +34,8 @@ INSTALLED_APPS = [
|
|||||||
"django.contrib.sessions",
|
"django.contrib.sessions",
|
||||||
# Inhouse apps
|
# Inhouse apps
|
||||||
"plane.analytics",
|
"plane.analytics",
|
||||||
"plane.api",
|
"plane.app",
|
||||||
|
"plane.space",
|
||||||
"plane.bgtasks",
|
"plane.bgtasks",
|
||||||
"plane.db",
|
"plane.db",
|
||||||
"plane.utils",
|
"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