From 4e3c9397eae66f30df38125d37f5d93dc6675fc4 Mon Sep 17 00:00:00 2001 From: pablohashescobar <118773738+pablohashescobar@users.noreply.github.com> Date: Wed, 22 Mar 2023 23:41:30 +0530 Subject: [PATCH] feat: page labels and favorites (#487) * dev: initiate page labels * dev: page labels * dev: my pages endpoint --- apiserver/plane/api/serializers/page.py | 82 ++++++++++++++++++++----- apiserver/plane/api/urls.py | 6 ++ apiserver/plane/api/views/__init__.py | 10 ++- apiserver/plane/api/views/page.py | 37 ++++++++++- apiserver/plane/db/models/__init__.py | 2 +- apiserver/plane/db/models/page.py | 21 +++++++ 6 files changed, 139 insertions(+), 19 deletions(-) diff --git a/apiserver/plane/api/serializers/page.py b/apiserver/plane/api/serializers/page.py index 047b6f2d0..b4397df91 100644 --- a/apiserver/plane/api/serializers/page.py +++ b/apiserver/plane/api/serializers/page.py @@ -3,21 +3,8 @@ 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", - ] +from .issue import IssueFlatSerializer, LabelSerializer +from plane.db.models import Page, PageBlock, PageFavorite, PageLabel, Label class PageBlockSerializer(BaseSerializer): @@ -33,6 +20,71 @@ class PageBlockSerializer(BaseSerializer): ] +class PageSerializer(BaseSerializer): + is_favorite = serializers.BooleanField(read_only=True) + label_details = LabelSerializer(read_only=True, source="labels", many=True) + labels_list = serializers.ListField( + child=serializers.PrimaryKeyRelatedField(queryset=Label.objects.all()), + write_only=True, + required=False, + ) + + class Meta: + model = Page + fields = "__all__" + read_only_fields = [ + "workspace", + "project", + "owned_by", + ] + + def create(self, validated_data): + labels = validated_data.pop("labels_list", None) + project_id = self.context["project_id"] + owned_by_id = self.context["owned_by_id"] + page = Page.objects.create( + **validated_data, project_id=project_id, owned_by_id=owned_by_id + ) + + if labels is not None: + PageLabel.objects.bulk_create( + [ + PageLabel( + label=label, + page=page, + project_id=project_id, + 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_list", None) + if labels is not None: + PageLabel.objects.filter(issue=instance).delete() + PageLabel.objects.bulk_create( + [ + PageLabel( + label=label, + page=instance, + project_id=instance.project_id, + 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) + + class PageFavoriteSerializer(BaseSerializer): page_detail = PageSerializer(source="page", read_only=True) diff --git a/apiserver/plane/api/urls.py b/apiserver/plane/api/urls.py index 69df81c9e..f82d3160d 100644 --- a/apiserver/plane/api/urls.py +++ b/apiserver/plane/api/urls.py @@ -106,6 +106,7 @@ from plane.api.views import ( PageBlockViewSet, PageFavoriteViewSet, CreateIssueFromPageBlockEndpoint, + MyPagesEndpoint, ## End Pages # Api Tokens ApiTokenEndpoint, @@ -978,6 +979,11 @@ urlpatterns = [ CreateIssueFromPageBlockEndpoint.as_view(), name="page-block-issues", ), + path( + "workspaces//projects//user/pages/", + MyPagesEndpoint.as_view(), + name="my-pages", + ), ## End Pages # API Tokens path("api-tokens/", ApiTokenEndpoint.as_view(), name="api-tokens"), diff --git a/apiserver/plane/api/views/__init__.py b/apiserver/plane/api/views/__init__.py index f5dfb2bac..37293e087 100644 --- a/apiserver/plane/api/views/__init__.py +++ b/apiserver/plane/api/views/__init__.py @@ -108,7 +108,13 @@ from .importer import ( ImportServiceEndpoint, UpdateServiceImportStatusEndpoint, BulkImportIssuesEndpoint, - BulkImportModulesEndpoint + BulkImportModulesEndpoint, ) -from .page import PageViewSet, PageBlockViewSet, PageFavoriteViewSet, CreateIssueFromPageBlockEndpoint +from .page import ( + PageViewSet, + PageBlockViewSet, + PageFavoriteViewSet, + CreateIssueFromPageBlockEndpoint, + MyPagesEndpoint, +) diff --git a/apiserver/plane/api/views/page.py b/apiserver/plane/api/views/page.py index cff613bd4..473ac6f80 100644 --- a/apiserver/plane/api/views/page.py +++ b/apiserver/plane/api/views/page.py @@ -16,7 +16,6 @@ from plane.db.models import ( PageFavorite, Issue, IssueAssignee, - IssueActivity, ) from plane.api.serializers import ( PageSerializer, @@ -51,6 +50,7 @@ class PageViewSet(BaseViewSet): .select_related("workspace") .select_related("owned_by") .annotate(is_favorite=Exists(subquery)) + .prefetch_related("labels") .distinct() ) @@ -59,6 +59,25 @@ class PageViewSet(BaseViewSet): project_id=self.kwargs.get("project_id"), owned_by=self.request.user ) + def create(self, request, slug, project_id): + try: + serializer = PageSerializer( + data=request.data, + context={"project_id": project_id, "owned_by_id": request.user.id}, + ) + + if serializer.is_valid(): + serializer.save() + return Response(serializer.data, status=status.HTTP_201_CREATED) + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + + except Exception as e: + print(e) + return Response( + {"error": "Something went wrong please try again later"}, + status=status.HTTP_400_BAD_REQUEST, + ) + class PageBlockViewSet(BaseViewSet): serializer_class = PageBlockSerializer @@ -182,3 +201,19 @@ class CreateIssueFromPageBlockEndpoint(BaseAPIView): {"error": "Something went wrong please try again later"}, status=status.HTTP_400_BAD_REQUEST, ) + + +class MyPagesEndpoint(BaseAPIView): + def get(self, request, slug, project_id): + try: + pages = Page.objects.filter( + workspace__slug=slug, project_id=project_id, owned_by=request.user + ) + serializer = PageSerializer(pages, many=True) + return Response(serializer.data, status=status.HTTP_200_OK) + except Exception as e: + capture_exception(e) + return Response( + {"error": "Something went wrong please try again later"}, + status=status.HTTP_400_BAD_REQUEST, + ) diff --git a/apiserver/plane/db/models/__init__.py b/apiserver/plane/db/models/__init__.py index 648562425..8a3021741 100644 --- a/apiserver/plane/db/models/__init__.py +++ b/apiserver/plane/db/models/__init__.py @@ -61,4 +61,4 @@ from .integration import ( from .importer import Importer -from .page import Page, PageBlock, PageFavorite \ No newline at end of file +from .page import Page, PageBlock, PageFavorite, PageLabel \ No newline at end of file diff --git a/apiserver/plane/db/models/page.py b/apiserver/plane/db/models/page.py index 5770c7b09..7eee40171 100644 --- a/apiserver/plane/db/models/page.py +++ b/apiserver/plane/db/models/page.py @@ -17,6 +17,9 @@ class Page(ProjectBaseModel): access = models.PositiveSmallIntegerField( choices=((0, "Public"), (1, "Private")), default=0 ) + labels = models.ManyToManyField( + "db.Label", blank=True, related_name="pages", through="db.PageLabel" + ) class Meta: verbose_name = "Page" @@ -71,3 +74,21 @@ class PageFavorite(ProjectBaseModel): def __str__(self): """Return user and the page""" return f"{self.user.email} <{self.page.name}>" + + +class PageLabel(ProjectBaseModel): + label = models.ForeignKey( + "db.Label", on_delete=models.CASCADE, related_name="page_labels" + ) + page = models.ForeignKey( + "db.Page", on_delete=models.CASCADE, related_name="page_labels" + ) + + class Meta: + verbose_name = "Page Label" + verbose_name_plural = "Page Labels" + db_table = "page_labels" + ordering = ("-created_at",) + + def __str__(self): + return f"{self.page.name} {self.label.name}"