From 74d55fb81e7544937a419cf901214eb17bd3fc58 Mon Sep 17 00:00:00 2001 From: Jon Date: Sat, 2 Nov 2024 16:12:09 +0930 Subject: [PATCH] feat(itim): Add Project Task API v2 endpoint ref: #248 #377 --- app/api/urls_v2.py | 2 + .../views/project_management/project_task.py | 1 + app/core/serializers/ticket.py | 46 +++- app/core/viewsets/ticket.py | 50 ++-- app/project_management/serializers/project.py | 8 +- .../serializers/project_task.py | 225 ++++++++++++++++++ .../viewsets/project_task.py | 83 +++++++ 7 files changed, 389 insertions(+), 26 deletions(-) create mode 100644 app/project_management/serializers/project_task.py create mode 100644 app/project_management/viewsets/project_task.py diff --git a/app/api/urls_v2.py b/app/api/urls_v2.py index 0163afd9..a5e8d78c 100644 --- a/app/api/urls_v2.py +++ b/app/api/urls_v2.py @@ -78,6 +78,7 @@ from project_management.viewsets import ( project as project_v2, project_milestone as project_milestone_v2, project_state as project_state_v2, + project_task, project_type as project_type_v2, ) @@ -155,6 +156,7 @@ router.register('project_management', project_management_v2.Index, basename='_ap router.register('project_management/project', project_v2.ViewSet, basename='_api_v2_project') router.register('project_management/project/(?P[0-9]+)/milestone', project_milestone_v2.ViewSet, basename='_api_v2_project_milestone') router.register('project_management/project/(?P[0-9]+)/notes', notes_v2.ViewSet, basename='_api_v2_project_notes') +router.register('project_management/project/(?P[0-9]+)/project_task', project_task.ViewSet, basename='_api_v2_ticket_project_task') router.register('settings', settings_index_v2.Index, basename='_api_v2_settings_home') diff --git a/app/api/views/project_management/project_task.py b/app/api/views/project_management/project_task.py index 0f6bfd7a..1def06af 100644 --- a/app/api/views/project_management/project_task.py +++ b/app/api/views/project_management/project_task.py @@ -6,6 +6,7 @@ from api.views.core.tickets import View +@extend_schema(deprecated=True) class View(View): _ticket_type:str = 'project_task' diff --git a/app/core/serializers/ticket.py b/app/core/serializers/ticket.py index 3dffb83d..ea1a5710 100644 --- a/app/core/serializers/ticket.py +++ b/app/core/serializers/ticket.py @@ -29,12 +29,26 @@ class TicketBaseSerializer(serializers.ModelSerializer): context = self.context.copy() - return reverse( - "v2:_api_v2_ticket_" + str(item.get_ticket_type_display()).lower() + "-detail", - request=context['view'].request, - kwargs={ + ticket_type = str(item.get_ticket_type_display()).lower().replace(' ', '_') + + if ticket_type == 'project_task': + + kwargs: dict = { + 'project_id': item.project.id, 'pk': item.pk } + + else: + + kwargs: dict = { + 'pk': item.pk + } + + + return reverse( + "v2:_api_v2_ticket_" + ticket_type + "-detail", + request=context['view'].request, + kwargs = kwargs ) @@ -65,13 +79,29 @@ class TicketModelSerializer(TicketBaseSerializer): context = self.context.copy() + context = self.context.copy() + + ticket_type = str(item.get_ticket_type_display()).lower().replace(' ', '_') + + if ticket_type == 'project_task': + + kwargs: dict = { + 'project_id': item.project.id, + 'pk': item.pk + } + + else: + + kwargs: dict = { + 'pk': item.pk + } + + return { '_self': reverse( - "v2:_api_v2_ticket_" + str(item.get_ticket_type_display()).lower() + "-detail", + "v2:_api_v2_ticket_" + ticket_type + "-detail", request=context['view'].request, - kwargs={ - 'pk': item.pk - } + kwargs = kwargs ), 'comments': reverse('v2:_api_v2_ticket_comments-list', request=context['view'].request, kwargs={'ticket_id': item.pk}), 'linked_items': reverse("v2:_api_v2_ticket_linked_item-list", request=context['view'].request, kwargs={'ticket_id': item.pk}), diff --git a/app/core/viewsets/ticket.py b/app/core/viewsets/ticket.py index a05369f6..4c4dc938 100644 --- a/app/core/viewsets/ticket.py +++ b/app/core/viewsets/ticket.py @@ -43,6 +43,14 @@ from itim.serializers.problem import ( ProblemTicketViewSerializer, ) +from project_management.serializers.project_task import ( + ProjectTaskAddTicketModelSerializer, + ProjectTaskChangeTicketModelSerializer, + ProjectTaskImportTicketModelSerializer, + ProjectTaskTriageTicketModelSerializer, + ProjectTaskTicketModelSerializer, + ProjectTaskTicketViewSerializer, +) from settings.models.user_settings import UserSettings @@ -181,9 +189,18 @@ class TicketViewSet(ModelViewSet): self.get_ticket_type() - queryset = super().get_queryset().filter( - ticket_type = self._ticket_type_id - ) + if str(self._ticket_type).lower().replace(' ', '_') == 'project_task': + + queryset = super().get_queryset().filter( + project_id = int(self.kwargs['project_id']) + ) + + else: + + queryset = super().get_queryset().filter( + ticket_type = self._ticket_type_id + ) + self.queryset = queryset @@ -198,9 +215,9 @@ class TicketViewSet(ModelViewSet): ticket_types = [e for e in Ticket.TicketType] - for i in range( 1, len(ticket_types) ): + for i in range( 0, len(ticket_types) ): - if self._ticket_type.lower() == str(ticket_types[i - 1].label).lower(): + if self._ticket_type.lower() == str(ticket_types[i].label).lower(): ticket_type_id = i @@ -220,7 +237,7 @@ class TicketViewSet(ModelViewSet): self.get_ticket_type() - serializer_prefix = self._ticket_type + serializer_prefix = str(self._ticket_type).replace(' ', '') if ( @@ -262,48 +279,47 @@ class TicketViewSet(ModelViewSet): if self.has_organization_permission( organization = organization, permissions_required = [ - 'core.import_ticket_request' + 'core.import_ticket_' + str(self._ticket_type).lower().replace(' ', '_') ] ): - serializer_prefix = self._ticket_type + 'Import' + serializer_prefix = serializer_prefix + 'Import' elif self.has_organization_permission( organization = organization, permissions_required = [ - 'core.triage_ticket_request' + 'core.triage_ticket_' + str(self._ticket_type).lower().replace(' ', '_') ] ): - serializer_prefix = self._ticket_type + 'Triage' + serializer_prefix = serializer_prefix + 'Triage' elif self.has_organization_permission( organization = organization, permissions_required = [ - 'core.change_ticket_request' + 'core.change_ticket_' + str(self._ticket_type).lower().replace(' ', '_') ] ): - serializer_prefix = self._ticket_type + 'Change' + serializer_prefix = serializer_prefix + 'Change' elif self.has_organization_permission( organization = organization, permissions_required = [ - 'core.add_ticket_request' + 'core.add_ticket_' + str(self._ticket_type).lower().replace(' ', '_') ] ): - serializer_prefix = self._ticket_type + 'Add' + serializer_prefix = serializer_prefix + 'Add' elif self.has_organization_permission( organization = organization, permissions_required = [ - 'core.view_ticket_request' + 'core.view_ticket_' + str(self._ticket_type).lower().replace(' ', '_') ] ): - serializer_prefix = self._ticket_type + 'View' - + serializer_prefix = serializer_prefix + 'View' if ( diff --git a/app/project_management/serializers/project.py b/app/project_management/serializers/project.py index 5a4aae5a..46362880 100644 --- a/app/project_management/serializers/project.py +++ b/app/project_management/serializers/project.py @@ -66,7 +66,13 @@ class ProjectModelSerializer(ProjectBaseSerializer): ), 'milestone': reverse("v2:_api_v2_project_milestone-list", request=self._context['view'].request, kwargs={'project_id': item.pk}), 'notes': reverse("v2:_api_v2_project_notes-list", request=self._context['view'].request, kwargs={'project_id': item.pk}), - 'tickets': 'ToDo' + 'tickets': reverse( + "v2:_api_v2_ticket_project_task-list", + request=self._context['view'].request, + kwargs={ + 'project_id': item.pk + } + ), } diff --git a/app/project_management/serializers/project_task.py b/app/project_management/serializers/project_task.py new file mode 100644 index 00000000..8930b769 --- /dev/null +++ b/app/project_management/serializers/project_task.py @@ -0,0 +1,225 @@ +from rest_framework import serializers + +from app.serializers.user import UserBaseSerializer + +from core.serializers.ticket import ( + Ticket, + TicketBaseSerializer, + TicketModelSerializer, + TicketViewSerializer +) + + + +class ProjectTaskTicketBaseSerializer( + TicketBaseSerializer +): + + class Meta( TicketBaseSerializer.Meta ): + + pass + + + +class ProjectTaskTicketModelSerializer( + ProjectTaskTicketBaseSerializer, + TicketModelSerializer, +): + + status = serializers.ChoiceField([(e.value, e.label) for e in Ticket.TicketStatus.ProjectTask]) + + class Meta( TicketModelSerializer.Meta ): + + fields = [ + 'id', + 'assigned_teams', + 'assigned_users', + 'category', + 'created', + 'modified', + 'status', + 'status_badge', + 'title', + 'description', + 'estimate', + 'duration', + 'urgency', + 'impact', + 'priority', + 'external_ref', + 'external_system', + 'ticket_type', + 'is_deleted', + 'date_closed', + 'opened_by', + 'organization', + 'project', + 'milestone', + 'subscribed_teams', + 'subscribed_users', + '_urls', + ] + + read_only_fields = [ + 'id', + 'display_name', + 'external_ref', + 'external_system', + 'status_badge', + 'ticket_type', + '_urls', + ] + + + +class ProjectTaskAddTicketModelSerializer( + ProjectTaskTicketModelSerializer, +): + """Serializer for `Add` user + + Args: + ProjectTaskTicketModelSerializer (class): Model Serializer + """ + + + class Meta(ProjectTaskTicketModelSerializer.Meta): + + read_only_fields = [ + 'id', + 'assigned_teams', + 'assigned_users', + 'category', + 'created', + 'modified', + 'status', + 'status_badge', + 'estimate', + 'duration', + 'impact', + 'priority', + 'external_ref', + 'external_system', + 'ticket_type', + 'is_deleted', + 'date_closed', + 'planned_start_date', + 'planned_finish_date', + 'real_start_date', + 'real_finish_date', + 'opened_by', + 'organization', + 'project', + 'milestone', + 'subscribed_teams', + 'subscribed_users', + '_urls', + ] + + + +class ProjectTaskChangeTicketModelSerializer( + ProjectTaskTicketModelSerializer, +): + """Serializer for `ProjectTask` user + + Args: + ProjectTaskTicketModelSerializer (class): ProjectTask Model Serializer + """ + + class Meta(ProjectTaskTicketModelSerializer.Meta): + + read_only_fields = [ + 'id', + 'assigned_teams', + 'assigned_users', + 'category', + 'created', + 'modified', + 'status', + 'status_badge', + 'estimate', + 'duration', + 'impact', + 'priority', + 'external_ref', + 'external_system', + 'ticket_type', + 'is_deleted', + 'date_closed', + 'planned_start_date', + 'planned_finish_date', + 'real_start_date', + 'real_finish_date', + 'opened_by', + 'organization', + 'project', + 'milestone', + 'subscribed_teams', + 'subscribed_users', + '_urls', + ] + + + +class ProjectTaskTriageTicketModelSerializer( + ProjectTaskTicketModelSerializer, +): + """Serializer for `Triage` user + + Args: + ProjectTaskTicketModelSerializer (class): ProjectTask Model Serializer + """ + + + class Meta(ProjectTaskTicketModelSerializer.Meta): + + read_only_fields = [ + 'id', + 'created', + 'modified', + 'status_badge', + 'estimate', + 'duration', + 'external_ref', + 'external_system', + 'ticket_type', + 'is_deleted', + 'date_closed', + 'planned_start_date', + 'planned_finish_date', + 'real_start_date', + 'real_finish_date', + 'opened_by', + 'organization', + '_urls', + ] + + + +class ProjectTaskImportTicketModelSerializer( + ProjectTaskTicketModelSerializer, +): + """Serializer for `Import` user + + Args: + ProjectTaskTicketModelSerializer (class): ProjectTask Model Serializer + """ + + class Meta(ProjectTaskTicketModelSerializer.Meta): + + read_only_fields = [ + 'id', + 'display_name', + 'status_badge', + 'ticket_type', + '_urls', + ] + + + +class ProjectTaskTicketViewSerializer( + ProjectTaskTicketModelSerializer, + TicketViewSerializer, +): + + pass diff --git a/app/project_management/viewsets/project_task.py b/app/project_management/viewsets/project_task.py new file mode 100644 index 00000000..9e94f939 --- /dev/null +++ b/app/project_management/viewsets/project_task.py @@ -0,0 +1,83 @@ +from drf_spectacular.utils import ( + extend_schema, + extend_schema_view, + OpenApiResponse, + PolymorphicProxySerializer, +) + +from project_management.serializers.project_task import ( + ProjectTaskAddTicketModelSerializer, + ProjectTaskChangeTicketModelSerializer, + ProjectTaskImportTicketModelSerializer, + ProjectTaskTriageTicketModelSerializer, + ProjectTaskTicketViewSerializer, +) + +from core.viewsets.ticket import TicketViewSet + + + +@extend_schema_view( + create=extend_schema( + summary = 'Create a Project Task', + description='', + request = PolymorphicProxySerializer( + component_name = 'ProjectTask', + serializers=[ + ProjectTaskImportTicketModelSerializer, + ProjectTaskAddTicketModelSerializer, + ProjectTaskChangeTicketModelSerializer, + ProjectTaskTriageTicketModelSerializer, + ], + resource_type_field_name=None, + many = False + ), + responses = { + 201: OpenApiResponse(description='Created', response=ProjectTaskTicketViewSerializer), + 403: OpenApiResponse(description='User is missing add permissions'), + } + ), + destroy = extend_schema( + summary = 'Delete a Project Task', + description = '', + responses = { + 204: OpenApiResponse(description=''), + 403: OpenApiResponse(description='User is missing delete permissions'), + } + ), + list = extend_schema( + summary = 'Fetch all Project Task', + description='', + responses = { + 200: OpenApiResponse(description='', response=ProjectTaskTicketViewSerializer), + 403: OpenApiResponse(description='User is missing view permissions'), + } + ), + retrieve = extend_schema( + summary = 'Fetch a Project Task', + description='', + responses = { + 200: OpenApiResponse(description='', response=ProjectTaskTicketViewSerializer), + 403: OpenApiResponse(description='User is missing view permissions'), + } + ), + update = extend_schema(exclude = True), + partial_update = extend_schema( + summary = 'Update a Project Task', + description = '', + responses = { + 200: OpenApiResponse(description='', response=ProjectTaskTicketViewSerializer), + 403: OpenApiResponse(description='User is missing change permissions'), + } + ), +) +class ViewSet(TicketViewSet): + """Change Ticket + + This class exists only for the purpose of swagger for documentation. + + Args: + TicketViewSet (class): Base Ticket ViewSet. + """ + + _ticket_type: str = 'Project Task'