From b6c911f4840dea56a1cc3201a6cec652ee0ece8c Mon Sep 17 00:00:00 2001 From: pablohashescobar Date: Mon, 10 Apr 2023 18:14:09 +0530 Subject: [PATCH 1/2] feat: workspace themes --- apiserver/plane/api/serializers/__init__.py | 1 + apiserver/plane/api/serializers/workspace.py | 21 ++++++- apiserver/plane/api/urls.py | 28 +++++++++ apiserver/plane/api/views/__init__.py | 2 + apiserver/plane/api/views/workspace.py | 64 ++++++++++++++++++++ apiserver/plane/db/models/__init__.py | 1 + apiserver/plane/db/models/workspace.py | 26 +++++++- 7 files changed, 139 insertions(+), 4 deletions(-) diff --git a/apiserver/plane/api/serializers/__init__.py b/apiserver/plane/api/serializers/__init__.py index 776864ee0..2adff8299 100644 --- a/apiserver/plane/api/serializers/__init__.py +++ b/apiserver/plane/api/serializers/__init__.py @@ -11,6 +11,7 @@ from .workspace import ( TeamSerializer, WorkSpaceMemberInviteSerializer, WorkspaceLiteSerializer, + WorkspaceThemeSerializer, ) from .project import ( ProjectSerializer, diff --git a/apiserver/plane/api/serializers/workspace.py b/apiserver/plane/api/serializers/workspace.py index 7b3cb1896..4f4d13f76 100644 --- a/apiserver/plane/api/serializers/workspace.py +++ b/apiserver/plane/api/serializers/workspace.py @@ -5,8 +5,15 @@ from rest_framework import serializers from .base import BaseSerializer from .user import UserLiteSerializer -from plane.db.models import User, Workspace, WorkspaceMember, Team, TeamMember -from plane.db.models import Workspace, WorkspaceMember, Team, WorkspaceMemberInvite +from plane.db.models import ( + User, + Workspace, + WorkspaceMember, + Team, + TeamMember, + WorkspaceMemberInvite, + WorkspaceTheme, +) class WorkSpaceSerializer(BaseSerializer): @@ -100,3 +107,13 @@ class WorkspaceLiteSerializer(BaseSerializer): "id", ] read_only_fields = fields + + +class WorkspaceThemeSerializer(BaseSerializer): + class Meta: + model = WorkspaceTheme + fields = "__all__" + read_only_fields = [ + "workspace", + "actor", + ] diff --git a/apiserver/plane/api/urls.py b/apiserver/plane/api/urls.py index c927b8719..9baf19a54 100644 --- a/apiserver/plane/api/urls.py +++ b/apiserver/plane/api/urls.py @@ -42,6 +42,8 @@ from plane.api.views import ( UserActivityGraphEndpoint, UserIssueCompletedGraphEndpoint, UserWorkspaceDashboardEndpoint, + WorkspaceThemeViewSet, + UserWorkspaceThemeEndpoint, ## End Workspaces # File Assets FileAssetEndpoint, @@ -350,6 +352,32 @@ urlpatterns = [ WorkspaceMemberUserViewsEndpoint.as_view(), name="workspace-member-details", ), + path( + "workspaces//workspace-themes/", + WorkspaceThemeViewSet.as_view( + { + "get": "list", + "post": "create", + } + ), + name="workspace-themes", + ), + path( + "workspaces//workspace-themes//", + WorkspaceThemeViewSet.as_view( + { + "get": "retrieve", + "patch": "partial_update", + "delete": "destroy", + } + ), + name="workspace-themes", + ), + path( + "users/me/workspaces//workspace-themes/", + UserWorkspaceThemeEndpoint.as_view(), + name="user-workspace-themes", + ), ## End Workspaces ## # Projects path( diff --git a/apiserver/plane/api/views/__init__.py b/apiserver/plane/api/views/__init__.py index 781005b89..f12507ea3 100644 --- a/apiserver/plane/api/views/__init__.py +++ b/apiserver/plane/api/views/__init__.py @@ -40,6 +40,8 @@ from .workspace import ( UserActivityGraphEndpoint, UserIssueCompletedGraphEndpoint, UserWorkspaceDashboardEndpoint, + WorkspaceThemeViewSet, + UserWorkspaceThemeEndpoint, ) from .state import StateViewSet from .shortcut import ShortCutViewSet diff --git a/apiserver/plane/api/views/workspace.py b/apiserver/plane/api/views/workspace.py index a1c18f995..69dd45f1b 100644 --- a/apiserver/plane/api/views/workspace.py +++ b/apiserver/plane/api/views/workspace.py @@ -36,6 +36,7 @@ from plane.api.serializers import ( WorkSpaceMemberInviteSerializer, UserLiteSerializer, ProjectMemberSerializer, + WorkspaceThemeSerializer, ) from plane.api.views.base import BaseAPIView from . import BaseViewSet @@ -48,6 +49,7 @@ from plane.db.models import ( ProjectMember, IssueActivity, Issue, + WorkspaceTheme, ) from plane.api.permissions import WorkSpaceBasePermission, WorkSpaceAdminPermission from plane.bgtasks.workspace_invitation_task import workspace_invitation @@ -752,3 +754,65 @@ class UserWorkspaceDashboardEndpoint(BaseAPIView): {"error": "Something went wrong please try again later"}, status=status.HTTP_400_BAD_REQUEST, ) + + +class WorkspaceThemeViewSet(BaseViewSet): + permission_classes = [ + WorkSpaceAdminPermission, + ] + model = WorkspaceTheme + serializer_class = WorkspaceThemeSerializer + + def get_queryset(self): + return super().get_queryset().filter(workspace__slug=self.kwargs.get("slug")) + + def create(self, request, slug): + try: + workspace = Workspace.objects.get(slug=slug) + serializer = WorkspaceThemeSerializer(data=request.data) + if serializer.is_valid(): + serializer.save(workspace=workspace, actor=request.user) + return Response(serializer.data, status=status.HTTP_201_CREATED) + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + except Workspace.DoesNotExist: + return Response( + {"error": "Workspace does not exist"}, + 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 UserWorkspaceThemeEndpoint(BaseAPIView): + def post(self, request, slug): + try: + workspace_theme_id = request.data.get("workspace_theme_id", False) + + if not workspace_theme_id: + return Response( + {"error": "Workspace Theme ID is required"}, + status=status.HTTP_400_BAD_REQUEST, + ) + + workspace_theme = WorkspaceTheme.objects.get( + workspace__slug=slug, pk=workspace_theme_id + ) + + # Update at member level + workspace_member = WorkspaceMember.objects.get( + workspace__slug=slug, member=request.user + ) + workspace_member.workspace_theme = workspace_theme + workspace_member.save() + serializer = WorkSpaceMemberSerializer(workspace_member) + 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 5df899362..b6ffe428c 100644 --- a/apiserver/plane/db/models/__init__.py +++ b/apiserver/plane/db/models/__init__.py @@ -8,6 +8,7 @@ from .workspace import ( Team, WorkspaceMemberInvite, TeamMember, + WorkspaceTheme, ) from .project import ( diff --git a/apiserver/plane/db/models/workspace.py b/apiserver/plane/db/models/workspace.py index 5715bb304..9c3e1eecd 100644 --- a/apiserver/plane/db/models/workspace.py +++ b/apiserver/plane/db/models/workspace.py @@ -36,7 +36,6 @@ class Workspace(BaseModel): ordering = ("-created_at",) - class WorkspaceMember(BaseModel): workspace = models.ForeignKey( "db.Workspace", on_delete=models.CASCADE, related_name="workspace_member" @@ -49,6 +48,9 @@ class WorkspaceMember(BaseModel): role = models.PositiveSmallIntegerField(choices=ROLE_CHOICES, default=10) company_role = models.TextField(null=True, blank=True) view_props = models.JSONField(null=True, blank=True) + workspace_theme = models.ForeignKey( + "db.WorkspaceTheme", on_delete=models.SET_NULL, related_name="theme", null=True + ) class Meta: unique_together = ["workspace", "member"] @@ -111,7 +113,6 @@ class Team(BaseModel): class TeamMember(BaseModel): - workspace = models.ForeignKey( Workspace, on_delete=models.CASCADE, related_name="team_member" ) @@ -129,3 +130,24 @@ class TeamMember(BaseModel): verbose_name_plural = "Team Members" db_table = "team_members" ordering = ("-created_at",) + + +class WorkspaceTheme(BaseModel): + workspace = models.ForeignKey( + "db.Workspace", on_delete=models.CASCADE, related_name="themes" + ) + name = models.CharField(max_length=300) + actor = models.ForeignKey( + settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name="themes" + ) + colors = models.JSONField(default=dict) + + def __str__(self): + return str(self.name) + str(self.actor.email) + + class Meta: + unique_together = ["workspace", "name"] + verbose_name = "Workspace Theme" + verbose_name_plural = "Workspace Themes" + db_table = "workspace_themes" + ordering = ("-created_at",) From bc457846fe9b4b6b17527ccdc3cb5d82beac1292 Mon Sep 17 00:00:00 2001 From: pablohashescobar Date: Mon, 10 Apr 2023 23:19:01 +0530 Subject: [PATCH 2/2] chore: move theme setting in user level from workspace level --- apiserver/plane/api/urls.py | 6 ------ apiserver/plane/api/views/__init__.py | 1 - apiserver/plane/api/views/workspace.py | 30 -------------------------- apiserver/plane/db/models/user.py | 1 + apiserver/plane/db/models/workspace.py | 3 --- 5 files changed, 1 insertion(+), 40 deletions(-) diff --git a/apiserver/plane/api/urls.py b/apiserver/plane/api/urls.py index 9baf19a54..34554fff5 100644 --- a/apiserver/plane/api/urls.py +++ b/apiserver/plane/api/urls.py @@ -43,7 +43,6 @@ from plane.api.views import ( UserIssueCompletedGraphEndpoint, UserWorkspaceDashboardEndpoint, WorkspaceThemeViewSet, - UserWorkspaceThemeEndpoint, ## End Workspaces # File Assets FileAssetEndpoint, @@ -373,11 +372,6 @@ urlpatterns = [ ), name="workspace-themes", ), - path( - "users/me/workspaces//workspace-themes/", - UserWorkspaceThemeEndpoint.as_view(), - name="user-workspace-themes", - ), ## End Workspaces ## # Projects path( diff --git a/apiserver/plane/api/views/__init__.py b/apiserver/plane/api/views/__init__.py index f12507ea3..82eb49e44 100644 --- a/apiserver/plane/api/views/__init__.py +++ b/apiserver/plane/api/views/__init__.py @@ -41,7 +41,6 @@ from .workspace import ( UserIssueCompletedGraphEndpoint, UserWorkspaceDashboardEndpoint, WorkspaceThemeViewSet, - UserWorkspaceThemeEndpoint, ) from .state import StateViewSet from .shortcut import ShortCutViewSet diff --git a/apiserver/plane/api/views/workspace.py b/apiserver/plane/api/views/workspace.py index 69dd45f1b..915ade2fc 100644 --- a/apiserver/plane/api/views/workspace.py +++ b/apiserver/plane/api/views/workspace.py @@ -786,33 +786,3 @@ class WorkspaceThemeViewSet(BaseViewSet): status=status.HTTP_400_BAD_REQUEST, ) - -class UserWorkspaceThemeEndpoint(BaseAPIView): - def post(self, request, slug): - try: - workspace_theme_id = request.data.get("workspace_theme_id", False) - - if not workspace_theme_id: - return Response( - {"error": "Workspace Theme ID is required"}, - status=status.HTTP_400_BAD_REQUEST, - ) - - workspace_theme = WorkspaceTheme.objects.get( - workspace__slug=slug, pk=workspace_theme_id - ) - - # Update at member level - workspace_member = WorkspaceMember.objects.get( - workspace__slug=slug, member=request.user - ) - workspace_member.workspace_theme = workspace_theme - workspace_member.save() - serializer = WorkSpaceMemberSerializer(workspace_member) - 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/user.py b/apiserver/plane/db/models/user.py index 8a30981f3..334ec3e13 100644 --- a/apiserver/plane/db/models/user.py +++ b/apiserver/plane/db/models/user.py @@ -72,6 +72,7 @@ class User(AbstractBaseUser, PermissionsMixin): my_issues_prop = models.JSONField(null=True) role = models.CharField(max_length=300, null=True, blank=True) is_bot = models.BooleanField(default=False) + theme = models.JSONField(default=dict) USERNAME_FIELD = "email" diff --git a/apiserver/plane/db/models/workspace.py b/apiserver/plane/db/models/workspace.py index 9c3e1eecd..b00d53013 100644 --- a/apiserver/plane/db/models/workspace.py +++ b/apiserver/plane/db/models/workspace.py @@ -48,9 +48,6 @@ class WorkspaceMember(BaseModel): role = models.PositiveSmallIntegerField(choices=ROLE_CHOICES, default=10) company_role = models.TextField(null=True, blank=True) view_props = models.JSONField(null=True, blank=True) - workspace_theme = models.ForeignKey( - "db.WorkspaceTheme", on_delete=models.SET_NULL, related_name="theme", null=True - ) class Meta: unique_together = ["workspace", "member"]