From 6c6aa3b0d50dadb8378b6b37aed26546b9dd035b Mon Sep 17 00:00:00 2001 From: pablohashescobar Date: Mon, 29 Jan 2024 14:13:42 +0530 Subject: [PATCH] chore: separate out project feature model --- apiserver/plane/api/serializers/project.py | 2 + apiserver/plane/api/views/project.py | 7 +- apiserver/plane/app/serializers/__init__.py | 1 + apiserver/plane/app/serializers/project.py | 14 ++ apiserver/plane/app/urls/project.py | 6 + apiserver/plane/app/views/__init__.py | 1 + apiserver/plane/app/views/project.py | 46 ++++++- ...view_remove_project_inbox_view_and_more.py | 120 ++++++++++++++++++ apiserver/plane/db/models/__init__.py | 1 + apiserver/plane/db/models/project.py | 35 ++++- 10 files changed, 224 insertions(+), 9 deletions(-) create mode 100644 apiserver/plane/db/migrations/0058_remove_project_cycle_view_remove_project_inbox_view_and_more.py diff --git a/apiserver/plane/api/serializers/project.py b/apiserver/plane/api/serializers/project.py index 342cc1a81..de38c0f64 100644 --- a/apiserver/plane/api/serializers/project.py +++ b/apiserver/plane/api/serializers/project.py @@ -8,6 +8,7 @@ from plane.db.models import ( WorkspaceMember, State, Estimate, + ProjectFeature, ) from .base import BaseSerializer @@ -78,6 +79,7 @@ class ProjectSerializer(BaseSerializer): project = Project.objects.create( **validated_data, workspace_id=self.context["workspace_id"] ) + _ = ProjectIdentifier.objects.create( name=project.identifier, project=project, diff --git a/apiserver/plane/api/views/project.py b/apiserver/plane/api/views/project.py index cb1f7dc7b..0acc1c973 100644 --- a/apiserver/plane/api/views/project.py +++ b/apiserver/plane/api/views/project.py @@ -11,7 +11,6 @@ from rest_framework.serializers import ValidationError from plane.db.models import ( Workspace, Project, - ProjectFavorite, ProjectMember, ProjectDeployBoard, State, @@ -19,6 +18,7 @@ from plane.db.models import ( Module, IssueProperty, Inbox, + ProjectFeature, ) from plane.app.permissions import ProjectBasePermission from plane.api.serializers import ProjectSerializer @@ -149,6 +149,11 @@ class ProjectAPIEndpoint(WebhookMixin, BaseAPIView): if serializer.is_valid(): serializer.save() + # features + _ = ProjectFeature.objects.create( + project_id=serializer.data["id"] + ) + # Add the user as Administrator to the project project_member = ProjectMember.objects.create( project_id=serializer.data["id"], diff --git a/apiserver/plane/app/serializers/__init__.py b/apiserver/plane/app/serializers/__init__.py index 0d72f9192..11c13e197 100644 --- a/apiserver/plane/app/serializers/__init__.py +++ b/apiserver/plane/app/serializers/__init__.py @@ -21,6 +21,7 @@ from .workspace import ( ) from .project import ( ProjectSerializer, + ProjectFeatureSerializer, ProjectListSerializer, ProjectDetailSerializer, ProjectMemberSerializer, diff --git a/apiserver/plane/app/serializers/project.py b/apiserver/plane/app/serializers/project.py index 999233442..fdd326ed1 100644 --- a/apiserver/plane/app/serializers/project.py +++ b/apiserver/plane/app/serializers/project.py @@ -16,6 +16,7 @@ from plane.db.models import ( ProjectFavorite, ProjectDeployBoard, ProjectPublicMember, + ProjectFeature, ) @@ -47,6 +48,8 @@ class ProjectSerializer(BaseSerializer): project = Project.objects.create( **validated_data, workspace_id=self.context["workspace_id"] ) + + # Create identifiers _ = ProjectIdentifier.objects.create( name=project.identifier, project=project, @@ -237,3 +240,14 @@ class ProjectPublicMemberSerializer(BaseSerializer): "project", "member", ] + + +class ProjectFeatureSerializer(BaseSerializer): + + class Meta: + model = ProjectFeature + fields = "__all__" + read_only_fields = [ + "project", + "workspace", + ] diff --git a/apiserver/plane/app/urls/project.py b/apiserver/plane/app/urls/project.py index f8ecac4c0..d315cd7c7 100644 --- a/apiserver/plane/app/urls/project.py +++ b/apiserver/plane/app/urls/project.py @@ -14,6 +14,7 @@ from plane.app.views import ( ProjectPublicCoverImagesEndpoint, ProjectDeployBoardViewSet, UserProjectRolesEndpoint, + ProjectFeatureEndpoint, ) @@ -175,4 +176,9 @@ urlpatterns = [ ), name="project-deploy-board", ), + path( + "workspaces//projects//project-features/", + ProjectFeatureEndpoint.as_view(), + name="project-feature", + ), ] diff --git a/apiserver/plane/app/views/__init__.py b/apiserver/plane/app/views/__init__.py index 0a959a667..e53b118eb 100644 --- a/apiserver/plane/app/views/__init__.py +++ b/apiserver/plane/app/views/__init__.py @@ -12,6 +12,7 @@ from .project import ( ProjectPublicCoverImagesEndpoint, ProjectDeployBoardViewSet, UserProjectRolesEndpoint, + ProjectFeatureEndpoint, ) from .user import ( UserEndpoint, diff --git a/apiserver/plane/app/views/project.py b/apiserver/plane/app/views/project.py index 5d2f95673..deccd7c6b 100644 --- a/apiserver/plane/app/views/project.py +++ b/apiserver/plane/app/views/project.py @@ -37,6 +37,7 @@ from plane.app.serializers import ( ProjectDeployBoardSerializer, ProjectMemberAdminSerializer, ProjectMemberRoleSerializer, + ProjectFeatureSerializer, ) from plane.app.permissions import ( @@ -62,6 +63,7 @@ from plane.db.models import ( Inbox, ProjectDeployBoard, IssueProperty, + ProjectFeature, ) from plane.bgtasks.project_invitation_task import project_invitation @@ -203,6 +205,10 @@ class ProjectViewSet(WebhookMixin, BaseViewSet): if serializer.is_valid(): serializer.save() + _ = ProjectFeature.objects.create( + project_id=serializer.data["id"] + ) + # Add the user as Administrator to the project project_member = ProjectMember.objects.create( project_id=serializer.data["id"], @@ -686,9 +692,14 @@ class ProjectMemberViewSet(BaseViewSet): ) bulk_project_members = [] - member_roles = {member.get("member_id"): member.get("role") for member in members} + member_roles = { + member.get("member_id"): member.get("role") for member in members + } # Update roles in the members array based on the member_roles dictionary - for project_member in ProjectMember.objects.filter(project_id=project_id, member_id__in=[member.get("member_id") for member in members]): + for project_member in ProjectMember.objects.filter( + project_id=project_id, + member_id__in=[member.get("member_id") for member in members], + ): project_member.role = member_roles[str(project_member.member_id)] project_member.is_active = True bulk_project_members.append(project_member) @@ -734,7 +745,10 @@ class ProjectMemberViewSet(BaseViewSet): bulk_issue_props, batch_size=10, ignore_conflicts=True ) - project_members = ProjectMember.objects.filter(project_id=project_id, member_id__in=[member.get("member_id") for member in members]) + project_members = ProjectMember.objects.filter( + project_id=project_id, + member_id__in=[member.get("member_id") for member in members], + ) serializer = ProjectMemberRoleSerializer(project_members, many=True) return Response(serializer.data, status=status.HTTP_201_CREATED) @@ -1138,3 +1152,29 @@ class UserProjectRolesEndpoint(BaseAPIView): for member in project_members } return Response(project_members, status=status.HTTP_200_OK) + + +class ProjectFeatureEndpoint(BaseAPIView): + permission_classes = [ + ProjectBasePermission, + ] + + def get(self, request, slug, project_id): + project_feature = ProjectFeature.objects.get( + workspace__slug=slug, project_id=project_id + ) + serializer = ProjectFeatureSerializer(project_feature) + return Response(serializer.data, status=status.HTTP_200_OK) + + def patch(self, request, slug, project_id): + project_feature = ProjectFeature.objects.get( + workspace__slug=slug, + project_id=project_id, + ) + serializer = ProjectFeatureSerializer( + project_feature, 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) diff --git a/apiserver/plane/db/migrations/0058_remove_project_cycle_view_remove_project_inbox_view_and_more.py b/apiserver/plane/db/migrations/0058_remove_project_cycle_view_remove_project_inbox_view_and_more.py new file mode 100644 index 000000000..077bdba96 --- /dev/null +++ b/apiserver/plane/db/migrations/0058_remove_project_cycle_view_remove_project_inbox_view_and_more.py @@ -0,0 +1,120 @@ +# Generated by Django 4.2.7 on 2024-01-29 07:11 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion +import uuid + + +def create_project_preferences(apps, schema_editor): + ProjectFeature = apps.get_model("db", "ProjectFeature") + Project = apps.get_model("db", "Project") + + ProjectFeature.objects.bulk_create( + [ + ProjectFeature( + project_id=project.get("id"), + workspace_id=project.get("workspace_id"), + cycles=project.get("cycle_view"), + modules=project.get("module_view"), + views=project.get("issue_views_view"), + pages=project.get("page_view"), + inbox=project.get("inbox_view"), + ) + for project in Project.objects.values( + "id", + "workspace_id", + "cycle_view", + "inbox_view", + "issue_views_view", + "page_view", + "module_view", + ) + ], + batch_size=100, + ) + + +class Migration(migrations.Migration): + dependencies = [ + ("db", "0057_auto_20240122_0901"), + ] + + operations = [ + migrations.CreateModel( + name="ProjectFeature", + fields=[ + ( + "created_at", + models.DateTimeField( + auto_now_add=True, verbose_name="Created At" + ), + ), + ( + "updated_at", + models.DateTimeField( + auto_now=True, verbose_name="Last Modified At" + ), + ), + ( + "id", + models.UUIDField( + db_index=True, + default=uuid.uuid4, + editable=False, + primary_key=True, + serialize=False, + unique=True, + ), + ), + ("modules", models.BooleanField(default=True)), + ("cycles", models.BooleanField(default=True)), + ("views", models.BooleanField(default=True)), + ("pages", models.BooleanField(default=True)), + ("inbox", models.BooleanField(default=False)), + ( + "created_by", + models.ForeignKey( + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="%(class)s_created_by", + to=settings.AUTH_USER_MODEL, + verbose_name="Created By", + ), + ), + ( + "project", + models.OneToOneField( + on_delete=django.db.models.deletion.CASCADE, + related_name="features", + to="db.project", + ), + ), + ( + "updated_by", + models.ForeignKey( + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="%(class)s_updated_by", + to=settings.AUTH_USER_MODEL, + verbose_name="Last Modified By", + ), + ), + ( + "workspace", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="project_features", + to="db.workspace", + ), + ), + ], + options={ + "verbose_name": "Project Feature", + "verbose_name_plural": "Project Features", + "db_table": "project_features", + "ordering": ("-created_at",), + }, + ), + migrations.RunPython(create_project_preferences), + ] diff --git a/apiserver/plane/db/models/__init__.py b/apiserver/plane/db/models/__init__.py index d9096bd01..88064fd27 100644 --- a/apiserver/plane/db/models/__init__.py +++ b/apiserver/plane/db/models/__init__.py @@ -22,6 +22,7 @@ from .project import ( ProjectFavorite, ProjectDeployBoard, ProjectPublicMember, + ProjectFeature, ) from .issue import ( diff --git a/apiserver/plane/db/models/project.py b/apiserver/plane/db/models/project.py index b93174724..6eaae334c 100644 --- a/apiserver/plane/db/models/project.py +++ b/apiserver/plane/db/models/project.py @@ -89,11 +89,6 @@ class Project(BaseModel): ) emoji = models.CharField(max_length=255, null=True, blank=True) icon_prop = models.JSONField(null=True) - module_view = models.BooleanField(default=True) - cycle_view = models.BooleanField(default=True) - issue_views_view = models.BooleanField(default=True) - page_view = models.BooleanField(default=True) - inbox_view = models.BooleanField(default=False) cover_image = models.URLField(blank=True, null=True, max_length=800) estimate = models.ForeignKey( "db.Estimate", @@ -146,6 +141,36 @@ class ProjectBaseModel(BaseModel): super(ProjectBaseModel, self).save(*args, **kwargs) +class ProjectFeature(BaseModel): + workspace = models.ForeignKey( + "db.Workspace", + on_delete=models.CASCADE, + related_name="project_features", + ) + project = models.OneToOneField( + Project, on_delete=models.CASCADE, related_name="features" + ) + modules = models.BooleanField(default=False) + cycles = models.BooleanField(default=False) + views = models.BooleanField(default=False) + pages = models.BooleanField(default=True) + inbox = models.BooleanField(default=False) + + def save(self, *args, **kwargs): + self.workspace = self.project.workspace + super(ProjectFeature, self).save(*args, **kwargs) + + def __str__(self): + """Return feature of the project""" + return f"{self.project.name} <{self.id}>" + + class Meta: + verbose_name = "Project Feature" + verbose_name_plural = "Project Features" + db_table = "project_features" + ordering = ("-created_at",) + + class ProjectMemberInvite(ProjectBaseModel): email = models.CharField(max_length=255) accepted = models.BooleanField(default=False)