mirror of
https://github.com/makeplane/plane
synced 2025-08-07 19:59:33 +00:00
Compare commits
3 Commits
refactor-g
...
chore-urls
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bdffb58581 | ||
|
|
6cd03cbb56 | ||
|
|
e6ecfcbbc7 |
@@ -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):
|
||||
|
||||
@@ -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",
|
||||
),
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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/<str: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/<str: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/<str: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/<str: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/<str: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/<str: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/<str: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/<str: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/<str: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/<str: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/<str: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/<str: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/<str: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/<str: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/<str: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",
|
||||
),
|
||||
|
||||
@@ -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",
|
||||
),
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
),
|
||||
|
||||
@@ -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",
|
||||
),
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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,8 @@ 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, IssueSequence
|
||||
from plane.utils.valid_int_uuid import is_uuid, is_int_or_uuid
|
||||
|
||||
|
||||
class TimezoneMixin:
|
||||
@@ -60,6 +62,44 @@ class BaseViewSet(TimezoneMixin, ModelViewSet, BasePaginator):
|
||||
|
||||
search_fields = []
|
||||
|
||||
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 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, project_id):
|
||||
# If the issue_id is present and is not a UUID then get the issue_id from the issue_identifier
|
||||
if issue_id:
|
||||
type, value = is_int_or_uuid(issue_id)
|
||||
if type == "int":
|
||||
issue_identifier = (
|
||||
IssueSequence.objects.filter(
|
||||
project_id=project_id, sequence=value
|
||||
)
|
||||
.values("issue_id")
|
||||
.first()
|
||||
)
|
||||
# If the issue_identifier is not present then raise ObjectDoesNotExist
|
||||
if not issue_identifier:
|
||||
raise ObjectDoesNotExist
|
||||
|
||||
# Update the issue_id in the kwargs
|
||||
return issue_identifier.get("issue_id")
|
||||
return issue_id
|
||||
|
||||
def get_queryset(self):
|
||||
try:
|
||||
return self.model.objects.all()
|
||||
@@ -116,8 +156,22 @@ class BaseViewSet(TimezoneMixin, ModelViewSet, BasePaginator):
|
||||
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
try:
|
||||
# Write identifier to the uuid
|
||||
project_id = kwargs.get("project_id", None)
|
||||
if project_id:
|
||||
kwargs["project_id"] = self.get_project_id(project_id)
|
||||
|
||||
# Check issue_id is present in the kwargs
|
||||
issue_id = kwargs.get("issue_id", None)
|
||||
if issue_id:
|
||||
kwargs["issue_id"] = self.get_issue_id(
|
||||
issue_id, kwargs["project_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 +179,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)
|
||||
@@ -138,7 +193,7 @@ class BaseViewSet(TimezoneMixin, ModelViewSet, BasePaginator):
|
||||
def project_id(self):
|
||||
project_id = self.kwargs.get("project_id", None)
|
||||
if project_id:
|
||||
return project_id
|
||||
return self.get_project_id(project_id)
|
||||
|
||||
if resolve(self.request.path_info).url_name == "project":
|
||||
return self.kwargs.get("pk", None)
|
||||
|
||||
@@ -44,6 +44,7 @@ from plane.db.models import (
|
||||
IssueReaction,
|
||||
IssueSubscriber,
|
||||
Project,
|
||||
IssueSequence,
|
||||
)
|
||||
from plane.utils.grouper import (
|
||||
issue_group_values,
|
||||
@@ -58,8 +59,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):
|
||||
@@ -197,6 +197,23 @@ class IssueViewSet(BaseViewSet):
|
||||
"workspace__id",
|
||||
]
|
||||
|
||||
def process_issue_id(self, issue_id, project_id):
|
||||
# Check if the pk is an integer or a UUID
|
||||
type, value = is_int_or_uuid(issue_id)
|
||||
if type == "int":
|
||||
# Get the issue_id from the sequence_id
|
||||
sequence = (
|
||||
IssueSequence.objects.filter(
|
||||
sequence=value,
|
||||
project_id=project_id,
|
||||
)
|
||||
.values("issue_id")
|
||||
.first()
|
||||
)
|
||||
# Call the parent dispatch method
|
||||
return sequence.get("issue_id")
|
||||
return issue_id
|
||||
|
||||
def get_queryset(self):
|
||||
return (
|
||||
Issue.issue_objects.filter(
|
||||
@@ -410,67 +427,76 @@ class IssueViewSet(BaseViewSet):
|
||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
def retrieve(self, request, slug, project_id, pk=None):
|
||||
pk = self.process_issue_id(pk, project_id)
|
||||
issue = (
|
||||
self.get_queryset()
|
||||
.filter(pk=pk)
|
||||
.annotate(
|
||||
label_ids=Coalesce(
|
||||
ArrayAgg(
|
||||
"labels__id",
|
||||
distinct=True,
|
||||
filter=~Q(labels__id__isnull=True),
|
||||
(
|
||||
self.get_queryset()
|
||||
.annotate(
|
||||
label_ids=Coalesce(
|
||||
ArrayAgg(
|
||||
"labels__id",
|
||||
distinct=True,
|
||||
filter=~Q(labels__id__isnull=True),
|
||||
),
|
||||
Value([], output_field=ArrayField(UUIDField())),
|
||||
),
|
||||
Value([], output_field=ArrayField(UUIDField())),
|
||||
),
|
||||
assignee_ids=Coalesce(
|
||||
ArrayAgg(
|
||||
"assignees__id",
|
||||
distinct=True,
|
||||
filter=~Q(assignees__id__isnull=True)
|
||||
& Q(assignees__member_project__is_active=True),
|
||||
assignee_ids=Coalesce(
|
||||
ArrayAgg(
|
||||
"assignees__id",
|
||||
distinct=True,
|
||||
filter=~Q(assignees__id__isnull=True)
|
||||
& Q(assignees__member_project__is_active=True),
|
||||
),
|
||||
Value([], output_field=ArrayField(UUIDField())),
|
||||
),
|
||||
Value([], output_field=ArrayField(UUIDField())),
|
||||
),
|
||||
module_ids=Coalesce(
|
||||
ArrayAgg(
|
||||
"issue_module__module_id",
|
||||
distinct=True,
|
||||
filter=~Q(issue_module__module_id__isnull=True),
|
||||
),
|
||||
Value([], output_field=ArrayField(UUIDField())),
|
||||
),
|
||||
)
|
||||
.prefetch_related(
|
||||
Prefetch(
|
||||
"issue_reactions",
|
||||
queryset=IssueReaction.objects.select_related(
|
||||
"issue", "actor"
|
||||
module_ids=Coalesce(
|
||||
ArrayAgg(
|
||||
"issue_module__module_id",
|
||||
distinct=True,
|
||||
filter=~Q(issue_module__module_id__isnull=True),
|
||||
),
|
||||
Value([], output_field=ArrayField(UUIDField())),
|
||||
),
|
||||
)
|
||||
)
|
||||
.prefetch_related(
|
||||
Prefetch(
|
||||
"issue_attachment",
|
||||
queryset=IssueAttachment.objects.select_related("issue"),
|
||||
.prefetch_related(
|
||||
Prefetch(
|
||||
"issue_reactions",
|
||||
queryset=IssueReaction.objects.select_related(
|
||||
"issue", "actor"
|
||||
),
|
||||
)
|
||||
)
|
||||
)
|
||||
.prefetch_related(
|
||||
Prefetch(
|
||||
"issue_link",
|
||||
queryset=IssueLink.objects.select_related("created_by"),
|
||||
.prefetch_related(
|
||||
Prefetch(
|
||||
"issue_attachment",
|
||||
queryset=IssueAttachment.objects.select_related(
|
||||
"issue"
|
||||
),
|
||||
)
|
||||
)
|
||||
)
|
||||
.annotate(
|
||||
is_subscribed=Exists(
|
||||
IssueSubscriber.objects.filter(
|
||||
workspace__slug=slug,
|
||||
project_id=project_id,
|
||||
issue_id=OuterRef("pk"),
|
||||
subscriber=request.user,
|
||||
.prefetch_related(
|
||||
Prefetch(
|
||||
"issue_link",
|
||||
queryset=IssueLink.objects.select_related(
|
||||
"created_by"
|
||||
),
|
||||
)
|
||||
)
|
||||
.annotate(
|
||||
is_subscribed=Exists(
|
||||
IssueSubscriber.objects.filter(
|
||||
workspace__slug=slug,
|
||||
project_id=project_id,
|
||||
issue_id=OuterRef("pk"),
|
||||
subscriber=request.user,
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
).first()
|
||||
.filter(pk=pk)
|
||||
.first()
|
||||
)
|
||||
|
||||
if not issue:
|
||||
return Response(
|
||||
{"error": "The required object does not exist."},
|
||||
@@ -481,6 +507,7 @@ class IssueViewSet(BaseViewSet):
|
||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||
|
||||
def partial_update(self, request, slug, project_id, pk=None):
|
||||
pk = self.process_issue_id(pk, project_id)
|
||||
issue = self.get_queryset().filter(pk=pk).first()
|
||||
|
||||
if not issue:
|
||||
@@ -515,8 +542,11 @@ class IssueViewSet(BaseViewSet):
|
||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
def destroy(self, request, slug, project_id, pk=None):
|
||||
pk = self.process_issue_id(pk, project_id)
|
||||
issue = Issue.objects.get(
|
||||
workspace__slug=slug, project_id=project_id, pk=pk
|
||||
workspace__slug=slug,
|
||||
project_id=project_id,
|
||||
pk=pk,
|
||||
)
|
||||
issue.delete()
|
||||
issue_activity.delay(
|
||||
|
||||
@@ -50,6 +50,7 @@ from plane.db.models import (
|
||||
)
|
||||
from plane.utils.cache import cache_response
|
||||
from plane.bgtasks.webhook_task import model_activity
|
||||
from plane.utils.valid_int_uuid import is_uuid
|
||||
|
||||
|
||||
class ProjectViewSet(BaseViewSet):
|
||||
@@ -61,6 +62,25 @@ class ProjectViewSet(BaseViewSet):
|
||||
ProjectBasePermission,
|
||||
]
|
||||
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
pk = kwargs.get("pk")
|
||||
# Check if the project identifier is a UUID
|
||||
if pk and not is_uuid(pk):
|
||||
# Check if the project identifier is a name
|
||||
project = (
|
||||
ProjectIdentifier.objects.filter(
|
||||
name=pk, workspace__slug=kwargs.get("slug")
|
||||
)
|
||||
.values("project")
|
||||
.first()
|
||||
)
|
||||
if project:
|
||||
# Update the project identifier with the UUID
|
||||
kwargs["pk"] = project["project"]
|
||||
|
||||
# Call the parent dispatch method
|
||||
return super().dispatch(request, *args, **kwargs)
|
||||
|
||||
def get_queryset(self):
|
||||
sort_order = ProjectMember.objects.filter(
|
||||
member=self.request.user,
|
||||
@@ -484,32 +504,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 +547,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,
|
||||
)
|
||||
|
||||
@@ -442,47 +442,53 @@ def model_activity(
|
||||
slug,
|
||||
origin=None,
|
||||
):
|
||||
"""Function takes in two json and computes differences between keys of both the json"""
|
||||
if current_instance is None:
|
||||
webhook_activity.delay(
|
||||
event=model_name,
|
||||
verb="created",
|
||||
field=None,
|
||||
old_value=None,
|
||||
new_value=None,
|
||||
actor_id=actor_id,
|
||||
slug=slug,
|
||||
current_site=origin,
|
||||
event_id=model_id,
|
||||
old_identifier=None,
|
||||
new_identifier=None,
|
||||
try:
|
||||
"""Function takes in two json and computes differences between keys of both the json"""
|
||||
if current_instance is None:
|
||||
webhook_activity.delay(
|
||||
event=model_name,
|
||||
verb="created",
|
||||
field=None,
|
||||
old_value=None,
|
||||
new_value=None,
|
||||
actor_id=actor_id,
|
||||
slug=slug,
|
||||
current_site=origin,
|
||||
event_id=model_id,
|
||||
old_identifier=None,
|
||||
new_identifier=None,
|
||||
)
|
||||
return
|
||||
|
||||
# Load the current instance
|
||||
current_instance = (
|
||||
json.loads(current_instance)
|
||||
if current_instance is not None
|
||||
else None
|
||||
)
|
||||
|
||||
# Loop through all keys in requested data and check the current value and requested value
|
||||
for key in requested_data:
|
||||
# Check if key is present in current instance or not
|
||||
if key in current_instance:
|
||||
current_value = current_instance.get(key, None)
|
||||
requested_value = requested_data.get(key, None)
|
||||
if current_value != requested_value:
|
||||
webhook_activity.delay(
|
||||
event=model_name,
|
||||
verb="updated",
|
||||
field=key,
|
||||
old_value=current_value,
|
||||
new_value=requested_value,
|
||||
actor_id=actor_id,
|
||||
slug=slug,
|
||||
current_site=origin,
|
||||
event_id=model_id,
|
||||
old_identifier=None,
|
||||
new_identifier=None,
|
||||
)
|
||||
|
||||
return
|
||||
except Exception as e:
|
||||
log_exception(e)
|
||||
return
|
||||
|
||||
# Load the current instance
|
||||
current_instance = (
|
||||
json.loads(current_instance) if current_instance is not None else None
|
||||
)
|
||||
|
||||
# Loop through all keys in requested data and check the current value and requested value
|
||||
for key in requested_data:
|
||||
# Check if key is present in current instance or not
|
||||
if key in current_instance:
|
||||
current_value = current_instance.get(key, None)
|
||||
requested_value = requested_data.get(key, None)
|
||||
if current_value != requested_value:
|
||||
webhook_activity.delay(
|
||||
event=model_name,
|
||||
verb="updated",
|
||||
field=key,
|
||||
old_value=current_value,
|
||||
new_value=requested_value,
|
||||
actor_id=actor_id,
|
||||
slug=slug,
|
||||
current_site=origin,
|
||||
event_id=model_id,
|
||||
old_identifier=None,
|
||||
new_identifier=None,
|
||||
)
|
||||
|
||||
return
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
# 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),
|
||||
migrations.AlterField(
|
||||
model_name="issue",
|
||||
name="sequence_id",
|
||||
field=models.IntegerField(
|
||||
db_index=True, default=1, verbose_name="Issue Sequence ID"
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="issuesequence",
|
||||
name="issue",
|
||||
field=models.OneToOneField(
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
related_name="issue_sequence",
|
||||
to="db.issue",
|
||||
),
|
||||
),
|
||||
]
|
||||
@@ -7,9 +7,8 @@ from django.contrib.postgres.fields import ArrayField
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.core.validators import MaxValueValidator, MinValueValidator
|
||||
from django.db import models
|
||||
from django.db.models.signals import post_save
|
||||
from django.dispatch import receiver
|
||||
from django.utils import timezone
|
||||
from django.db import transaction
|
||||
|
||||
# Module imports
|
||||
from plane.utils.html_processor import strip_tags
|
||||
@@ -174,6 +173,8 @@ class Issue(ProjectBaseModel):
|
||||
ordering = ("-created_at",)
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
# is the model being saved for the first time?
|
||||
is_new = self._state.adding
|
||||
# This means that the model isn't saved to the database yet
|
||||
if self.state is None:
|
||||
try:
|
||||
@@ -205,32 +206,43 @@ class Issue(ProjectBaseModel):
|
||||
self.completed_at = None
|
||||
except ImportError:
|
||||
pass
|
||||
# Atomic transaction to ensure that the sequence is created only if the issue is created
|
||||
with transaction.atomic():
|
||||
if is_new:
|
||||
# Get the maximum display_id value from the database
|
||||
last_id = IssueSequence.objects.filter(
|
||||
project=self.project
|
||||
).aggregate(largest=models.Max("sequence"))["largest"]
|
||||
# aggregate can return None! Check it first.
|
||||
# If it isn't none, just use the last ID specified (which should be the greatest) and add one to it
|
||||
if last_id:
|
||||
self.sequence_id = last_id + 1
|
||||
else:
|
||||
self.sequence_id = 1
|
||||
|
||||
if self._state.adding:
|
||||
# Get the maximum display_id value from the database
|
||||
last_id = IssueSequence.objects.filter(
|
||||
project=self.project
|
||||
).aggregate(largest=models.Max("sequence"))["largest"]
|
||||
# aggregate can return None! Check it first.
|
||||
# If it isn't none, just use the last ID specified (which should be the greatest) and add one to it
|
||||
if last_id:
|
||||
self.sequence_id = last_id + 1
|
||||
else:
|
||||
self.sequence_id = 1
|
||||
largest_sort_order = Issue.objects.filter(
|
||||
project=self.project, state=self.state
|
||||
).aggregate(largest=models.Max("sort_order"))["largest"]
|
||||
if largest_sort_order is not None:
|
||||
self.sort_order = largest_sort_order + 10000
|
||||
|
||||
largest_sort_order = Issue.objects.filter(
|
||||
project=self.project, state=self.state
|
||||
).aggregate(largest=models.Max("sort_order"))["largest"]
|
||||
if largest_sort_order is not None:
|
||||
self.sort_order = largest_sort_order + 10000
|
||||
|
||||
# Strip the html tags using html parser
|
||||
self.description_stripped = (
|
||||
None
|
||||
if (self.description_html == "" or self.description_html is None)
|
||||
else strip_tags(self.description_html)
|
||||
)
|
||||
super(Issue, self).save(*args, **kwargs)
|
||||
# Strip the html tags using html parser
|
||||
self.description_stripped = (
|
||||
None
|
||||
if (
|
||||
self.description_html == ""
|
||||
or self.description_html is None
|
||||
)
|
||||
else strip_tags(self.description_html)
|
||||
)
|
||||
super(Issue, self).save(*args, **kwargs)
|
||||
# Create a sequence for the issue
|
||||
if is_new:
|
||||
IssueSequence.objects.create(
|
||||
issue=self,
|
||||
sequence=self.sequence_id,
|
||||
project=self.project,
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
"""Return name of the issue"""
|
||||
@@ -667,14 +679,3 @@ class IssueVote(ProjectBaseModel):
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.issue.name} {self.actor.email}"
|
||||
|
||||
|
||||
# TODO: Find a better method to save the model
|
||||
@receiver(post_save, sender=Issue)
|
||||
def create_issue_sequence(sender, instance, created, **kwargs):
|
||||
if created:
|
||||
IssueSequence.objects.create(
|
||||
issue=instance,
|
||||
sequence=instance.sequence_id,
|
||||
project=instance.project,
|
||||
)
|
||||
|
||||
@@ -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"]
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
29
apiserver/plane/utils/valid_int_uuid.py
Normal file
29
apiserver/plane/utils/valid_int_uuid.py
Normal file
@@ -0,0 +1,29 @@
|
||||
# Python imports
|
||||
import uuid
|
||||
|
||||
|
||||
def is_uuid(value):
|
||||
try:
|
||||
# Check if the value is a valid UUID
|
||||
uuid.UUID(str(value))
|
||||
return True
|
||||
except ValueError:
|
||||
return False
|
||||
|
||||
|
||||
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
|
||||
Reference in New Issue
Block a user