dev: update urls to use project identifiers and issue sequence

This commit is contained in:
pablohashescobar
2024-06-25 21:18:01 +05:30
parent 535a27309e
commit e6ecfcbbc7
19 changed files with 289 additions and 130 deletions

View File

@@ -44,12 +44,16 @@ class ProjectSerializer(BaseSerializer):
detail="Project Identifier is taken"
)
project = Project.objects.create(
**validated_data, workspace_id=self.context["workspace_id"]
**validated_data,
workspace_id=self.context["workspace_id"],
)
_ = ProjectIdentifier.objects.create(
name=project.identifier,
project=project,
workspace_id=self.context["workspace_id"],
created_by_id=project.created_by_id,
updated_by_id=project.updated_by_id,
current_active=project.identifier,
)
return project
@@ -61,29 +65,38 @@ class ProjectSerializer(BaseSerializer):
project = super().update(instance, validated_data)
return project
# If no Project Identifier is found create it
project_identifier = ProjectIdentifier.objects.filter(
name=identifier, workspace_id=instance.workspace_id
).first()
if project_identifier is None:
project = super().update(instance, validated_data)
project_identifier = ProjectIdentifier.objects.filter(
project=project
).first()
if project_identifier is not None:
project_identifier.name = identifier
project_identifier.save()
return project
# If found check if the project_id to be updated and identifier project id is same
if project_identifier.project_id == instance.id:
# If same pass update
# When updating check if the project identifier is different
if instance.identifier == identifier:
project = super().update(instance, validated_data)
return project
# If not same fail update
raise serializers.ValidationError(
detail="Project Identifier is already taken"
# Check if this project identifier is already taken in the current workspace
if ProjectIdentifier.objects.filter(
name=identifier,
workspace_id=instance.workspace_id,
).exists():
raise serializers.ValidationError(
detail="Project Identifier is already taken"
)
# Update the project identifier
project = super().update(instance, validated_data)
# If no Project Identifier is found create it and deactivate the old ones
ProjectIdentifier.objects.filter(
project_id=instance.id,
workspace_id=instance.workspace_id,
).update(current_active=identifier)
# Create new project identifier
ProjectIdentifier.objects.create(
name=identifier,
project=instance,
workspace_id=instance.workspace_id,
created_by_id=project.created_by_id,
updated_by_id=project.updated_by_id,
current_active=identifier,
)
# Return the updated project
return project
class ProjectLiteSerializer(BaseSerializer):

View File

@@ -14,7 +14,7 @@ from plane.app.views import (
urlpatterns = [
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/cycles/",
"workspaces/<str:slug>/projects/<str:project_id>/cycles/",
CycleViewSet.as_view(
{
"get": "list",
@@ -24,7 +24,7 @@ urlpatterns = [
name="project-cycle",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/cycles/<uuid:pk>/",
"workspaces/<str:slug>/projects/<str:project_id>/cycles/<uuid:pk>/",
CycleViewSet.as_view(
{
"get": "retrieve",
@@ -36,7 +36,7 @@ urlpatterns = [
name="project-cycle",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/cycles/<uuid:cycle_id>/cycle-issues/",
"workspaces/<str:slug>/projects/<str:project_id>/cycles/<uuid:cycle_id>/cycle-issues/",
CycleIssueViewSet.as_view(
{
"get": "list",
@@ -46,7 +46,7 @@ urlpatterns = [
name="project-issue-cycle",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/cycles/<uuid:cycle_id>/cycle-issues/<uuid:issue_id>/",
"workspaces/<str:slug>/projects/<str:project_id>/cycles/<uuid:cycle_id>/cycle-issues/<uuid:issue_id>/",
CycleIssueViewSet.as_view(
{
"get": "retrieve",
@@ -58,12 +58,12 @@ urlpatterns = [
name="project-issue-cycle",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/cycles/date-check/",
"workspaces/<str:slug>/projects/<str:project_id>/cycles/date-check/",
CycleDateCheckEndpoint.as_view(),
name="project-cycle-date",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/user-favorite-cycles/",
"workspaces/<str:slug>/projects/<str:project_id>/user-favorite-cycles/",
CycleFavoriteViewSet.as_view(
{
"get": "list",
@@ -73,7 +73,7 @@ urlpatterns = [
name="user-favorite-cycle",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/user-favorite-cycles/<uuid:cycle_id>/",
"workspaces/<str:slug>/projects/<str:project_id>/user-favorite-cycles/<uuid:cycle_id>/",
CycleFavoriteViewSet.as_view(
{
"delete": "destroy",
@@ -82,27 +82,27 @@ urlpatterns = [
name="user-favorite-cycle",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/cycles/<uuid:cycle_id>/transfer-issues/",
"workspaces/<str:slug>/projects/<str:project_id>/cycles/<uuid:cycle_id>/transfer-issues/",
TransferCycleIssueEndpoint.as_view(),
name="transfer-issues",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/cycles/<uuid:cycle_id>/user-properties/",
"workspaces/<str:slug>/projects/<str:project_id>/cycles/<uuid:cycle_id>/user-properties/",
CycleUserPropertiesEndpoint.as_view(),
name="cycle-user-filters",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/cycles/<uuid:cycle_id>/archive/",
"workspaces/<str:slug>/projects/<str:project_id>/cycles/<uuid:cycle_id>/archive/",
CycleArchiveUnarchiveEndpoint.as_view(),
name="cycle-archive-unarchive",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/archived-cycles/",
"workspaces/<str:slug>/projects/<str:project_id>/archived-cycles/",
CycleArchiveUnarchiveEndpoint.as_view(),
name="cycle-archive-unarchive",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/archived-cycles/<uuid:pk>/",
"workspaces/<str:slug>/projects/<str:project_id>/archived-cycles/<uuid:pk>/",
CycleArchiveUnarchiveEndpoint.as_view(),
name="cycle-archive-unarchive",
),

View File

@@ -10,12 +10,12 @@ from plane.app.views import (
urlpatterns = [
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/project-estimates/",
"workspaces/<str:slug>/projects/<str:project_id>/project-estimates/",
ProjectEstimatePointEndpoint.as_view(),
name="project-estimate-points",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/estimates/",
"workspaces/<str:slug>/projects/<str:project_id>/estimates/",
BulkEstimatePointEndpoint.as_view(
{
"get": "list",
@@ -25,7 +25,7 @@ urlpatterns = [
name="bulk-create-estimate-points",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/estimates/<uuid:estimate_id>/",
"workspaces/<str:slug>/projects/<str:project_id>/estimates/<uuid:estimate_id>/",
BulkEstimatePointEndpoint.as_view(
{
"get": "retrieve",
@@ -36,7 +36,7 @@ urlpatterns = [
name="bulk-create-estimate-points",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/estimates/<uuid:estimate_id>/estimate-points/",
"workspaces/<str:slug>/projects/<str:project_id>/estimates/<uuid:estimate_id>/estimate-points/",
EstimatePointEndpoint.as_view(
{
"post": "create",
@@ -45,7 +45,7 @@ urlpatterns = [
name="estimate-points",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/estimates/<uuid:estimate_id>/estimate-points/<estimate_point_id>/",
"workspaces/<str:slug>/projects/<str:project_id>/estimates/<uuid:estimate_id>/estimate-points/<estimate_point_id>/",
EstimatePointEndpoint.as_view(
{
"patch": "partial_update",

View File

@@ -2,7 +2,10 @@ from django.urls import path
from plane.app.views import UnsplashEndpoint
from plane.app.views import GPTIntegrationEndpoint, WorkspaceGPTIntegrationEndpoint
from plane.app.views import (
GPTIntegrationEndpoint,
WorkspaceGPTIntegrationEndpoint,
)
urlpatterns = [
@@ -12,11 +15,11 @@ urlpatterns = [
name="unsplash",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/ai-assistant/",
"workspaces/<str:slug>/projects/<str:project_id>/ai-assistant/",
GPTIntegrationEndpoint.as_view(),
name="importer",
),
path(
path(
"workspaces/<str:slug>/ai-assistant/",
WorkspaceGPTIntegrationEndpoint.as_view(),
name="importer",

View File

@@ -9,7 +9,7 @@ from plane.app.views import (
urlpatterns = [
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/inboxes/",
"workspaces/<str:slug>/projects/<str:project_id>/inboxes/",
InboxViewSet.as_view(
{
"get": "list",
@@ -19,7 +19,7 @@ urlpatterns = [
name="inbox",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/inboxes/<uuid:pk>/",
"workspaces/<str:slug>/projects/<str:project_id>/inboxes/<uuid:pk>/",
InboxViewSet.as_view(
{
"get": "retrieve",
@@ -30,7 +30,7 @@ urlpatterns = [
name="inbox",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/inbox-issues/",
"workspaces/<str:slug>/projects/<str:project_id>/inbox-issues/",
InboxIssueViewSet.as_view(
{
"get": "list",
@@ -40,7 +40,7 @@ urlpatterns = [
name="inbox-issue",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/inbox-issues/<uuid:issue_id>/",
"workspaces/<str:slug>/projects/<str:project_id>/inbox-issues/<uuid:issue_id>/",
InboxIssueViewSet.as_view(
{
"get": "retrieve",

View File

@@ -25,12 +25,12 @@ from plane.app.views import (
urlpatterns = [
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/issues/list/",
"workspaces/<str:slug>/projects/<str:project_id>/issues/list/",
IssueListEndpoint.as_view(),
name="project-issue",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/issues/",
"workspaces/<str:slug>/projects/<str:project_id>/issues/",
IssueViewSet.as_view(
{
"get": "list",
@@ -40,7 +40,7 @@ urlpatterns = [
name="project-issue",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/issues/<uuid:pk>/",
"workspaces/<str:slug>/projects/<str:project_id>/issues/<str:pk>/",
IssueViewSet.as_view(
{
"get": "retrieve",
@@ -52,7 +52,7 @@ urlpatterns = [
name="project-issue",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/issue-labels/",
"workspaces/<str:slug>/projects/<str:project_id>/issue-labels/",
LabelViewSet.as_view(
{
"get": "list",
@@ -62,7 +62,7 @@ urlpatterns = [
name="project-issue-labels",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/issue-labels/<uuid:pk>/",
"workspaces/<str:slug>/projects/<str:project_id>/issue-labels/<uuid:pk>/",
LabelViewSet.as_view(
{
"get": "retrieve",
@@ -74,28 +74,28 @@ urlpatterns = [
name="project-issue-labels",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/bulk-create-labels/",
"workspaces/<str:slug>/projects/<str:project_id>/bulk-create-labels/",
BulkCreateIssueLabelsEndpoint.as_view(),
name="project-bulk-labels",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/bulk-delete-issues/",
"workspaces/<str:slug>/projects/<str:project_id>/bulk-delete-issues/",
BulkDeleteIssuesEndpoint.as_view(),
name="project-issues-bulk",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/bulk-archive-issues/",
"workspaces/<str:slug>/projects/<str:project_id>/bulk-archive-issues/",
BulkArchiveIssuesEndpoint.as_view(),
name="bulk-archive-issues",
),
##
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/issues/<uuid:issue_id>/sub-issues/",
"workspaces/<str:slug>/projects/<str:project_id>/issues/<uuid:issue_id>/sub-issues/",
SubIssuesEndpoint.as_view(),
name="sub-issues",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/issues/<uuid:issue_id>/issue-links/",
"workspaces/<str:slug>/projects/<str:project_id>/issues/<uuid:issue_id>/issue-links/",
IssueLinkViewSet.as_view(
{
"get": "list",
@@ -105,7 +105,7 @@ urlpatterns = [
name="project-issue-links",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/issues/<uuid:issue_id>/issue-links/<uuid:pk>/",
"workspaces/<str:slug>/projects/<str:project_id>/issues/<uuid:issue_id>/issue-links/<uuid:pk>/",
IssueLinkViewSet.as_view(
{
"get": "retrieve",
@@ -117,12 +117,12 @@ urlpatterns = [
name="project-issue-links",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/issues/<uuid:issue_id>/issue-attachments/",
"workspaces/<str:slug>/projects/<str:project_id>/issues/<uuid:issue_id>/issue-attachments/",
IssueAttachmentEndpoint.as_view(),
name="project-issue-attachments",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/issues/<uuid:issue_id>/issue-attachments/<uuid:pk>/",
"workspaces/<str:slug>/projects/<str:project_id>/issues/<uuid:issue_id>/issue-attachments/<uuid:pk>/",
IssueAttachmentEndpoint.as_view(),
name="project-issue-attachments",
),
@@ -134,14 +134,14 @@ urlpatterns = [
## End Issues
## Issue Activity
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/issues/<uuid:issue_id>/history/",
"workspaces/<str:slug>/projects/<str:project_id>/issues/<uuid:issue_id>/history/",
IssueActivityEndpoint.as_view(),
name="project-issue-history",
),
## Issue Activity
## IssueComments
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/issues/<uuid:issue_id>/comments/",
"workspaces/<str:slug>/projects/<str:project_id>/issues/<uuid:issue_id>/comments/",
IssueCommentViewSet.as_view(
{
"get": "list",
@@ -151,7 +151,7 @@ urlpatterns = [
name="project-issue-comment",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/issues/<uuid:issue_id>/comments/<uuid:pk>/",
"workspaces/<str:slug>/projects/<str:project_id>/issues/<uuid:issue_id>/comments/<uuid:pk>/",
IssueCommentViewSet.as_view(
{
"get": "retrieve",
@@ -165,7 +165,7 @@ urlpatterns = [
## End IssueComments
# Issue Subscribers
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/issues/<uuid:issue_id>/issue-subscribers/",
"workspaces/<str:slug>/projects/<str:project_id>/issues/<uuid:issue_id>/issue-subscribers/",
IssueSubscriberViewSet.as_view(
{
"get": "list",
@@ -175,12 +175,12 @@ urlpatterns = [
name="project-issue-subscribers",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/issues/<uuid:issue_id>/issue-subscribers/<uuid:subscriber_id>/",
"workspaces/<str:slug>/projects/<str:project_id>/issues/<uuid:issue_id>/issue-subscribers/<uuid:subscriber_id>/",
IssueSubscriberViewSet.as_view({"delete": "destroy"}),
name="project-issue-subscribers",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/issues/<uuid:issue_id>/subscribe/",
"workspaces/<str:slug>/projects/<str:project_id>/issues/<uuid:issue_id>/subscribe/",
IssueSubscriberViewSet.as_view(
{
"get": "subscription_status",
@@ -193,7 +193,7 @@ urlpatterns = [
## End Issue Subscribers
# Issue Reactions
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/issues/<uuid:issue_id>/reactions/",
"workspaces/<str:slug>/projects/<str:project_id>/issues/<uuid:issue_id>/reactions/",
IssueReactionViewSet.as_view(
{
"get": "list",
@@ -203,7 +203,7 @@ urlpatterns = [
name="project-issue-reactions",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/issues/<uuid:issue_id>/reactions/<str:reaction_code>/",
"workspaces/<str:slug>/projects/<str:project_id>/issues/<uuid:issue_id>/reactions/<str:reaction_code>/",
IssueReactionViewSet.as_view(
{
"delete": "destroy",
@@ -214,7 +214,7 @@ urlpatterns = [
## End Issue Reactions
# Comment Reactions
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/comments/<uuid:comment_id>/reactions/",
"workspaces/<str:slug>/projects/<str:project_id>/comments/<uuid:comment_id>/reactions/",
CommentReactionViewSet.as_view(
{
"get": "list",
@@ -224,7 +224,7 @@ urlpatterns = [
name="project-issue-comment-reactions",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/comments/<uuid:comment_id>/reactions/<str:reaction_code>/",
"workspaces/<str:slug>/projects/<str:project_id>/comments/<uuid:comment_id>/reactions/<str:reaction_code>/",
CommentReactionViewSet.as_view(
{
"delete": "destroy",
@@ -235,14 +235,14 @@ urlpatterns = [
## End Comment Reactions
## IssueProperty
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/user-properties/",
"workspaces/<str:slug>/projects/<str:project_id>/user-properties/",
IssueUserDisplayPropertyEndpoint.as_view(),
name="project-issue-display-properties",
),
## IssueProperty End
## Issue Archives
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/archived-issues/",
"workspaces/<str:slug>/projects/<str:project_id>/archived-issues/",
IssueArchiveViewSet.as_view(
{
"get": "list",
@@ -251,7 +251,7 @@ urlpatterns = [
name="project-issue-archive",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/issues/<uuid:pk>/archive/",
"workspaces/<str:slug>/projects/<str:project_id>/issues/<uuid:pk>/archive/",
IssueArchiveViewSet.as_view(
{
"get": "retrieve",
@@ -264,7 +264,7 @@ urlpatterns = [
## End Issue Archives
## Issue Relation
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/issues/<uuid:issue_id>/issue-relation/",
"workspaces/<str:slug>/projects/<str:project_id>/issues/<uuid:issue_id>/issue-relation/",
IssueRelationViewSet.as_view(
{
"get": "list",
@@ -274,7 +274,7 @@ urlpatterns = [
name="issue-relation",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/issues/<uuid:issue_id>/remove-relation/",
"workspaces/<str:slug>/projects/<str:project_id>/issues/<uuid:issue_id>/remove-relation/",
IssueRelationViewSet.as_view(
{
"post": "remove_relation",
@@ -285,7 +285,7 @@ urlpatterns = [
## End Issue Relation
## Issue Drafts
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/issue-drafts/",
"workspaces/<str:slug>/projects/<str:project_id>/issue-drafts/",
IssueDraftViewSet.as_view(
{
"get": "list",
@@ -295,7 +295,7 @@ urlpatterns = [
name="project-issue-draft",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/issue-drafts/<uuid:pk>/",
"workspaces/<str:slug>/projects/<str:project_id>/issue-drafts/<uuid:pk>/",
IssueDraftViewSet.as_view(
{
"get": "retrieve",
@@ -306,7 +306,7 @@ urlpatterns = [
name="project-issue-draft",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/bulk-operation-issues/",
"workspaces/<str:slug>/projects/<str:project_id>/bulk-operation-issues/",
BulkIssueOperationsEndpoint.as_view(),
name="bulk-operations-issues",
),

View File

@@ -13,7 +13,7 @@ from plane.app.views import (
urlpatterns = [
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/modules/",
"workspaces/<str:slug>/projects/<str:project_id>/modules/",
ModuleViewSet.as_view(
{
"get": "list",
@@ -23,7 +23,7 @@ urlpatterns = [
name="project-modules",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/modules/<uuid:pk>/",
"workspaces/<str:slug>/projects/<str:project_id>/modules/<uuid:pk>/",
ModuleViewSet.as_view(
{
"get": "retrieve",
@@ -35,7 +35,7 @@ urlpatterns = [
name="project-modules",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/issues/<uuid:issue_id>/modules/",
"workspaces/<str:slug>/projects/<str:project_id>/issues/<uuid:issue_id>/modules/",
ModuleIssueViewSet.as_view(
{
"post": "create_issue_modules",
@@ -44,7 +44,7 @@ urlpatterns = [
name="issue-module",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/modules/<uuid:module_id>/issues/",
"workspaces/<str:slug>/projects/<str:project_id>/modules/<uuid:module_id>/issues/",
ModuleIssueViewSet.as_view(
{
"post": "create_module_issues",
@@ -54,7 +54,7 @@ urlpatterns = [
name="project-module-issues",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/modules/<uuid:module_id>/issues/<uuid:issue_id>/",
"workspaces/<str:slug>/projects/<str:project_id>/modules/<uuid:module_id>/issues/<uuid:issue_id>/",
ModuleIssueViewSet.as_view(
{
"get": "retrieve",
@@ -66,7 +66,7 @@ urlpatterns = [
name="project-module-issues",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/modules/<uuid:module_id>/module-links/",
"workspaces/<str:slug>/projects/<str:project_id>/modules/<uuid:module_id>/module-links/",
ModuleLinkViewSet.as_view(
{
"get": "list",
@@ -76,7 +76,7 @@ urlpatterns = [
name="project-issue-module-links",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/modules/<uuid:module_id>/module-links/<uuid:pk>/",
"workspaces/<str:slug>/projects/<str:project_id>/modules/<uuid:module_id>/module-links/<uuid:pk>/",
ModuleLinkViewSet.as_view(
{
"get": "retrieve",
@@ -88,7 +88,7 @@ urlpatterns = [
name="project-issue-module-links",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/user-favorite-modules/",
"workspaces/<str:slug>/projects/<str:project_id>/user-favorite-modules/",
ModuleFavoriteViewSet.as_view(
{
"get": "list",
@@ -98,7 +98,7 @@ urlpatterns = [
name="user-favorite-module",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/user-favorite-modules/<uuid:module_id>/",
"workspaces/<str:slug>/projects/<str:project_id>/user-favorite-modules/<uuid:module_id>/",
ModuleFavoriteViewSet.as_view(
{
"delete": "destroy",
@@ -107,22 +107,22 @@ urlpatterns = [
name="user-favorite-module",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/modules/<uuid:module_id>/user-properties/",
"workspaces/<str:slug>/projects/<str:project_id>/modules/<uuid:module_id>/user-properties/",
ModuleUserPropertiesEndpoint.as_view(),
name="cycle-user-filters",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/modules/<uuid:module_id>/archive/",
"workspaces/<str:slug>/projects/<str:project_id>/modules/<uuid:module_id>/archive/",
ModuleArchiveUnarchiveEndpoint.as_view(),
name="module-archive-unarchive",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/archived-modules/",
"workspaces/<str:slug>/projects/<str:project_id>/archived-modules/",
ModuleArchiveUnarchiveEndpoint.as_view(),
name="module-archive-unarchive",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/archived-modules/<uuid:pk>/",
"workspaces/<str:slug>/projects/<str:project_id>/archived-modules/<uuid:pk>/",
ModuleArchiveUnarchiveEndpoint.as_view(),
name="module-archive-unarchive",
),

View File

@@ -12,7 +12,7 @@ from plane.app.views import (
urlpatterns = [
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/pages/",
"workspaces/<str:slug>/projects/<str:project_id>/pages/",
PageViewSet.as_view(
{
"get": "list",
@@ -22,7 +22,7 @@ urlpatterns = [
name="project-pages",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/pages/<uuid:pk>/",
"workspaces/<str:slug>/projects/<str:project_id>/pages/<uuid:pk>/",
PageViewSet.as_view(
{
"get": "retrieve",
@@ -34,7 +34,7 @@ urlpatterns = [
),
# favorite pages
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/favorite-pages/<uuid:pk>/",
"workspaces/<str:slug>/projects/<str:project_id>/favorite-pages/<uuid:pk>/",
PageFavoriteViewSet.as_view(
{
"post": "create",
@@ -45,7 +45,7 @@ urlpatterns = [
),
# archived pages
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/pages/<uuid:pk>/archive/",
"workspaces/<str:slug>/projects/<str:project_id>/pages/<uuid:pk>/archive/",
PageViewSet.as_view(
{
"post": "archive",
@@ -56,7 +56,7 @@ urlpatterns = [
),
# lock and unlock
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/pages/<uuid:pk>/lock/",
"workspaces/<str:slug>/projects/<str:project_id>/pages/<uuid:pk>/lock/",
PageViewSet.as_view(
{
"post": "lock",
@@ -66,22 +66,22 @@ urlpatterns = [
name="project-pages-lock-unlock",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/pages/<uuid:pk>/transactions/",
"workspaces/<str:slug>/projects/<str:project_id>/pages/<uuid:pk>/transactions/",
PageLogEndpoint.as_view(),
name="page-transactions",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/pages/<uuid:pk>/transactions/<uuid:transaction>/",
"workspaces/<str:slug>/projects/<str:project_id>/pages/<uuid:pk>/transactions/<uuid:transaction>/",
PageLogEndpoint.as_view(),
name="page-transactions",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/pages/<uuid:pk>/sub-pages/",
"workspaces/<str:slug>/projects/<str:project_id>/pages/<uuid:pk>/sub-pages/",
SubPagesEndpoint.as_view(),
name="sub-page",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/pages/<uuid:pk>/description/",
"workspaces/<str:slug>/projects/<str:project_id>/pages/<uuid:pk>/description/",
PagesDescriptionViewSet.as_view(
{
"get": "retrieve",

View File

@@ -30,7 +30,7 @@ urlpatterns = [
name="project",
),
path(
"workspaces/<str:slug>/projects/<uuid:pk>/",
"workspaces/<str:slug>/projects/<str:pk>/",
ProjectViewSet.as_view(
{
"get": "retrieve",
@@ -47,7 +47,7 @@ urlpatterns = [
name="project-identifiers",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/invitations/",
"workspaces/<str:slug>/projects/<str:project_id>/invitations/",
ProjectInvitationsViewset.as_view(
{
"get": "list",
@@ -57,7 +57,7 @@ urlpatterns = [
name="project-member-invite",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/invitations/<uuid:pk>/",
"workspaces/<str:slug>/projects/<str:project_id>/invitations/<uuid:pk>/",
ProjectInvitationsViewset.as_view(
{
"get": "retrieve",
@@ -82,12 +82,12 @@ urlpatterns = [
name="user-project-roles",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/join/<uuid:pk>/",
"workspaces/<str:slug>/projects/<str:project_id>/join/<uuid:pk>/",
ProjectJoinEndpoint.as_view(),
name="project-join",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/members/",
"workspaces/<str:slug>/projects/<str:project_id>/members/",
ProjectMemberViewSet.as_view(
{
"get": "list",
@@ -97,7 +97,7 @@ urlpatterns = [
name="project-member",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/members/<uuid:pk>/",
"workspaces/<str:slug>/projects/<str:project_id>/members/<uuid:pk>/",
ProjectMemberViewSet.as_view(
{
"get": "retrieve",
@@ -108,7 +108,7 @@ urlpatterns = [
name="project-member",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/members/leave/",
"workspaces/<str:slug>/projects/<str:project_id>/members/leave/",
ProjectMemberViewSet.as_view(
{
"post": "leave",
@@ -117,17 +117,17 @@ urlpatterns = [
name="project-member",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/team-invite/",
"workspaces/<str:slug>/projects/<str:project_id>/team-invite/",
AddTeamToProjectEndpoint.as_view(),
name="projects",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/project-views/",
"workspaces/<str:slug>/projects/<str:project_id>/project-views/",
ProjectUserViewsEndpoint.as_view(),
name="project-view",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/project-members/me/",
"workspaces/<str:slug>/projects/<str:project_id>/project-members/me/",
ProjectMemberUserEndpoint.as_view(),
name="project-member-view",
),
@@ -142,7 +142,7 @@ urlpatterns = [
name="project-favorite",
),
path(
"workspaces/<str:slug>/user-favorite-projects/<uuid:project_id>/",
"workspaces/<str:slug>/user-favorite-projects/<str:project_id>/",
ProjectFavoritesViewSet.as_view(
{
"delete": "destroy",
@@ -156,7 +156,7 @@ urlpatterns = [
name="project-covers",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/project-deploy-boards/",
"workspaces/<str:slug>/projects/<str:project_id>/project-deploy-boards/",
DeployBoardViewSet.as_view(
{
"get": "list",
@@ -166,7 +166,7 @@ urlpatterns = [
name="project-deploy-board",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/project-deploy-boards/<uuid:pk>/",
"workspaces/<str:slug>/projects/<str:project_id>/project-deploy-boards/<uuid:pk>/",
DeployBoardViewSet.as_view(
{
"get": "retrieve",
@@ -177,7 +177,7 @@ urlpatterns = [
name="project-deploy-board",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/archive/",
"workspaces/<str:slug>/projects/<str:project_id>/archive/",
ProjectArchiveUnarchiveEndpoint.as_view(),
name="project-archive-unarchive",
),

View File

@@ -14,7 +14,7 @@ urlpatterns = [
name="global-search",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/search-issues/",
"workspaces/<str:slug>/projects/<str:project_id>/search-issues/",
IssueSearchEndpoint.as_view(),
name="project-issue-search",
),

View File

@@ -6,7 +6,7 @@ from plane.app.views import StateViewSet
urlpatterns = [
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/states/",
"workspaces/<str:slug>/projects/<str:project_id>/states/",
StateViewSet.as_view(
{
"get": "list",
@@ -16,7 +16,7 @@ urlpatterns = [
name="project-states",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/states/<uuid:pk>/",
"workspaces/<str:slug>/projects/<str:project_id>/states/<uuid:pk>/",
StateViewSet.as_view(
{
"get": "retrieve",
@@ -27,7 +27,7 @@ urlpatterns = [
name="project-state",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/states/<uuid:pk>/mark-default/",
"workspaces/<str:slug>/projects/<str:project_id>/states/<uuid:pk>/mark-default/",
StateViewSet.as_view(
{
"post": "mark_as_default",

View File

@@ -11,7 +11,7 @@ from plane.app.views import (
urlpatterns = [
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/views/",
"workspaces/<str:slug>/projects/<str:project_id>/views/",
IssueViewViewSet.as_view(
{
"get": "list",
@@ -21,7 +21,7 @@ urlpatterns = [
name="project-view",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/views/<uuid:pk>/",
"workspaces/<str:slug>/projects/<str:project_id>/views/<uuid:pk>/",
IssueViewViewSet.as_view(
{
"get": "retrieve",
@@ -64,7 +64,7 @@ urlpatterns = [
name="global-view-issues",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/user-favorite-views/",
"workspaces/<str:slug>/projects/<str:project_id>/user-favorite-views/",
IssueViewFavoriteViewSet.as_view(
{
"get": "list",
@@ -74,7 +74,7 @@ urlpatterns = [
name="user-favorite-view",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/user-favorite-views/<uuid:view_id>/",
"workspaces/<str:slug>/projects/<str:project_id>/user-favorite-views/<uuid:view_id>/",
IssueViewFavoriteViewSet.as_view(
{
"delete": "destroy",

View File

@@ -1,12 +1,12 @@
# Python imports
import traceback
import uuid
import zoneinfo
# Django imports
from django.conf import settings
from django.core.exceptions import ObjectDoesNotExist, ValidationError
from django.db import IntegrityError
# Django imports
from django.urls import resolve
from django.utils import timezone
from django_filters.rest_framework import DjangoFilterBackend
@@ -24,6 +24,7 @@ from rest_framework.viewsets import ModelViewSet
from plane.authentication.session import BaseSessionAuthentication
from plane.utils.exception_logger import log_exception
from plane.utils.paginator import BasePaginator
from plane.db.models import ProjectIdentifier
class TimezoneMixin:
@@ -60,6 +61,38 @@ class BaseViewSet(TimezoneMixin, ModelViewSet, BasePaginator):
search_fields = []
def is_uuid(self, value):
try:
# Check if the value is a valid UUID
uuid.UUID(str(value))
return True
except ValueError:
return False
def get_project_id(self, project_id):
# If the project_id is present and is not a UUID then get the project_id from the project_identifier
if project_id and not self.is_uuid(project_id):
project_identifier = (
ProjectIdentifier.objects.filter(
workspace__slug=self.workspace_slug, name=project_id
)
.values("project_id")
.first()
)
# If the project_identifier is not present then raise ObjectDoesNotExist
if not project_identifier:
raise ObjectDoesNotExist
# Update the project_id in the kwargs
return project_identifier.get("project_id")
return project_id
def get_issue_id(self, issue_id):
# If the issue_id is present and is not a UUID then get the issue_id from the issue_identifier
if issue_id and not self.is_uuid(issue_id):
pass
return issue_id
def get_queryset(self):
try:
return self.model.objects.all()
@@ -116,8 +149,18 @@ class BaseViewSet(TimezoneMixin, ModelViewSet, BasePaginator):
def dispatch(self, request, *args, **kwargs):
try:
# Write identifier to the uuid
project_id = self.kwargs.get("project_id", None)
if project_id:
self.kwargs["project_id"] = self.get_project_id(project_id)
# Check issue_id is present in the kwargs
issue_id = self.kwargs.get("issue_id", None)
if issue_id:
self.kwargs["issue_id"] = self.get_issue_id(issue_id)
# If the project_id is not present then get the project_id from the project_identifier
response = super().dispatch(request, *args, **kwargs)
# Print the number of queries if the debug is enabled
if settings.DEBUG:
from django.db import connection
@@ -125,6 +168,7 @@ class BaseViewSet(TimezoneMixin, ModelViewSet, BasePaginator):
f"{request.method} - {request.get_full_path()} of Queries: {len(connection.queries)}"
)
# Return the response
return response
except Exception as exc:
response = self.handle_exception(exc)

View File

@@ -58,8 +58,7 @@ from plane.utils.paginator import (
)
from .. import BaseAPIView, BaseViewSet
from plane.utils.user_timezone_converter import user_timezone_converter
# Module imports
from plane.utils.valid_int_uuid import is_int_or_uuid
class IssueListEndpoint(BaseAPIView):
@@ -410,9 +409,10 @@ class IssueViewSet(BaseViewSet):
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
def retrieve(self, request, slug, project_id, pk=None):
type, value = is_int_or_uuid(pk)
issue = (
self.get_queryset()
.filter(pk=pk)
.annotate(
label_ids=Coalesce(
ArrayAgg(
@@ -470,7 +470,18 @@ class IssueViewSet(BaseViewSet):
)
)
)
).first()
)
if type == "int":
issue = issue.filter(sequence_id=pk)
elif type == "uuid":
issue = issue.filter(pk=pk)
else:
return Response(
{"error": "Invalid issue id"},
status=status.HTTP_400_BAD_REQUEST,
)
issue = issue.first()
if not issue:
return Response(
{"error": "The required object does not exist."},

View File

@@ -61,6 +61,21 @@ class ProjectViewSet(BaseViewSet):
ProjectBasePermission,
]
def dispatch(self, request, *args, **kwargs):
pk = kwargs.get("pk")
if pk and not self.is_uuid(pk):
project = (
ProjectIdentifier.objects.filter(
name=pk, workspace__slug=kwargs.get("slug")
)
.values("project")
.first()
)
if project:
kwargs["pk"] = project["project"]
return super().dispatch(request, *args, **kwargs)
def get_queryset(self):
sort_order = ProjectMember.objects.filter(
member=self.request.user,
@@ -484,32 +499,39 @@ class ProjectIdentifierEndpoint(BaseAPIView):
]
def get(self, request, slug):
# Get the identifier name
name = request.GET.get("name", "").strip().upper()
# Check if the name is passed
if name == "":
return Response(
{"error": "Name is required"},
status=status.HTTP_400_BAD_REQUEST,
)
# Check if the identifier exists
exists = ProjectIdentifier.objects.filter(
name=name, workspace__slug=slug
).values("id", "name", "project")
# Return the response
return Response(
{"exists": len(exists), "identifiers": exists},
status=status.HTTP_200_OK,
)
def delete(self, request, slug):
# Get the identifier name
name = request.data.get("name", "").strip().upper()
# Check if the name is passed
if name == "":
return Response(
{"error": "Name is required"},
status=status.HTTP_400_BAD_REQUEST,
)
# Check if the identifier is used in any project
if Project.objects.filter(
identifier=name, workspace__slug=slug
).exists():
@@ -520,10 +542,12 @@ class ProjectIdentifierEndpoint(BaseAPIView):
status=status.HTTP_400_BAD_REQUEST,
)
# Delete the identifier
ProjectIdentifier.objects.filter(
name=name, workspace__slug=slug
).delete()
# Return success
return Response(
status=status.HTTP_204_NO_CONTENT,
)

View File

@@ -0,0 +1,41 @@
# Generated by Django 4.2.11 on 2024-06-25 07:32
from django.db import migrations, models
import django.db.models.deletion
def migrate_current_active(apps, schema_editor):
ProjectIdentifier = apps.get_model("db", "ProjectIdentifier")
# Update current_active field with name field
ProjectIdentifier.objects.update(current_active=models.F("name"))
class Migration(migrations.Migration):
dependencies = [
("db", "0069_alter_account_provider_and_more"),
]
operations = [
migrations.AddField(
model_name="projectidentifier",
name="current_active",
field=models.CharField(db_index=True, max_length=12, null=True),
),
migrations.AlterField(
model_name="projectidentifier",
name="name",
field=models.CharField(db_index=True, max_length=12),
),
migrations.AlterField(
model_name="projectidentifier",
name="project",
field=models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="project_identifier",
to="db.project",
),
),
migrations.RunPython(migrate_current_active),
]

View File

@@ -206,7 +206,6 @@ class ProjectMember(ProjectBaseModel):
return f"{self.member.email} <{self.project.name}>"
# TODO: Remove workspace relation later
class ProjectIdentifier(AuditModel):
workspace = models.ForeignKey(
"db.Workspace",
@@ -214,10 +213,11 @@ class ProjectIdentifier(AuditModel):
related_name="project_identifiers",
null=True,
)
project = models.OneToOneField(
project = models.ForeignKey(
Project, on_delete=models.CASCADE, related_name="project_identifier"
)
name = models.CharField(max_length=12)
name = models.CharField(max_length=12, db_index=True)
current_active = models.CharField(max_length=12, db_index=True, null=True)
class Meta:
unique_together = ["name", "workspace"]

View File

@@ -8,7 +8,10 @@ DEBUG = True
# Debug Toolbar settings
INSTALLED_APPS += ("debug_toolbar",) # noqa
MIDDLEWARE += ("debug_toolbar.middleware.DebugToolbarMiddleware",) # noqa
MIDDLEWARE += ( # noqa
"debug_toolbar.middleware.DebugToolbarMiddleware",
"kolo.middleware.KoloMiddleware",
)
DEBUG_TOOLBAR_PATCH_SETTINGS = False

View File

@@ -0,0 +1,20 @@
# Python imports
import uuid
def is_int_or_uuid(value):
# Check if the value can be converted to an integer
try:
int_value = int(value)
return "int", int_value
except ValueError:
pass
# Check if the value can be converted to a UUID
try:
uuid_value = uuid.UUID(str(value))
return "uuid", uuid_value
except ValueError:
pass
return "", None