From 03f9ca45d8d8388de3064525d20ac63e2ee4c796 Mon Sep 17 00:00:00 2001 From: pablohashescobar Date: Wed, 11 Oct 2023 19:24:45 +0530 Subject: [PATCH] dev: create model for project setting --- apiserver/plane/api/serializers/__init__.py | 1 + apiserver/plane/api/serializers/project.py | 12 ++ apiserver/plane/api/urls.py | 21 +++ apiserver/plane/api/views/__init__.py | 1 + apiserver/plane/api/views/project.py | 20 ++- .../db/migrations/0046_auto_20231011_1334.py | 123 +++++++++++++++++ apiserver/plane/db/models/__init__.py | 1 + apiserver/plane/db/models/project.py | 124 ++++++++++++------ 8 files changed, 264 insertions(+), 39 deletions(-) create mode 100644 apiserver/plane/db/migrations/0046_auto_20231011_1334.py diff --git a/apiserver/plane/api/serializers/__init__.py b/apiserver/plane/api/serializers/__init__.py index dbf7ca049..69d48bd75 100644 --- a/apiserver/plane/api/serializers/__init__.py +++ b/apiserver/plane/api/serializers/__init__.py @@ -11,6 +11,7 @@ from .workspace import ( ) from .project import ( ProjectSerializer, + ProjectSettingSerializer, ProjectDetailSerializer, ProjectMemberSerializer, ProjectMemberInviteSerializer, diff --git a/apiserver/plane/api/serializers/project.py b/apiserver/plane/api/serializers/project.py index 49d986cae..a49b3313e 100644 --- a/apiserver/plane/api/serializers/project.py +++ b/apiserver/plane/api/serializers/project.py @@ -10,6 +10,7 @@ from plane.api.serializers.workspace import WorkSpaceSerializer, WorkspaceLiteSe from plane.api.serializers.user import UserLiteSerializer, UserAdminLiteSerializer from plane.db.models import ( Project, + ProjectSetting, ProjectMember, ProjectMemberInvite, ProjectIdentifier, @@ -192,3 +193,14 @@ class ProjectPublicMemberSerializer(BaseSerializer): "project", "member", ] + + +class ProjectSettingSerializer(BaseSerializer): + + class Meta: + model = ProjectSetting + fields = "__all__" + read_only_fields = [ + "workspace", + "project", + ] diff --git a/apiserver/plane/api/urls.py b/apiserver/plane/api/urls.py index 6e7a3821f..7176453ac 100644 --- a/apiserver/plane/api/urls.py +++ b/apiserver/plane/api/urls.py @@ -59,6 +59,7 @@ from plane.api.views import ( ## End File Assets # Projects ProjectViewSet, + ProjectSettingViewSet, InviteProjectEndpoint, ProjectMemberViewSet, ProjectMemberEndpoint, @@ -481,6 +482,26 @@ urlpatterns = [ ), name="project", ), + path( + "workspaces//projects//settings/", + ProjectSettingViewSet.as_view( + { + "get": "list", + "post": "create", + } + ), + name="project", + ), + path( + "workspaces//projects//settings/", + ProjectSettingViewSet.as_view( + { + "get": "retrieve", + "patch": "partial_update", + } + ), + name="project", + ), path( "workspaces//project-identifiers/", ProjectIdentifierEndpoint.as_view(), diff --git a/apiserver/plane/api/views/__init__.py b/apiserver/plane/api/views/__init__.py index 8a974f868..62f8d8ac4 100644 --- a/apiserver/plane/api/views/__init__.py +++ b/apiserver/plane/api/views/__init__.py @@ -1,5 +1,6 @@ from .project import ( ProjectViewSet, + ProjectSettingViewSet, ProjectMemberViewSet, UserProjectInvitationsViewset, InviteProjectEndpoint, diff --git a/apiserver/plane/api/views/project.py b/apiserver/plane/api/views/project.py index 1ba227177..cbbf5b0a8 100644 --- a/apiserver/plane/api/views/project.py +++ b/apiserver/plane/api/views/project.py @@ -29,11 +29,11 @@ from sentry_sdk import capture_exception from .base import BaseViewSet, BaseAPIView from plane.api.serializers import ( ProjectSerializer, + ProjectSettingSerializer, ProjectMemberSerializer, ProjectDetailSerializer, ProjectMemberInviteSerializer, ProjectFavoriteSerializer, - IssueLiteSerializer, ProjectDeployBoardSerializer, ProjectMemberAdminSerializer, ) @@ -67,6 +67,7 @@ from plane.db.models import ( ModuleMember, Inbox, ProjectDeployBoard, + ProjectSetting, ) from plane.bgtasks.project_invitation_task import project_invitation @@ -1246,3 +1247,20 @@ class ProjectPublicCoverImagesEndpoint(BaseAPIView): except Exception as e: capture_exception(e) return Response([], status=status.HTTP_200_OK) + + +class ProjectSettingViewSet(BaseViewSet): + model = ProjectSetting + permission_classes = [ + ProjectBasePermission, + ] + serializer_class = ProjectSettingSerializer + + def get_queryset(self): + super().get_queryset().filter( + workspace__slug=self.kwargs.get("slug"), + project_id=self.kwargs.get("project_id"), + ) + + def perform_create(self, serializer): + serializer.save(project_id=self.kwargs.get("project_id")) diff --git a/apiserver/plane/db/migrations/0046_auto_20231011_1334.py b/apiserver/plane/db/migrations/0046_auto_20231011_1334.py new file mode 100644 index 000000000..8e782852c --- /dev/null +++ b/apiserver/plane/db/migrations/0046_auto_20231011_1334.py @@ -0,0 +1,123 @@ +# Generated by Django 4.2.3 on 2023-10-11 13:34 +import uuid +from django.db import migrations, models +import django.db.models.deletion +from django.conf import settings + + +def create_project_settings(apps, schema_editor): + Project = apps.get_model("db", "Project") + ProjectSetting = apps.get_model("db", "ProjectSetting") + + ProjectSetting.objects.bulk_create( + [ + ProjectSetting( + project=project, + workspace_id=project.workspace_id, + archive_in=project.archive_in, + close_in=project.close_in, + cycle_view=project.cycle_view, + default_assignee=project.default_assignee, + default_state=project.default_state, + estimate=project.estimate, + inbox_view=project.inbox_view, + issue_views_view=project.issue_views_view, + module_view=project.module_view, + page_view=project.page_view, + project_lead=project.project_lead, + ) + for project in Project.objects.all() + ], + batch_size=1000, + ) + + +class Migration(migrations.Migration): + + dependencies = [ + ('db', '0045_issueactivity_epoch_workspacemember_issue_props_and_more'), + ] + + operations = [ + migrations.CreateModel( + name='ProjectSetting', + 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)), + ('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)), + ('archive_in', models.IntegerField(default=0, validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(12)])), + ('close_in', models.IntegerField(default=0, validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(12)])), + ('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')), + ('default_assignee', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='default_assignee', to=settings.AUTH_USER_MODEL)), + ('default_state', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='default_state', to='db.state')), + ('estimate', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='projects', to='db.estimate')), + ('project', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='project_settings', to='db.project')), + ('project_lead', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='project_lead', to=settings.AUTH_USER_MODEL)), + ('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_settings', to='db.workspace')), + ], + options={ + 'verbose_name': 'Project Settings', + 'verbose_name_plural': 'Project Settings', + 'db_table': 'project_settings', + 'ordering': ('-created_at',), + }, + ), + migrations.RunPython(create_project_settings), + migrations.RemoveField( + model_name='project', + name='archive_in', + ), + migrations.RemoveField( + name='close_in', + ), + migrations.RemoveField( + model_name='project', + name='cycle_view', + ), + migrations.RemoveField( + model_name='project', + name='default_assignee', + ), + migrations.RemoveField( + model_name='project', + name='default_state', + ), + migrations.RemoveField( + model_name='project', + name='description_html', + ), + migrations.RemoveField( + model_name='project', + name='description_text', + ), + migrations.RemoveField( + model_name='project', + name='estimate', + ), + migrations.RemoveField( + model_name='project', + name='inbox_view', + ), + migrations.RemoveField( + model_name='project', + name='issue_views_view', + ), + migrations.RemoveField( + model_name='project', + name='module_view', + ), + migrations.RemoveField( + model_name='project', + name='page_view', + ), + migrations.RemoveField( + model_name='project', + name='project_lead', + ), + ] diff --git a/apiserver/plane/db/models/__init__.py b/apiserver/plane/db/models/__init__.py index 9496b5906..6690adf36 100644 --- a/apiserver/plane/db/models/__init__.py +++ b/apiserver/plane/db/models/__init__.py @@ -13,6 +13,7 @@ from .workspace import ( from .project import ( Project, + ProjectSetting, ProjectMember, ProjectBaseModel, ProjectMemberInvite, diff --git a/apiserver/plane/db/models/project.py b/apiserver/plane/db/models/project.py index 4cd2134ac..622eb2786 100644 --- a/apiserver/plane/db/models/project.py +++ b/apiserver/plane/db/models/project.py @@ -38,7 +38,7 @@ def get_default_props(): }, "display_filters": { "group_by": None, - "order_by": '-created_at', + "order_by": "-created_at", "type": None, "sub_issue": True, "show_empty_groups": True, @@ -56,12 +56,6 @@ class Project(BaseModel): NETWORK_CHOICES = ((0, "Secret"), (2, "Public")) name = models.CharField(max_length=255, verbose_name="Project Name") description = models.TextField(verbose_name="Project Description", blank=True) - description_text = models.JSONField( - verbose_name="Project Description RT", blank=True, null=True - ) - description_html = models.JSONField( - verbose_name="Project Description HTML", blank=True, null=True - ) network = models.PositiveSmallIntegerField(default=2, choices=NETWORK_CHOICES) workspace = models.ForeignKey( "db.WorkSpace", on_delete=models.CASCADE, related_name="workspace_project" @@ -70,40 +64,40 @@ class Project(BaseModel): max_length=12, verbose_name="Project Identifier", ) - default_assignee = models.ForeignKey( - settings.AUTH_USER_MODEL, - on_delete=models.CASCADE, - related_name="default_assignee", - null=True, - blank=True, - ) - project_lead = models.ForeignKey( - settings.AUTH_USER_MODEL, - on_delete=models.CASCADE, - related_name="project_lead", - null=True, - blank=True, - ) + # default_assignee = models.ForeignKey( + # settings.AUTH_USER_MODEL, + # on_delete=models.CASCADE, + # related_name="default_assignee", + # null=True, + # blank=True, + # ) + # project_lead = models.ForeignKey( + # settings.AUTH_USER_MODEL, + # on_delete=models.CASCADE, + # related_name="project_lead", + # null=True, + # blank=True, + # ) 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) + # 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", on_delete=models.SET_NULL, related_name="projects", null=True - ) - archive_in = models.IntegerField( - default=0, validators=[MinValueValidator(0), MaxValueValidator(12)] - ) - close_in = models.IntegerField( - default=0, validators=[MinValueValidator(0), MaxValueValidator(12)] - ) - default_state = models.ForeignKey( - "db.State", on_delete=models.SET_NULL, null=True, related_name="default_state" - ) + # estimate = models.ForeignKey( + # "db.Estimate", on_delete=models.SET_NULL, related_name="projects", null=True + # ) + # archive_in = models.IntegerField( + # default=0, validators=[MinValueValidator(0), MaxValueValidator(12)] + # ) + # close_in = models.IntegerField( + # default=0, validators=[MinValueValidator(0), MaxValueValidator(12)] + # ) + # default_state = models.ForeignKey( + # "db.State", on_delete=models.SET_NULL, null=True, related_name="default_state" + # ) def __str__(self): """Return name of the project""" @@ -121,6 +115,60 @@ class Project(BaseModel): return super().save(*args, **kwargs) +class ProjectSetting(BaseModel): + workspace = models.ForeignKey( + "db.Workspace", on_delete=models.CASCADE, related_name="project_settings" + ) + project = models.OneToOneField( + "db.Project", on_delete=models.CASCADE, related_name="project_settings" + ) + default_assignee = models.ForeignKey( + settings.AUTH_USER_MODEL, + on_delete=models.CASCADE, + related_name="default_assignee", + null=True, + blank=True, + ) + project_lead = models.ForeignKey( + settings.AUTH_USER_MODEL, + on_delete=models.CASCADE, + related_name="project_lead", + null=True, + blank=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) + estimate = models.ForeignKey( + "db.Estimate", on_delete=models.SET_NULL, related_name="projects", null=True + ) + archive_in = models.IntegerField( + default=0, validators=[MinValueValidator(0), MaxValueValidator(12)] + ) + close_in = models.IntegerField( + default=0, validators=[MinValueValidator(0), MaxValueValidator(12)] + ) + default_state = models.ForeignKey( + "db.State", on_delete=models.SET_NULL, null=True, related_name="default_state" + ) + + def save(self, *args, **kwargs): + self.workspace = self.project.workspace + super(ProjectSetting, self).save(*args, **kwargs) + + def __str__(self): + """Return name of the project""" + return f"{self.project.name} <{self.workspace.name}>" + + class Meta: + verbose_name = "Project Settings" + verbose_name_plural = "Project Settings" + db_table = "project_settings" + ordering = ("-created_at",) + + class ProjectBaseModel(BaseModel): project = models.ForeignKey( Project, on_delete=models.CASCADE, related_name="project_%(class)s"