feat: page and page-blocks (#468)

* dev: initiate paper models

* feat: page and page-blocks

* dev: page id filter for page blocks
This commit is contained in:
pablohashescobar 2023-03-22 01:34:10 +05:30 committed by GitHub
parent 025c8b3835
commit f7dbc5e9c0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 392 additions and 1 deletions

View File

@ -60,3 +60,5 @@ from .integration import (
) )
from .importer import ImporterSerializer from .importer import ImporterSerializer
from .page import PageSerializer, PageBlockSerializer, PageFavoriteSerializer

View File

@ -0,0 +1,46 @@
# Third party imports
from rest_framework import serializers
# Module imports
from .base import BaseSerializer
from .issue import IssueFlatSerializer
from plane.db.models import Page, PageBlock, PageFavorite
class PageSerializer(BaseSerializer):
is_favorite = serializers.BooleanField(read_only=True)
class Meta:
model = Page
fields = "__all__"
read_only_fields = [
"workspace",
"project",
"owned_by",
]
class PageBlockSerializer(BaseSerializer):
issue_detail = IssueFlatSerializer(source="issue", read_only=True)
class Meta:
model = PageBlock
fields = "__all__"
read_only_fields = [
"workspace",
"project",
"page",
]
class PageFavoriteSerializer(BaseSerializer):
page_detail = PageSerializer(source="page", read_only=True)
class Meta:
model = PageFavorite
fields = "__all__"
read_only_fields = [
"workspace",
"project",
"user",
]

View File

@ -100,6 +100,12 @@ from plane.api.views import (
ModuleFavoriteViewSet, ModuleFavoriteViewSet,
ModuleLinkViewSet, ModuleLinkViewSet,
## End Modules ## End Modules
# Pages
PageViewSet,
PageBlockViewSet,
PageFavoriteViewSet,
CreateIssueFromPageBlockEndpoint,
## End Pages
# Api Tokens # Api Tokens
ApiTokenEndpoint, ApiTokenEndpoint,
## End Api Tokens ## End Api Tokens
@ -899,6 +905,74 @@ urlpatterns = [
name="user-favorite-module", name="user-favorite-module",
), ),
## End Modules ## End Modules
# Pages
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/pages/",
PageViewSet.as_view(
{
"get": "list",
"post": "create",
}
),
name="project-pages",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/pages/<uuid:pk>/",
PageViewSet.as_view(
{
"get": "retrieve",
"patch": "partial_update",
"delete": "destroy",
}
),
name="project-pages",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/pages/<uuid:page_id>/page-blocks/",
PageBlockViewSet.as_view(
{
"get": "list",
"post": "create",
}
),
name="project-page-blocks",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/pages/<uuid:page_id>/page-blocks/<uuid:pk>/",
PageBlockViewSet.as_view(
{
"get": "retrieve",
"patch": "partial_update",
"delete": "destroy",
}
),
name="project-page-blocks",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/user-favorite-pages/",
PageFavoriteViewSet.as_view(
{
"get": "list",
"post": "create",
}
),
name="user-favorite-pages",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/user-favorite-pages/<uuid:page_id>/",
PageFavoriteViewSet.as_view(
{
"delete": "destroy",
}
),
name="user-favorite-pages",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/pages/<uuid:page_id>/page-blocks/<uuid:page_block_id>/issues/",
CreateIssueFromPageBlockEndpoint.as_view(),
name="page-block-issues",
),
## End Pages
# API Tokens # API Tokens
path("api-tokens/", ApiTokenEndpoint.as_view(), name="api-tokens"), path("api-tokens/", ApiTokenEndpoint.as_view(), name="api-tokens"),
path("api-tokens/<uuid:pk>/", ApiTokenEndpoint.as_view(), name="api-tokens"), path("api-tokens/<uuid:pk>/", ApiTokenEndpoint.as_view(), name="api-tokens"),

View File

@ -109,3 +109,5 @@ from .importer import (
UpdateServiceImportStatusEndpoint, UpdateServiceImportStatusEndpoint,
BulkImportIssuesEndpoint, BulkImportIssuesEndpoint,
) )
from .page import PageViewSet, PageBlockViewSet, PageFavoriteViewSet, CreateIssueFromPageBlockEndpoint

View File

@ -0,0 +1,184 @@
# Django imports
from django.db import IntegrityError
from django.db.models import Exists, OuterRef, Q
# Third party imports
from rest_framework import status
from rest_framework.response import Response
from sentry_sdk import capture_exception
# Module imports
from .base import BaseViewSet, BaseAPIView
from plane.api.permissions import ProjectEntityPermission
from plane.db.models import (
Page,
PageBlock,
PageFavorite,
Issue,
IssueAssignee,
IssueActivity,
)
from plane.api.serializers import (
PageSerializer,
PageBlockSerializer,
PageFavoriteSerializer,
IssueSerializer,
)
class PageViewSet(BaseViewSet):
serializer_class = PageSerializer
model = Page
permission_classes = [
ProjectEntityPermission,
]
def get_queryset(self):
subquery = PageFavorite.objects.filter(
user=self.request.user,
page_id=OuterRef("pk"),
project_id=self.kwargs.get("project_id"),
workspace__slug=self.kwargs.get("slug"),
)
return self.filter_queryset(
super()
.get_queryset()
.filter(workspace__slug=self.kwargs.get("slug"))
.filter(project_id=self.kwargs.get("project_id"))
.filter(project__project_projectmember__member=self.request.user)
.filter(Q(owned_by=self.request.user) | Q(access=0))
.select_related("project")
.select_related("workspace")
.select_related("owned_by")
.annotate(is_favorite=Exists(subquery))
.distinct()
)
def perform_create(self, serializer):
serializer.save(
project_id=self.kwargs.get("project_id"), owned_by=self.request.user
)
class PageBlockViewSet(BaseViewSet):
serializer_class = PageBlockSerializer
model = PageBlock
permission_classes = [
ProjectEntityPermission,
]
def get_queryset(self):
return self.filter_queryset(
super()
.get_queryset()
.filter(workspace__slug=self.kwargs.get("slug"))
.filter(project_id=self.kwargs.get("project_id"))
.filter(page_id=self.kwargs.get("page_id"))
.filter(project__project_projectmember__member=self.request.user)
.select_related("project")
.select_related("workspace")
.select_related("page")
.select_related("issue")
.distinct()
)
def perform_create(self, serializer):
serializer.save(
project_id=self.kwargs.get("project_id"),
page_id=self.kwargs.get("page_id"),
)
class PageFavoriteViewSet(BaseViewSet):
permission_classes = [
ProjectEntityPermission,
]
serializer_class = PageFavoriteSerializer
model = PageFavorite
def get_queryset(self):
return self.filter_queryset(
super()
.get_queryset()
.filter(workspace__slug=self.kwargs.get("slug"))
.filter(user=self.request.user)
.select_related("page", "page__owned_by")
)
def create(self, request, slug, project_id):
try:
serializer = PageFavoriteSerializer(data=request.data)
if serializer.is_valid():
serializer.save(user=request.user, project_id=project_id)
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
except IntegrityError as e:
if "already exists" in str(e):
return Response(
{"error": "The page is already added to favorites"},
status=status.HTTP_410_GONE,
)
else:
capture_exception(e)
return Response(
{"error": "Something went wrong please try again later"},
status=status.HTTP_400_BAD_REQUEST,
)
except Exception as e:
capture_exception(e)
return Response(
{"error": "Something went wrong please try again later"},
status=status.HTTP_400_BAD_REQUEST,
)
def destroy(self, request, slug, project_id, page_id):
try:
page_favorite = PageFavorite.objects.get(
project=project_id,
user=request.user,
workspace__slug=slug,
page_id=page_id,
)
page_favorite.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
except PageFavorite.DoesNotExist:
return Response(
{"error": "Page is not in favorites"},
status=status.HTTP_400_BAD_REQUEST,
)
except Exception as e:
capture_exception(e)
return Response(
{"error": "Something went wrong please try again later"},
status=status.HTTP_400_BAD_REQUEST,
)
class CreateIssueFromPageBlockEndpoint(BaseAPIView):
def post(self, request, slug, project_id, page_id, page_block_id):
try:
page_block = PageBlock.objects.get(
pk=page_block_id,
workspace__slug=slug,
project_id=project_id,
page_id=page_id,
)
issue = Issue.objects.create(name=page_block.name, project_id=project_id)
_ = IssueAssignee.objects.create(
issue=issue, assignee=request.user, project_id=project_id
)
page_block.issue = issue
page_block.save()
return Response(IssueSerializer(issue).data, status=status.HTTP_200_OK)
except PageBlock.DoesNotExist:
return Response(
{"error": "Page Block does not exist"}, status=status.HTTP_404_NOT_FOUND
)
except Exception as e:
capture_exception(e)
return Response(
{"error": "Something went wrong please try again later"},
status=status.HTTP_400_BAD_REQUEST,
)

View File

@ -60,3 +60,5 @@ from .integration import (
) )
from .importer import Importer from .importer import Importer
from .page import Page, PageBlock, PageFavorite

View File

@ -85,7 +85,7 @@ class Issue(ProjectBaseModel):
pass pass
else: else:
try: try:
from plane.db.models import State from plane.db.models import State, PageBlock
# Get the completed states of the project # Get the completed states of the project
completed_states = State.objects.filter( completed_states = State.objects.filter(
@ -94,7 +94,15 @@ class Issue(ProjectBaseModel):
# Check if the current issue state and completed state id are same # Check if the current issue state and completed state id are same
if self.state.id in completed_states: if self.state.id in completed_states:
self.completed_at = timezone.now() self.completed_at = timezone.now()
# check if there are any page blocks
PageBlock.objects.filter(issue_id=self.id).filter().update(
completed_at=timezone.now()
)
else: else:
PageBlock.objects.filter(issue_id=self.id).filter().update(
completed_at=None
)
self.completed_at = None self.completed_at = None
except ImportError: except ImportError:

View File

@ -0,0 +1,73 @@
# Django imports
from django.db import models
from django.conf import settings
# Module imports
from . import ProjectBaseModel
class Page(ProjectBaseModel):
name = models.CharField(max_length=255)
description = models.JSONField(default=dict, blank=True)
description_html = models.TextField(blank=True, default="<p></p>")
description_stripped = models.TextField(blank=True, null=True)
owned_by = models.ForeignKey(
settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name="pages"
)
access = models.PositiveSmallIntegerField(
choices=((0, "Public"), (1, "Private")), default=0
)
class Meta:
verbose_name = "Page"
verbose_name_plural = "Pages"
db_table = "pages"
ordering = ("-created_at",)
def __str__(self):
"""Return owner email and page name"""
return f"{self.owned_by.email} <{self.name}>"
class PageBlock(ProjectBaseModel):
page = models.ForeignKey("db.Page", on_delete=models.CASCADE, related_name="blocks")
name = models.CharField(max_length=255)
description = models.JSONField(default=dict, blank=True)
description_html = models.TextField(blank=True, default="<p></p>")
description_stripped = models.TextField(blank=True, null=True)
issue = models.ForeignKey(
"db.Issue", on_delete=models.SET_NULL, related_name="blocks", null=True
)
completed_at = models.DateTimeField(null=True)
class Meta:
verbose_name = "Page Block"
verbose_name_plural = "Page Blocks"
db_table = "page_blocks"
ordering = ("-created_at",)
def __str__(self):
"""Return page and page block"""
return f"{self.page.name} <{self.name}>"
class PageFavorite(ProjectBaseModel):
user = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
related_name="page_favorites",
)
page = models.ForeignKey(
"db.Page", on_delete=models.CASCADE, related_name="page_favorites"
)
class Meta:
unique_together = ["page", "user"]
verbose_name = "Page Favorite"
verbose_name_plural = "Page Favorites"
db_table = "page_favorites"
ordering = ("-created_at",)
def __str__(self):
"""Return user and the page"""
return f"{self.user.email} <{self.page.name}>"