diff --git a/apiserver/plane/app/serializers/__init__.py b/apiserver/plane/app/serializers/__init__.py index 134eee891ab..796e68b4070 100644 --- a/apiserver/plane/app/serializers/__init__.py +++ b/apiserver/plane/app/serializers/__init__.py @@ -20,7 +20,8 @@ WorkspaceMemberMeSerializer, WorkspaceUserPropertiesSerializer, WorkspaceUserLinkSerializer, - WorkspaceRecentVisitSerializer + WorkspaceRecentVisitSerializer, + WorkspaceHomePreferenceSerializer, ) from .project import ( ProjectSerializer, diff --git a/apiserver/plane/app/serializers/workspace.py b/apiserver/plane/app/serializers/workspace.py index ad45cf1a82a..53368a6fcc6 100644 --- a/apiserver/plane/app/serializers/workspace.py +++ b/apiserver/plane/app/serializers/workspace.py @@ -16,10 +16,11 @@ WorkspaceUserProperties, WorkspaceUserLink, UserRecentVisit, - Issue, - Page, + Issue, + Page, Project, - ProjectMember + ProjectMember, + WorkspaceHomePreference, ) from plane.utils.constants import RESTRICTED_WORKSPACE_SLUGS @@ -27,6 +28,7 @@ from django.core.validators import URLValidator from django.core.exceptions import ValidationError + class WorkSpaceSerializer(DynamicBaseSerializer): owner = UserLiteSerializer(read_only=True) total_members = serializers.IntegerField(read_only=True) @@ -119,6 +121,7 @@ class Meta: fields = "__all__" read_only_fields = ["workspace", "user"] + class WorkspaceUserLinkSerializer(BaseSerializer): class Meta: model = WorkspaceUserLink @@ -129,7 +132,7 @@ def to_internal_value(self, data): url = data.get("url", "") if url and not url.startswith(("http://", "https://")): data["url"] = "http://" + url - + return super().to_internal_value(data) def validate_url(self, value): @@ -141,18 +144,29 @@ def validate_url(self, value): return value + class IssueRecentVisitSerializer(serializers.ModelSerializer): project_identifier = serializers.SerializerMethodField() class Meta: model = Issue - fields = ["name", "state", "priority", "assignees", "type", "sequence_id", "project_id", "project_identifier"] + fields = [ + "name", + "state", + "priority", + "assignees", + "type", + "sequence_id", + "project_id", + "project_identifier", + ] def get_project_identifier(self, obj): project = obj.project return project.identifier if project else None + class ProjectMemberSerializer(BaseSerializer): member = UserLiteSerializer(read_only=True) @@ -160,55 +174,66 @@ class Meta: model = ProjectMember fields = ["member"] + class ProjectRecentVisitSerializer(serializers.ModelSerializer): - project_members = serializers.SerializerMethodField() - + project_members = serializers.SerializerMethodField() + class Meta: model = Project fields = ["id", "name", "logo_props", "project_members", "identifier"] def get_project_members(self, obj): - members = ProjectMember.objects.filter(project_id=obj.id).select_related('member') + members = ProjectMember.objects.filter(project_id=obj.id).select_related( + "member" + ) serializer = ProjectMemberSerializer(members, many=True) return serializer.data - + + class PageRecentVisitSerializer(serializers.ModelSerializer): project_id = serializers.SerializerMethodField() project_identifier = serializers.SerializerMethodField() class Meta: model = Page - fields = ["id", "name", "logo_props", "project_id", "owned_by", "project_identifier"] + fields = [ + "id", + "name", + "logo_props", + "project_id", + "owned_by", + "project_identifier", + ] def get_project_id(self, obj): - return obj.project_id if hasattr(obj, 'project_id') else obj.projects.values_list('id', flat=True).first() - + return ( + obj.project_id + if hasattr(obj, "project_id") + else obj.projects.values_list("id", flat=True).first() + ) + def get_project_identifier(self, obj): project = obj.projects.first() return project.identifier if project else None + def get_entity_model_and_serializer(entity_type): entity_map = { "issue": (Issue, IssueRecentVisitSerializer), "page": (Page, PageRecentVisitSerializer), - "project": (Project, ProjectRecentVisitSerializer) + "project": (Project, ProjectRecentVisitSerializer), } return entity_map.get(entity_type, (None, None)) + class WorkspaceRecentVisitSerializer(BaseSerializer): entity_data = serializers.SerializerMethodField() class Meta: model = UserRecentVisit - fields = [ - "id", - "entity_name", - "entity_identifier", - "entity_data", - "visited_at" - ] + fields = ["id", "entity_name", "entity_identifier", "entity_data", "visited_at"] read_only_fields = ["workspace", "owner", "created_by", "updated_by"] def get_entity_data(self, obj): @@ -225,3 +250,10 @@ def get_entity_data(self, obj): except entity_model.DoesNotExist: return None return None + + +class WorkspaceHomePreferenceSerializer(BaseSerializer): + class Meta: + model = WorkspaceHomePreference + fields = ["key", "is_enabled", "sort_order"] + read_only_fields = ["worspace", "created_by", "update_by"] diff --git a/apiserver/plane/app/urls/workspace.py b/apiserver/plane/app/urls/workspace.py index f8cdbbd5b46..2bb3d5b1d12 100644 --- a/apiserver/plane/app/urls/workspace.py +++ b/apiserver/plane/app/urls/workspace.py @@ -28,7 +28,8 @@ WorkspaceFavoriteGroupEndpoint, WorkspaceDraftIssueViewSet, QuickLinkViewSet, - UserRecentVisitViewSet + UserRecentVisitViewSet, + WorkspacePreferenceViewSet, ) @@ -215,25 +216,33 @@ WorkspaceDraftIssueViewSet.as_view({"post": "create_draft_to_issue"}), name="workspace-drafts-issues", ), - # quick link path( "workspaces//quick-links/", QuickLinkViewSet.as_view({"get": "list", "post": "create"}), - name="workspace-quick-links" + name="workspace-quick-links", + ), + path( + "workspaces//quick-links//", + QuickLinkViewSet.as_view( + {"get": "retrieve", "patch": "partial_update", "delete": "destroy"} + ), + name="workspace-quick-links", + ), + # Widgets + path( + "workspaces//home-preferences/", + WorkspacePreferenceViewSet.as_view(), + name="workspace-home-preference", ), path( - "workspaces//quick-links//", - QuickLinkViewSet.as_view({ - "get": "retrieve", - "patch": "partial_update", - "delete": "destroy" - }), - name="workspace-quick-links" + "workspaces//home-preferences//", + WorkspacePreferenceViewSet.as_view(), + name="workspace-home-preference", ), path( "workspaces//recent-visits/", UserRecentVisitViewSet.as_view({"get": "list"}), - name="workspace-recent-visits" - ) + name="workspace-recent-visits", + ), ] diff --git a/apiserver/plane/app/views/__init__.py b/apiserver/plane/app/views/__init__.py index 19dbd4d8f08..53c9c0a7266 100644 --- a/apiserver/plane/app/views/__init__.py +++ b/apiserver/plane/app/views/__init__.py @@ -41,6 +41,7 @@ from .workspace.draft import WorkspaceDraftIssueViewSet +from .workspace.preference import WorkspacePreferenceViewSet from .workspace.favorite import ( WorkspaceFavoriteEndpoint, WorkspaceFavoriteGroupEndpoint, diff --git a/apiserver/plane/app/views/workspace/preference.py b/apiserver/plane/app/views/workspace/preference.py new file mode 100644 index 00000000000..21c06321cb0 --- /dev/null +++ b/apiserver/plane/app/views/workspace/preference.py @@ -0,0 +1,72 @@ +# Module imports +from ..base import BaseAPIView +from plane.db.models.workspace import WorkspaceHomePreference +from plane.app.permissions import allow_permission, ROLE +from plane.db.models import Workspace +from plane.app.serializers.workspace import WorkspaceHomePreferenceSerializer + +# Third party imports +from rest_framework.response import Response +from rest_framework import status + + +class WorkspacePreferenceViewSet(BaseAPIView): + model = WorkspaceHomePreference + + def get_serializer_class(self): + return WorkspaceHomePreferenceSerializer + + @allow_permission([ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST], level="WORKSPACE") + def get(self, request, slug): + workspace = Workspace.objects.get(slug=slug) + + get_preference = WorkspaceHomePreference.objects.filter( + user=request.user, workspace_id=workspace.id + ) + + create_preference_keys = [] + + keys = [key for key, _ in WorkspaceHomePreference.HomeWidgetKeys.choices] + + for preference in keys: + if preference not in get_preference.values_list("key", flat=True): + create_preference_keys.append(preference) + + preference = WorkspaceHomePreference.objects.bulk_create( + [ + WorkspaceHomePreference( + key=key, user=request.user, workspace=workspace + ) + for key in create_preference_keys + ], + batch_size=10, + ignore_conflicts=True, + ) + preference = WorkspaceHomePreference.objects.filter( + user=request.user, workspace_id=workspace.id + ) + + return Response( + preference.values("key", "is_enabled", "config", "sort_order"), + status=status.HTTP_200_OK, + ) + + @allow_permission([ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST], level="WORKSPACE") + def patch(self, request, slug, key): + preference = WorkspaceHomePreference.objects.filter( + key=key, workspace__slug=slug + ).first() + + if preference: + serializer = WorkspaceHomePreferenceSerializer( + preference, data=request.data, partial=True + ) + + if serializer.is_valid(): + serializer.save() + return Response(serializer.data, status=status.HTTP_200_OK) + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + + return Response( + {"detail": "Preference not found"}, status=status.HTTP_400_BAD_REQUEST + ) diff --git a/apiserver/plane/db/models/__init__.py b/apiserver/plane/db/models/__init__.py index 216e445e6b2..09b372fddcf 100644 --- a/apiserver/plane/db/models/__init__.py +++ b/apiserver/plane/db/models/__init__.py @@ -69,6 +69,7 @@ WorkspaceTheme, WorkspaceUserProperties, WorkspaceUserLink, + WorkspaceHomePreference ) from .favorite import UserFavorite