From 1604f4585d276d48ecf9c4545fca43e640b7889f Mon Sep 17 00:00:00 2001 From: pablohashescobar Date: Thu, 12 Oct 2023 13:51:32 +0530 Subject: [PATCH] dev: create project settings --- apiserver/plane/api/serializers/__init__.py | 3 +- apiserver/plane/api/serializers/project.py | 18 ++++- apiserver/plane/api/views/estimate.py | 8 +- apiserver/plane/api/views/issue.py | 8 +- apiserver/plane/api/views/project.py | 79 ++++++++++++++++--- .../plane/bgtasks/issue_automation_task.py | 28 +++---- .../db/migrations/0046_auto_20231011_1334.py | 1 + ...e_projectsetting_default_state_and_more.py | 24 ++++++ apiserver/plane/db/models/project.py | 4 +- 9 files changed, 135 insertions(+), 38 deletions(-) create mode 100644 apiserver/plane/db/migrations/0047_remove_projectsetting_default_state_and_more.py diff --git a/apiserver/plane/api/serializers/__init__.py b/apiserver/plane/api/serializers/__init__.py index 69d48bd75..d38e829f7 100644 --- a/apiserver/plane/api/serializers/__init__.py +++ b/apiserver/plane/api/serializers/__init__.py @@ -13,6 +13,7 @@ from .project import ( ProjectSerializer, ProjectSettingSerializer, ProjectDetailSerializer, + ProjectSettingDetailSerializer, ProjectMemberSerializer, ProjectMemberInviteSerializer, ProjectIdentifierSerializer, @@ -21,7 +22,7 @@ from .project import ( ProjectMemberLiteSerializer, ProjectDeployBoardSerializer, ProjectMemberAdminSerializer, - ProjectPublicMemberSerializer + ProjectPublicMemberSerializer, ) from .state import StateSerializer, StateLiteSerializer from .view import GlobalViewSerializer, IssueViewSerializer, IssueViewFavoriteSerializer diff --git a/apiserver/plane/api/serializers/project.py b/apiserver/plane/api/serializers/project.py index a49b3313e..d1720fc0a 100644 --- a/apiserver/plane/api/serializers/project.py +++ b/apiserver/plane/api/serializers/project.py @@ -97,8 +97,6 @@ class ProjectLiteSerializer(BaseSerializer): class ProjectDetailSerializer(BaseSerializer): workspace = WorkSpaceSerializer(read_only=True) - default_assignee = UserLiteSerializer(read_only=True) - project_lead = UserLiteSerializer(read_only=True) is_favorite = serializers.BooleanField(read_only=True) total_members = serializers.IntegerField(read_only=True) total_cycles = serializers.IntegerField(read_only=True) @@ -179,12 +177,12 @@ class ProjectDeployBoardSerializer(BaseSerializer): fields = "__all__" read_only_fields = [ "workspace", - "project", "anchor", + "project", + "anchor", ] class ProjectPublicMemberSerializer(BaseSerializer): - class Meta: model = ProjectPublicMember fields = "__all__" @@ -196,6 +194,18 @@ class ProjectPublicMemberSerializer(BaseSerializer): class ProjectSettingSerializer(BaseSerializer): + class Meta: + model = ProjectSetting + fields = "__all__" + read_only_fields = [ + "workspace", + "project", + ] + + +class ProjectSettingDetailSerializer(BaseSerializer): + default_assignee = UserLiteSerializer(read_only=True) + project_lead = UserLiteSerializer(read_only=True) class Meta: model = ProjectSetting diff --git a/apiserver/plane/api/views/estimate.py b/apiserver/plane/api/views/estimate.py index 68de54d7a..943ffdbe4 100644 --- a/apiserver/plane/api/views/estimate.py +++ b/apiserver/plane/api/views/estimate.py @@ -9,7 +9,7 @@ from sentry_sdk import capture_exception # Module imports from .base import BaseViewSet, BaseAPIView from plane.api.permissions import ProjectEntityPermission -from plane.db.models import Project, Estimate, EstimatePoint +from plane.db.models import ProjectSetting, Estimate, EstimatePoint from plane.api.serializers import ( EstimateSerializer, EstimatePointSerializer, @@ -24,10 +24,10 @@ class ProjectEstimatePointEndpoint(BaseAPIView): def get(self, request, slug, project_id): try: - project = Project.objects.get(workspace__slug=slug, pk=project_id) - if project.estimate_id is not None: + project_setting = ProjectSetting.objects.get(workspace__slug=slug, pk=project_id) + if project_setting.estimate_id is not None: estimate_points = EstimatePoint.objects.filter( - estimate_id=project.estimate_id, + estimate_id=project_setting.estimate_id, project_id=project_id, workspace__slug=slug, ) diff --git a/apiserver/plane/api/views/issue.py b/apiserver/plane/api/views/issue.py index 2d13449fd..143ad78c4 100644 --- a/apiserver/plane/api/views/issue.py +++ b/apiserver/plane/api/views/issue.py @@ -63,6 +63,7 @@ from plane.api.permissions import ( ) from plane.db.models import ( Project, + ProjectSetting, Issue, IssueActivity, IssueComment, @@ -293,13 +294,14 @@ class IssueViewSet(BaseViewSet): def create(self, request, slug, project_id): try: project = Project.objects.get(pk=project_id) + project_setting = ProjectSetting.objects.get(workspace__slug=slug, project_id=project_id) serializer = IssueCreateSerializer( data=request.data, context={ "project_id": project_id, "workspace_id": project.workspace_id, - "default_assignee_id": project.default_assignee_id, + "default_assignee_id": project_setting.default_assignee_id, }, ) @@ -2565,13 +2567,13 @@ class IssueDraftViewSet(BaseViewSet): def create(self, request, slug, project_id): try: project = Project.objects.get(pk=project_id) - + project_setting = ProjectSetting.objects.get(workspace__slug=slug, project_id=project_id) serializer = IssueCreateSerializer( data=request.data, context={ "project_id": project_id, "workspace_id": project.workspace_id, - "default_assignee_id": project.default_assignee_id, + "default_assignee_id": project_setting.default_assignee_id, }, ) diff --git a/apiserver/plane/api/views/project.py b/apiserver/plane/api/views/project.py index cbbf5b0a8..62b737a13 100644 --- a/apiserver/plane/api/views/project.py +++ b/apiserver/plane/api/views/project.py @@ -29,6 +29,7 @@ from sentry_sdk import capture_exception from .base import BaseViewSet, BaseAPIView from plane.api.serializers import ( ProjectSerializer, + ProjectSettingDetailSerializer, ProjectSettingSerializer, ProjectMemberSerializer, ProjectDetailSerializer, @@ -99,7 +100,7 @@ class ProjectViewSet(BaseViewSet): .filter(workspace__slug=self.kwargs.get("slug")) .filter(Q(project_projectmember__member=self.request.user) | Q(network=2)) .select_related( - "workspace", "workspace__owner", "default_assignee", "project_lead" + "workspace", "workspace__owner", ) .annotate(is_favorite=Exists(subquery)) .annotate( @@ -211,20 +212,29 @@ class ProjectViewSet(BaseViewSet): if serializer.is_valid(): serializer.save() + project_lead = request.data.get("project_lead", None) + + # Create Project Setting + _ = ProjectSetting.objects.create( + project_id=serializer.data["id"], + project_lead_id=request.data.get("project_lead", None) + ) + # Add the user as Administrator to the project project_member = ProjectMember.objects.create( project_id=serializer.data["id"], member=request.user, role=20 ) - if serializer.data["project_lead"] is not None and str( - serializer.data["project_lead"] + if project_lead is not None and str( + project_lead ) != str(request.user.id): ProjectMember.objects.create( project_id=serializer.data["id"], - member_id=serializer.data["project_lead"], + member_id=project_lead, role=20, ) + # Default states states = [ { @@ -308,7 +318,7 @@ class ProjectViewSet(BaseViewSet): status=status.HTTP_410_GONE, ) except Exception as e: - capture_exception(e) + print(e) return Response( {"error": "Something went wrong please try again later"}, status=status.HTTP_400_BAD_REQUEST, @@ -974,7 +984,7 @@ class ProjectFavoritesViewSet(BaseViewSet): .filter(workspace__slug=self.kwargs.get("slug")) .filter(user=self.request.user) .select_related( - "project", "project__project_lead", "project__default_assignee" + "project", ) .select_related("workspace", "workspace__owner") ) @@ -1254,13 +1264,62 @@ class ProjectSettingViewSet(BaseViewSet): permission_classes = [ ProjectBasePermission, ] - serializer_class = ProjectSettingSerializer + + def get_serializer_class(self, *args, **kwargs): + if self.action in ["create", "partial_update"]: + return ProjectSettingSerializer + return ProjectSettingDetailSerializer def get_queryset(self): - super().get_queryset().filter( - workspace__slug=self.kwargs.get("slug"), - project_id=self.kwargs.get("project_id"), + return ( + 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")) + + def list(self, request, slug, project_id): + try: + project_setting = self.get_queryset().first() + + if project_setting is not None: + serializer = ProjectSettingDetailSerializer(project_setting) + return Response(serializer.data, status=status.HTTP_200_OK) + return Response( + {"error": "Project setting does not exists"}, + status=status.HTTP_404_NOT_FOUND, + ) + except Exception as e: + capture_exception(e) + return Response( + {"error": "Something went wrong please try again later"}, + status=status.HTTP_400_BAD_REQUEST, + ) + + def partial_update(self, request, slug, project_id): + try: + project_setting = self.get_queryset().first() + + # Check if it is None + if project_setting is not None: + serializer = ProjectSettingSerializer( + project_setting, data=request.data, partial=True + ) + if serializer.is_valid(): + return Response(serializer.data, status=status.HTTP_200_OK) + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + return Response( + {"error": "Project setting does not exists"}, + status=status.HTTP_404_NOT_FOUND, + ) + 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/bgtasks/issue_automation_task.py b/apiserver/plane/bgtasks/issue_automation_task.py index 68c64403a..7dd6cecd2 100644 --- a/apiserver/plane/bgtasks/issue_automation_task.py +++ b/apiserver/plane/bgtasks/issue_automation_task.py @@ -12,7 +12,7 @@ from celery import shared_task from sentry_sdk import capture_exception # Module imports -from plane.db.models import Issue, Project, State +from plane.db.models import Issue, ProjectSetting, State from plane.bgtasks.issue_activites_task import issue_activity @@ -25,11 +25,11 @@ def archive_and_close_old_issues(): def archive_old_issues(): try: # Get all the projects whose archive_in is greater than 0 - projects = Project.objects.filter(archive_in__gt=0) + project_settings = ProjectSetting.objects.filter(archive_in__gt=0) - for project in projects: - project_id = project.id - archive_in = project.archive_in + for project_setting in project_settings: + project_id = project_setting.project_id + archive_in = project_setting.archive_in # Get all the issues whose updated_at in less that the archive_in month issues = Issue.issue_objects.filter( @@ -75,7 +75,7 @@ def archive_old_issues(): issue_activity.delay( type="issue.activity.updated", requested_data=json.dumps({"archived_at": str(archive_at)}), - actor_id=str(project.created_by_id), + actor_id=str(project_setting.created_by_id), issue_id=issue.id, project_id=project_id, current_instance=None, @@ -95,13 +95,13 @@ def archive_old_issues(): def close_old_issues(): try: # Get all the projects whose close_in is greater than 0 - projects = Project.objects.filter(close_in__gt=0).select_related( - "default_state" + project_settings = ProjectSetting.objects.filter(close_in__gt=0).select_related( + "close_state" ) - for project in projects: - project_id = project.id - close_in = project.close_in + for project_setting in project_settings: + project_id = project_setting.project_id + close_in = project_setting.close_in # Get all the issues whose updated_at in less that the close_in month issues = Issue.issue_objects.filter( @@ -130,10 +130,10 @@ def close_old_issues(): # Check if Issues if issues: - if project.default_state is None: + if project_setting.close_state is None: close_state = State.objects.filter(group="cancelled").first() else: - close_state = project.default_state + close_state = project_setting.close_state issues_to_update = [] for issue in issues: @@ -147,7 +147,7 @@ def close_old_issues(): issue_activity.delay( type="issue.activity.updated", requested_data=json.dumps({"closed_to": str(issue.state_id)}), - actor_id=str(project.created_by_id), + actor_id=str(project_setting.created_by_id), issue_id=issue.id, project_id=project_id, current_instance=None, diff --git a/apiserver/plane/db/migrations/0046_auto_20231011_1334.py b/apiserver/plane/db/migrations/0046_auto_20231011_1334.py index 8e782852c..301aff1f0 100644 --- a/apiserver/plane/db/migrations/0046_auto_20231011_1334.py +++ b/apiserver/plane/db/migrations/0046_auto_20231011_1334.py @@ -74,6 +74,7 @@ class Migration(migrations.Migration): name='archive_in', ), migrations.RemoveField( + model_name='project', name='close_in', ), migrations.RemoveField( diff --git a/apiserver/plane/db/migrations/0047_remove_projectsetting_default_state_and_more.py b/apiserver/plane/db/migrations/0047_remove_projectsetting_default_state_and_more.py new file mode 100644 index 000000000..a615a89f9 --- /dev/null +++ b/apiserver/plane/db/migrations/0047_remove_projectsetting_default_state_and_more.py @@ -0,0 +1,24 @@ +# Generated by Django 4.2.3 on 2023-10-12 07:13 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('db', '0046_auto_20231011_1334'), + ] + + operations = [ + migrations.RemoveField( + model_name='projectsetting', + name='default_state', + ), + migrations.AddField( + model_name='projectsetting', + name='close_state', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='close_state', to='db.state'), + ), + ] diff --git a/apiserver/plane/db/models/project.py b/apiserver/plane/db/models/project.py index 622eb2786..b2590f94d 100644 --- a/apiserver/plane/db/models/project.py +++ b/apiserver/plane/db/models/project.py @@ -150,8 +150,8 @@ class ProjectSetting(BaseModel): 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" + close_state = models.ForeignKey( + "db.State", on_delete=models.SET_NULL, null=True, related_name="close_state" ) def save(self, *args, **kwargs):