Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -110,3 +110,4 @@ build/
.react-router/
temp/
scripts/
.worktrees/
1 change: 1 addition & 0 deletions apps/api/plane/api/serializers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,3 +60,4 @@
from .invite import WorkspaceInviteSerializer
from .member import ProjectMemberSerializer
from .sticky import StickySerializer
from .page import PageSerializer
126 changes: 126 additions & 0 deletions apps/api/plane/api/serializers/page.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
# Copyright (c) 2023-present Plane Software, Inc. and contributors
# SPDX-License-Identifier: AGPL-3.0-only
# See the LICENSE file for details.

from django.db import transaction
from rest_framework import serializers

from plane.db.models import Label, Page, PageLabel, Project, ProjectPage

from .base import BaseSerializer


class PageSerializer(BaseSerializer):
label_ids = serializers.ListField(child=serializers.UUIDField(), read_only=True)
project_ids = serializers.ListField(child=serializers.UUIDField(), read_only=True)
labels = serializers.ListField(
child=serializers.PrimaryKeyRelatedField(queryset=Label.objects.none()),
write_only=True,
required=False,
)

class Meta:
model = Page
fields = [
"id",
"name",
"description_html",
"description_json",
"owned_by",
"access",
"color",
"labels",
"label_ids",
"parent",
"is_locked",
"archived_at",
"view_props",
"logo_props",
"project_ids",
"workspace",
"external_id",
"external_source",
"sort_order",
"created_at",
"updated_at",
"created_by",
"updated_by",
]
read_only_fields = ["workspace", "owned_by", "project_ids", "label_ids"]

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# Scope label choices to the current workspace to prevent cross-workspace
# label assignment.
workspace_id = None
project_id = self.context.get("project_id")
if project_id:
workspace_id = (
Project.objects.filter(pk=project_id).values_list("workspace_id", flat=True).first()
)
elif self.instance:
workspace_id = self.instance.workspace_id

if workspace_id:
self.fields["labels"].child = serializers.PrimaryKeyRelatedField(
queryset=Label.objects.filter(workspace_id=workspace_id)
)

def create(self, validated_data):
labels = validated_data.pop("labels", None)
project_id = self.context["project_id"]
owned_by_id = self.context["owned_by_id"]

project = Project.objects.get(pk=project_id)

with transaction.atomic():
page = Page.objects.create(
**validated_data,
owned_by_id=owned_by_id,
workspace_id=project.workspace_id,
)

ProjectPage.objects.create(
workspace_id=page.workspace_id,
project_id=project_id,
page_id=page.id,
created_by_id=page.created_by_id,
updated_by_id=page.updated_by_id,
)

if labels is not None:
PageLabel.objects.bulk_create(
[
PageLabel(
label=label,
page=page,
workspace_id=page.workspace_id,
created_by_id=page.created_by_id,
updated_by_id=page.updated_by_id,
)
for label in labels
],
batch_size=10,
)

return page

def update(self, instance, validated_data):
labels = validated_data.pop("labels", None)
with transaction.atomic():
if labels is not None:
PageLabel.objects.filter(page=instance).delete()
PageLabel.objects.bulk_create(
[
PageLabel(
label=label,
page=instance,
workspace_id=instance.workspace_id,
created_by_id=instance.created_by_id,
updated_by_id=instance.updated_by_id,
)
for label in labels
],
batch_size=10,
)
return super().update(instance, validated_data)
2 changes: 2 additions & 0 deletions apps/api/plane/api/urls/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
from .work_item import urlpatterns as work_item_patterns
from .invite import urlpatterns as invite_patterns
from .sticky import urlpatterns as sticky_patterns
from .page import urlpatterns as page_patterns

urlpatterns = [
*asset_patterns,
Expand All @@ -28,4 +29,5 @@
*work_item_patterns,
*invite_patterns,
*sticky_patterns,
*page_patterns,
]
35 changes: 35 additions & 0 deletions apps/api/plane/api/urls/page.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Copyright (c) 2023-present Plane Software, Inc. and contributors
# SPDX-License-Identifier: AGPL-3.0-only
# See the LICENSE file for details.

from django.urls import path

from plane.api.views.page import (
PageListCreateAPIEndpoint,
PageDetailAPIEndpoint,
PageArchiveUnarchiveAPIEndpoint,
PageLockUnlockAPIEndpoint,
)

urlpatterns = [
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/pages/",
PageListCreateAPIEndpoint.as_view(http_method_names=["get", "post"]),
name="page-list",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/pages/<uuid:pk>/",
PageDetailAPIEndpoint.as_view(http_method_names=["get", "patch", "delete"]),
name="page-detail",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/pages/<uuid:page_id>/archive/",
PageArchiveUnarchiveAPIEndpoint.as_view(http_method_names=["post", "delete"]),
name="page-archive",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/pages/<uuid:page_id>/lock/",
PageLockUnlockAPIEndpoint.as_view(http_method_names=["post", "delete"]),
name="page-lock",
),
]
7 changes: 7 additions & 0 deletions apps/api/plane/api/views/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,3 +61,10 @@
from .invite import WorkspaceInvitationsViewset

from .sticky import StickyViewSet

from .page import (
PageListCreateAPIEndpoint,
PageDetailAPIEndpoint,
PageArchiveUnarchiveAPIEndpoint,
PageLockUnlockAPIEndpoint,
)
Loading