Files
centurion_erp/app/core/serializers/ticket.py

421 lines
11 KiB
Python

from rest_framework import serializers
from rest_framework.reverse import reverse
from drf_spectacular.utils import extend_schema_serializer
from access.serializers.entity import BaseSerializer as EntityBaseSerializer
from access.serializers.organization import OrganizationBaseSerializer
from api.serializers import common
from app.serializers.user import UserBaseSerializer
from core import exceptions as centurion_exception
from core import fields as centurion_field
from core.fields.badge import BadgeField
from core.models.ticket_base import TicketBase
from core.serializers.ticket_category import TicketCategoryBaseSerializer
from project_management.serializers.project import ProjectBaseSerializer
from project_management.serializers.project_milestone import ProjectMilestoneBaseSerializer
@extend_schema_serializer(component_name = 'TicketBaseBaseSerializer')
class BaseSerializer(serializers.ModelSerializer):
"""Base Ticket Model"""
display_name = serializers.SerializerMethodField('get_display_name')
def get_display_name(self, item) -> str:
return str( item )
url = serializers.SerializerMethodField('get_url')
def get_url(self, item) -> str:
return item.get_url( request = self.context['view'].request )
class Meta:
model = TicketBase
fields = [
'id',
'display_name',
'url',
]
read_only_fields = [
'id',
'display_name',
'url',
]
@extend_schema_serializer(component_name = 'TicketBaseModelSerializer')
class ModelSerializer(
common.CommonModelSerializer,
BaseSerializer
):
"""Ticket Base Model"""
_urls = serializers.SerializerMethodField('get_url')
def get_url(self, item) -> dict:
ticket_type = str(item.ticket_type)
url_dict: dict = {
'_self': item.get_url( request = self._context['view'].request ),
'comments': reverse('v2:_api_v2_ticket_comment_base-list', request=self._context['view'].request, kwargs={'ticket_id': item.pk}),
'linked_items': reverse("v2:_api_v2_ticket_linked_item-list", request=self._context['view'].request, kwargs={'ticket_id': item.pk}),
}
if item.project:
url_dict.update({
'project': reverse("v2:_api_v2_project-list", request=self._context['view'].request, kwargs={}),
})
if item.category:
url_dict.update({
'ticketcategory': reverse(
'v2:_api_v2_ticket_category-list',
request=self._context['view'].request,
kwargs={},
) + '?' + ticket_type + '=true',
})
url_dict.update({
'related_tickets': reverse("v2:_api_v2_ticket_related-list", request=self._context['view'].request, kwargs={'ticket_id': item.pk}),
})
return url_dict
description = centurion_field.MarkdownField( required = True, style_class = 'large' )
impact_badge = BadgeField(label='Impact')
organization = common.OrganizationField(
required = True,
write_only = True,
)
priority_badge = BadgeField(
label = 'Priority',
read_only = True,
)
status_badge = BadgeField(
label = 'Status',
read_only = True,
)
ticket_duration = serializers.IntegerField(
help_text = 'Total time spent on ticket',
label = 'Time Spent',
read_only = True,
)
ticket_estimation = serializers.IntegerField(
help_text = 'Time estimation to complete the ticket',
label = 'Time estimation',
read_only = True,
)
urgency_badge = BadgeField(
label = 'Urgency',
read_only = True,
)
class Meta:
model = TicketBase
fields = [
'id',
'display_name',
'organization',
'external_system',
'external_ref',
'parent_ticket',
'ticket_type',
'status',
'status_badge',
'category',
'title',
'description',
'ticket_duration',
'ticket_estimation',
'project',
'milestone',
'urgency',
'urgency_badge',
'impact',
'impact_badge',
'priority',
'priority_badge',
'opened_by',
'subscribed_to',
'assigned_to',
'planned_start_date',
'planned_finish_date',
'real_start_date',
'real_finish_date',
'is_deleted',
'is_solved',
'date_solved',
'is_closed',
'date_closed',
'created',
'modified',
'_urls',
]
read_only_fields = [
'id',
'display_name',
'created',
'modified',
'_urls',
]
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if self.context.get('view', None) is not None:
read_only_fields = [
'id',
'display_name',
'created',
'modified',
'_urls',
]
if not self.context['view']._has_import:
read_only_fields += [
'external_system',
'external_ref',
'ticket_type',
]
self.Meta.read_only_fields = read_only_fields
def validate_field_milestone( self, attrs, raise_exception = False ) -> bool:
milestone = attrs.get('milestone', None)
project = attrs.get('project', None)
if milestone is not None:
if project is None:
raise centurion_exception.ValidationError(
detail = {
'milestone': 'Milestones require a project'
},
code = 'milestone_requires_project',
)
elif project.id != milestone.project.id:
del attrs['milestone']
raise centurion_exception.ValidationError(
detail = {
'milestone': 'Milestone must be from the same project'
},
code = 'milestone_same_project',
)
return attrs
def validate_field_external_system( self, attrs, raise_exception = False ) -> bool:
external_system = attrs.get('external_system', None)
external_ref = attrs.get('external_ref', None)
if external_system is None and external_ref is not None:
raise centurion_exception.ValidationError(
detail = {
'external_system': 'External System is required when an External Ref is defined'
},
code = 'external_system_missing',
)
elif external_system is not None and external_ref is None:
raise centurion_exception.ValidationError(
detail = {
'external_ref': 'External Ref is required when an External System is defined'
},
code = 'external_ref_missing',
)
return attrs
def validate(self, attrs):
if getattr(self.context['view'], 'action', '') in [ 'create' ]:
# Always set that the ticket was opened by user ho is making the request
try:
attrs['opened_by'] = self.context['request'].user
except KeyError:
pass
attrs = self.validate_field_milestone( attrs )
attrs = self.validate_field_external_system( attrs )
attrs = super().validate( attrs )
has_import_permission = self.context['view']._has_import
has_triage_permission = self.context['view']._has_triage
status = int(attrs.get('status', 0))
opened_by_id = attrs.get('opened_by', 0)
if opened_by_id != 0:
opened_by_id = opened_by_id.id
request_user_id = int(self.context['request'].user.id)
if opened_by_id == 0:
request_user_id = 0
if not (
has_triage_permission
or has_import_permission
):
if(
status == TicketBase.TicketStatus.ASSIGNED
or status == TicketBase.TicketStatus.ASSIGNED_PLANNING
):
raise centurion_exception.ValidationError(
detail = {
'status': 'You cant assign a ticket if you dont have permission triage'
},
code = 'no_triage_status_assigned',
)
if status == TicketBase.TicketStatus.PENDING:
raise centurion_exception.ValidationError(
detail = {
'status': 'You cant set a ticket to pending if you dont have permission triage'
},
code = 'no_triage_status_pending',
)
if(
status == TicketBase.TicketStatus.SOLVED
and opened_by_id != request_user_id
):
raise centurion_exception.ValidationError(
detail = {
'status': 'You cant solve a ticket if you dont have permission triage'
},
code = 'no_triage_status_solve',
)
if(
status == TicketBase.TicketStatus.INVALID
and opened_by_id != request_user_id
):
raise centurion_exception.ValidationError(
detail = {
'status': 'You cant mark a ticket as invalid if you did not raise the ticket or you dont have permission triage'
},
code = 'no_triage_status_invalid',
)
if status == TicketBase.TicketStatus.CLOSED:
raise centurion_exception.ValidationError(
detail = {
'status': 'You cant close a ticket if you dont have permission triage'
},
code = 'no_triage_status_close',
)
elif (
has_triage_permission
or has_import_permission
):
if(
(
'status' not in attrs
or attrs.get('status', 0) == self.Meta.model.TicketStatus.NEW
)
and 'assigned_to' in attrs
):
attrs['status'] = self.Meta.model.TicketStatus.ASSIGNED
return attrs
def is_valid(self, raise_exception = False):
is_valid = super().is_valid( raise_exception = raise_exception )
return is_valid
@extend_schema_serializer(component_name = 'TicketBaseViewSerializer')
class ViewSerializer(ModelSerializer):
"""Ticket Base View Model"""
assigned_to = EntityBaseSerializer(many=True, label = 'assigned to')
category = TicketCategoryBaseSerializer(label = 'category')
milestone = ProjectMilestoneBaseSerializer(many=False, read_only=True)
opened_by = UserBaseSerializer()
organization = OrganizationBaseSerializer(many=False, read_only=True)
parent_ticket = BaseSerializer()
project = ProjectBaseSerializer(many=False, read_only=True)
subscribed_to = EntityBaseSerializer(many=True, label = 'subscribved to')