Skip to content
Snippets Groups Projects
specification.py 62.2 KiB
Newer Older
"""
This file contains the viewsets (based on the elsewhere defined data models and serializers)
"""

from django.shortcuts import get_object_or_404, get_list_or_404, render
Fabio Vitello's avatar
Fabio Vitello committed

from django.contrib.auth.models import User
Jorrit Schaap's avatar
Jorrit Schaap committed
from django_filters import rest_framework as filters
import django_property_filter as property_filters
from rest_framework.viewsets import ReadOnlyModelViewSet
from rest_framework import status
from rest_framework.response import Response
from rest_framework.decorators import permission_classes
from rest_framework.permissions import IsAuthenticated
from rest_framework.decorators import action
from rest_framework.response import Response as RestResponse

from drf_yasg.utils import swagger_auto_schema
from drf_yasg.openapi import Parameter
from lofar.sas.tmss.tmss.tmssapp.viewsets.lofar_viewset import LOFARViewSet, LOFARNestedViewSet, AbstractTemplateViewSet, LOFARCopyViewSet, LOFARFilterBackend
from lofar.sas.tmss.tmss.tmssapp import models
from lofar.sas.tmss.tmss.tmssapp import serializers
from lofar.sas.tmss.tmss.tmssapp.adapters.reports import create_cycle_report, create_project_report
from django.http import JsonResponse
from datetime import datetime
from lofar.common.json_utils import get_default_json_object_for_schema
from lofar.common.datetimeutils import formatDatetime
from lofar.sas.tmss.tmss.tmssapp.tasks import *
from lofar.sas.tmss.tmss.tmssapp.subtasks import *
from lofar.sas.tmss.tmss.tmssapp.viewsets.permissions import TMSSDjangoModelPermissions
from django.urls  import resolve, get_script_prefix,Resolver404
from rest_framework.filters import OrderingFilter

from django.core.exceptions import ObjectDoesNotExist

# This is required for keeping a user reference as ForeignKey in other models
# (I think so that the HyperlinkedModelSerializer can generate a URI)
class UserViewSet(ReadOnlyModelViewSet):
    queryset = User.objects.all()
    serializer_class = serializers.UserSerializer


class TagsViewSet(LOFARViewSet):
    queryset = models.Tags.objects.all()
    serializer_class = serializers.TagsSerializer
class CommonSchemaTemplateViewSet(AbstractTemplateViewSet):
    queryset = models.CommonSchemaTemplate.objects.all()
    serializer_class = serializers.CommonSchemaTemplateSerializer

class GeneratorTemplateViewSet(AbstractTemplateViewSet):
    queryset = models.GeneratorTemplate.objects.all()
    serializer_class = serializers.GeneratorTemplateSerializer


class DefaultGeneratorTemplateViewSet(LOFARViewSet):
    queryset = models.DefaultGeneratorTemplate.objects.all()
    serializer_class = serializers.DefaultGeneratorTemplateSerializer


class SchedulingUnitObservingStrategyTemplateViewSet(LOFARViewSet):
    queryset = models.SchedulingUnitObservingStrategyTemplate.objects.all()
    serializer_class = serializers.SchedulingUnitObservingStrategyTemplateSerializer

    @swagger_auto_schema(responses={status.HTTP_201_CREATED: 'The newly created scheduling unit',
                                    status.HTTP_403_FORBIDDEN: 'forbidden'},
                         operation_description="Create a new SchedulingUnit based on this SchedulingUnitObservingStrategyTemplate, with the given <name> and <description> and make it a child of the given <scheduling_set_id>",
                         manual_parameters=[Parameter(name='scheduling_set_id', required=True, type='integer', in_='query',
                                                      description="the id of the scheduling_set which will be the parent of the newly created scheduling_unit"),
                                            Parameter(name='name', required=False, type='string', in_='query',
                                                      description="The name for the newly created scheduling_unit"),
                                            Parameter(name='description', required=False, type='string', in_='query',
                                                      description="The description for the newly created scheduling_unit")])
    @action(methods=['post'], detail=True)
    def create_scheduling_unit(self, request, pk=None):
        strategy_template = get_object_or_404(models.SchedulingUnitObservingStrategyTemplate, pk=pk)
        spec = add_defaults_to_json_object_for_schema(strategy_template.template,
                                                      strategy_template.scheduling_unit_template.schema)

        # get the default_scheduling_constraints_template and fill a doc if available
        default_scheduling_constraints_template = models.DefaultSchedulingConstraintsTemplate.objects.all().order_by('created_at').last()
        if default_scheduling_constraints_template:
            scheduling_constraints_template = default_scheduling_constraints_template.template
            scheduling_constraints_doc = get_default_json_object_for_schema(scheduling_constraints_template.schema)
        else:
            scheduling_constraints_template = None
            scheduling_constraints_doc = None

        scheduling_set = get_object_or_404(models.SchedulingSet, pk=request.query_params['scheduling_set_id'])

        scheduling_unit_draft = models.SchedulingUnitDraft.objects.create(name=request.query_params.get('name', "scheduling unit"),
                                                                          description=request.query_params.get('description', ""),
                                                                          requirements_doc=spec,
                                                                          scheduling_set=scheduling_set,
                                                                          requirements_template=strategy_template.scheduling_unit_template,
                                                                          observation_strategy_template=strategy_template,
                                                                          scheduling_constraints_doc=scheduling_constraints_doc,
                                                                          scheduling_constraints_template=scheduling_constraints_template)

        scheduling_unit_observation_strategy_template_path = request._request.path
        base_path = scheduling_unit_observation_strategy_template_path[:scheduling_unit_observation_strategy_template_path.find('/scheduling_unit_observing_strategy_template')]
        scheduling_unit_draft_path = '%s/scheduling_unit_draft/%s/' % (base_path, scheduling_unit_draft.id,)

        # return a response with the new serialized SchedulingUnitDraft, and a Location to the new instance in the header
        return Response(serializers.SchedulingUnitDraftSerializer(scheduling_unit_draft, context={'request':request}).data,
                        status=status.HTTP_201_CREATED,
                        headers={'Location': scheduling_unit_draft_path})
class SchedulingUnitObservingStrategyTemplateNestedViewSet(LOFARNestedViewSet):
    queryset = models.SchedulingUnitObservingStrategyTemplate.objects.all()
    serializer_class = serializers.SchedulingUnitObservingStrategyTemplateSerializer

    def get_queryset(self):
            if 'scheduling_set_id' in self.kwargs:
                scheduling_unit_drafts = get_list_or_404(models.SchedulingUnitDraft, scheduling_set__id=self.kwargs['scheduling_set_id'])
                return [draft.observation_strategy_template for draft in scheduling_unit_drafts]
            else:
                return models.SchedulingUnitObservingStrategyTemplate.objects.all()
Jorrit Schaap's avatar
Jorrit Schaap committed
class SchedulingUnitTemplateFilter(filters.FilterSet):
    class Meta:
        model = models.SchedulingUnitTemplate
        fields = {
            'name': ['exact'],
            'version': ['lt', 'gt', 'exact']
        }
class SchedulingUnitTemplateViewSet(AbstractTemplateViewSet):
    queryset = models.SchedulingUnitTemplate.objects.all()
    serializer_class = serializers.SchedulingUnitTemplateSerializer
Jorrit Schaap's avatar
Jorrit Schaap committed
    filter_class = SchedulingUnitTemplateFilter
class DefaultSchedulingUnitTemplateViewSet(LOFARViewSet):
    queryset = models.DefaultSchedulingUnitTemplate.objects.all()
    serializer_class = serializers.DefaultSchedulingUnitTemplateSerializer

class SchedulingConstraintsTemplateFilter(filters.FilterSet):
    class Meta:
        model = models.SchedulingConstraintsTemplate
        fields = {
            'name': ['exact'],
            'version': ['lt', 'gt', 'exact']
        }


class SchedulingConstraintsTemplateViewSet(AbstractTemplateViewSet):
    queryset = models.SchedulingConstraintsTemplate.objects.all()
    serializer_class = serializers.SchedulingConstraintsTemplateSerializer
    filter_class = SchedulingConstraintsTemplateFilter


class DefaultSchedulingConstraintsTemplateViewSet(LOFARViewSet):
    queryset = models.DefaultSchedulingConstraintsTemplate.objects.all()
    serializer_class = serializers.DefaultSchedulingConstraintsTemplateSerializer


Jorrit Schaap's avatar
Jorrit Schaap committed
class TaskTemplateFilter(filters.FilterSet):
    class Meta:
        model = models.TaskTemplate
        fields = {
            'name': ['exact'],
            'version': ['lt', 'gt', 'exact']
        }

class TaskTemplateViewSet(AbstractTemplateViewSet):
    queryset = models.TaskTemplate.objects.all()
    serializer_class = serializers.TaskTemplateSerializer
Jorrit Schaap's avatar
Jorrit Schaap committed
    filter_class = TaskTemplateFilter
class DefaultTaskTemplateViewSet(LOFARViewSet):
    queryset = models.DefaultTaskTemplate.objects.all()
    serializer_class = serializers.DefaultTaskTemplateSerializer
class TaskRelationSelectionTemplateViewSet(AbstractTemplateViewSet):
    queryset = models.TaskRelationSelectionTemplate.objects.all()
    serializer_class = serializers.TaskRelationSelectionTemplateSerializer
class DefaultTaskRelationSelectionTemplateViewSet(LOFARViewSet):
    queryset = models.DefaultTaskRelationSelectionTemplate.objects.all()
    serializer_class = serializers.DefaultTaskRelationSelectionTemplateSerializer
class ReservationStrategyTemplateViewSet(LOFARViewSet):
    queryset = models.ReservationStrategyTemplate.objects.all()
    serializer_class = serializers.ReservationStrategyTemplateSerializer

    @swagger_auto_schema(responses={status.HTTP_201_CREATED: 'The newly created reservation',
                                    status.HTTP_403_FORBIDDEN: 'forbidden'},
                         operation_description="Create a new Reservation based on this ReservationStrategyTemplate, "
                                               "with the given <name>, <description>, <start_time> and <stop_time>",
                         manual_parameters=[Parameter(name='start_time', required=True, type='string', in_='query',
                                                      description="The start time as a timestamp string in isoformat"),
                                            Parameter(name='stop_time', required=True, type='string', in_='query',
                                                      description="The stop time as a timestamp string in isoformat"),
                                            Parameter(name='name', required=False, type='string', in_='query',
                                                      description="The name for the newly created reservation"),
                                            Parameter(name='description', required=False, type='string', in_='query',
                                                      description="The description for the newly created reservation"),
                                            Parameter(name='project_id', required=False, type='integer', in_='query',
                                                      description="the id of the project which will be the parent of the newly created reservation"),
                                            ])
    @action(methods=['post'], detail=True)
    def create_reservation(self, request, pk=None):
        strategy_template = get_object_or_404(models.ReservationStrategyTemplate, pk=pk)
        reservation_template_spec = add_defaults_to_json_object_for_schema(strategy_template.template,
                                                      strategy_template.reservation_template.schema)

        start_time = request.query_params.get('start_time', None)
        stop_time = request.query_params.get('stop_time', None)
        if start_time:
            start_time = dateutil.parser.parse(start_time)  # string to datetime
        else:
            start_time = datetime.now()
        if stop_time:
            stop_time = dateutil.parser.parse(stop_time)  # string to datetime
        else:

        project_id = request.query_params.get('project_id', None)
        if project_id:
            project = get_object_or_404(models.Project, pk=request.query_params['project_id'])
        else:
            project = None

        reservation = Reservation.objects.create(name=request.query_params.get('name', "reservation"),
                                                 description=request.query_params.get('description', ""),
                                                 project=project,
                                                 specifications_template=strategy_template.reservation_template,
                                                 specifications_doc=reservation_template_spec,
                                                 start_time=start_time,

        reservation_strategy_template_path = request._request.path
        base_path = reservation_strategy_template_path[:reservation_strategy_template_path.find('/reservation_strategy_template')]
        reservation_path = '%s/reservation/%s/' % (base_path, reservation.id,)

        # return a response with the new serialized Reservation, and a Location to the new instance in the header
        return Response(serializers.ReservationSerializer(reservation, context={'request':request}).data,
                        status=status.HTTP_201_CREATED,
                        headers={'Location': reservation_path})


class DefaultReservationTemplateViewSet(LOFARViewSet):
    queryset = models.DefaultReservationTemplate.objects.all()
    serializer_class = serializers.DefaultReservationTemplateSerializer


class ReservationTemplateViewSet(AbstractTemplateViewSet):
    queryset = models.ReservationTemplate.objects.all()
    serializer_class = serializers.ReservationTemplateSerializer


class ReservationViewSet(LOFARViewSet):
    queryset = models.Reservation.objects.all()
    serializer_class = serializers.ReservationSerializer


class RoleViewSet(LOFARViewSet):
    queryset = models.Role.objects.all()
    serializer_class = serializers.RoleSerializer
class IOTypeViewSet(LOFARViewSet):
    queryset = models.IOType.objects.all()
    serializer_class = serializers.IOTypeSerializer


class SchedulingRelationPlacement(LOFARViewSet):
    queryset = models.SchedulingRelationPlacement.objects.all()
    serializer_class = serializers.SchedulingRelationPlacementSerializer
class DatatypeViewSet(LOFARViewSet):
    queryset = models.Datatype.objects.all()
    serializer_class = serializers.DatatypeSerializer
class DataformatViewSet(LOFARViewSet):
    queryset = models.Dataformat.objects.all()
    serializer_class = serializers.DataformatSerializer
class CopyReasonViewSet(LOFARViewSet):
    queryset = models.CopyReason.objects.all()
    serializer_class = serializers.CopyReasonSerializer
class TaskConnectorTypeViewSet(LOFARViewSet):
    queryset = models.TaskConnectorType.objects.all()
    serializer_class = serializers.TaskConnectorTypeSerializer
class CycleViewSet(LOFARViewSet):
    permission_classes = (TMSSDjangoModelPermissions,)      # override default project permission
    filter_backends = (LOFARFilterBackend, OrderingFilter)                                    # override default project permission
    queryset = models.Cycle.objects.all()
    serializer_class = serializers.CycleSerializer
    ordering = ['start']

    @swagger_auto_schema(responses={200: 'The Report information',
                                    403: 'forbidden'},
                         operation_description="Get Report information for the cycle.")
    @action(methods=['get'], detail=True, url_name="report", name="Get Report")
    def report(self, request, pk=None):
        cycle = get_object_or_404(models.Cycle, pk=pk)
        result = create_cycle_report(request, cycle)
        return Response(result, status=status.HTTP_200_OK)


class CycleQuotaViewSet(LOFARViewSet):
    queryset = models.CycleQuota.objects.all()
    serializer_class = serializers.CycleQuotaSerializer

    def get_queryset(self):
        queryset = models.CycleQuota.objects.all()

        # query by project
        project = self.request.query_params.get('project', None)
        if project is not None:
            return queryset.filter(project=project)

        return queryset
class ProjectViewSet(LOFARViewSet):
    queryset = models.Project.objects.all()
    serializer_class = serializers.ProjectSerializer
Jorrit Schaap's avatar
Jorrit Schaap committed
    ordering = ['name']
    def get_queryset(self):
        queryset = models.Project.objects.all()

        # query by cycle
        cycle = self.request.query_params.get('cycle', None)
        if cycle is not None:
            return queryset.filter(cycles__name=cycle)

        return queryset

    @swagger_auto_schema(responses={200: 'The Report information',
                                    403: 'forbidden'},
                         operation_description="Get Report information for the project.")
    @action(methods=['get'], detail=True, url_name="report", name="Get Report")
    def report(self, request, pk=None):
        project = get_object_or_404(models.Project, pk=pk)
        result = create_project_report(request, project)
        return Response(result, status=status.HTTP_200_OK)

class ProjectNestedViewSet(LOFARNestedViewSet):
    queryset = models.Project.objects.all()
    serializer_class = serializers.ProjectSerializer

        if 'cycle_id' in self.kwargs:
            cycle = get_object_or_404(models.Cycle, pk=self.kwargs['cycle_id'])
            return cycle.projects.all()
        else:
            return models.Project.objects.all()

class ProjectQuotaViewSet(LOFARViewSet):
    queryset = models.ProjectQuota.objects.all()
    serializer_class = serializers.ProjectQuotaSerializer

    def get_queryset(self):
        queryset = models.ProjectQuota.objects.all()

        # query by project
        project = self.request.query_params.get('project', None)
        if project is not None:
            return queryset.filter(project=project)

        return queryset


class ProjectQuotaArchiveLocationViewSet(LOFARViewSet):
    queryset = models.ProjectQuotaArchiveLocation.objects.all()
    serializer_class = serializers.ProjectQuotaArchiveLocationSerializer

    def get_queryset(self):
        queryset = models.ProjectQuotaArchiveLocation.objects.all()

        # query by project
        project = self.request.query_params.get('project', None)
        if project is not None:
            return queryset.filter(project=project)

        return queryset

Fabio Vitello's avatar
Fabio Vitello committed

class ResourceTypeViewSet(LOFARViewSet):
    queryset = models.ResourceType.objects.all()
    serializer_class = serializers.ResourceTypeSerializer

class SchedulingSetViewSet(LOFARViewSet):
    queryset = models.SchedulingSet.objects.all()
    serializer_class = serializers.SchedulingSetSerializer
class SystemSettingFlagViewSet(LOFARViewSet):
    queryset = models.SystemSettingFlag.objects.all()
    serializer_class = serializers.SystemSettingFlagSerializer


class SettingViewSet(LOFARViewSet):
    queryset = models.Setting.objects.all()
    serializer_class = serializers.SettingSerializer


class QuantityViewSet(LOFARViewSet):
    queryset = models.Quantity.objects.all()
    serializer_class = serializers.QuantitySerializer


class PeriodCategoryViewSet(LOFARViewSet):
    queryset = models.PeriodCategory.objects.all()
    serializer_class = serializers.PeriodCategorySerializer


class ProjectCategoryViewSet(LOFARViewSet):
    queryset = models.ProjectCategory.objects.all()
    serializer_class = serializers.ProjectCategorySerializer


class SchedulingUnitDraftPropertyFilter(property_filters.PropertyFilterSet):
    project = property_filters.PropertyCharFilter(field_name='project')

    class Meta:
        model = models.SchedulingUnitDraft
        fields = '__all__'
        filter_overrides = {
            models.JSONField: {
                'filter_class': property_filters.CharFilter,
            },
            models.ArrayField: {
                'filter_class': property_filters.CharFilter,
                'extra': lambda f: {'lookup_expr': 'icontains'}
            },
        }
class SchedulingUnitDraftViewSet(LOFARViewSet):
    queryset = models.SchedulingUnitDraft.objects.all()
    serializer_class = serializers.SchedulingUnitDraftSerializer
    filter_class = SchedulingUnitDraftPropertyFilter    # note that this breaks other filter backends from LOFARViewSet
    # prefetch all reverse related references from other models on their related_name to avoid a ton of duplicate queries
    queryset = queryset.prefetch_related('copied_from') \
                       .prefetch_related('scheduling_unit_blueprints')\
                       .prefetch_related('task_drafts')

    # preselect all references to other models to avoid even more duplicate queries
    queryset = queryset.select_related('copies') \
                       .select_related('copy_reason') \
                       .select_related('scheduling_set')

    # use select_related for forward related references
    queryset = queryset.select_related('copy_reason', 'scheduling_set', 'requirements_template', 'observation_strategy_template', 'scheduling_constraints_template')


Jorrit Schaap's avatar
Jorrit Schaap committed
    @swagger_auto_schema(responses={201: 'The Created SchedulingUnitBlueprint, see Location in Response header',
Jorrit Schaap's avatar
Jorrit Schaap committed
                                    403: 'forbidden'},
Jorrit Schaap's avatar
Jorrit Schaap committed
                         operation_description="Carve SchedulingUnitDraft in stone, and make an (uneditable) blueprint out of it.")
    @action(methods=['post'], detail=True, url_name="create_scheduling_unit_blueprint", name="Create SchedulingUnitBlueprint")
Jorrit Schaap's avatar
Jorrit Schaap committed
    def create_scheduling_unit_blueprint(self, request, pk=None):
        scheduling_unit_draft = get_object_or_404(models.SchedulingUnitDraft, pk=pk)
        scheduling_unit_blueprint = create_scheduling_unit_blueprint_from_scheduling_unit_draft(scheduling_unit_draft)

        # url path magic to construct the new scheduling_unit_blueprint_path url
        scheduling_unit_draft_path = request._request.path
        base_path = scheduling_unit_draft_path[:scheduling_unit_draft_path.find('/scheduling_unit_draft')]
        scheduling_unit_blueprint_path = '%s/scheduling_unit_blueprint/%s/' % (base_path, scheduling_unit_blueprint.id,)

        # return a response with the new serialized SchedulingUnitBlueprintSerializer, and a Location to the new instance in the header
        return Response(serializers.SchedulingUnitBlueprintSerializer(scheduling_unit_blueprint, context={'request':request}).data,
                        status=status.HTTP_201_CREATED,
                        headers={'Location': scheduling_unit_blueprint_path})

Jorrit Schaap's avatar
Jorrit Schaap committed
    @swagger_auto_schema(responses={201: 'The Created SchedulingUnitBlueprint, see Location in Response header',
                                    403: 'forbidden'},
                         operation_description="Carve this SchedulingUnitDraft and its TaskDraft(s) in stone, and make blueprint(s) out of it and create their subtask(s), and schedule the ones that are not dependend on predecessors")
    @action(methods=['post'], detail=True, url_name="create_blueprints_and_schedule", name="Create Blueprints-Tree and Schedule")
    def create_blueprints_and_schedule(self, request, pk=None):
        scheduling_unit_draft = get_object_or_404(models.SchedulingUnitDraft, pk=pk)
        scheduling_unit_blueprint = create_task_blueprints_and_subtasks_and_schedule_subtasks_from_scheduling_unit_draft(scheduling_unit_draft)

        # url path magic to construct the new scheduling_unit_blueprint_path url
        scheduling_unit_draft_path = request._request.path
        base_path = scheduling_unit_draft_path[:scheduling_unit_draft_path.find('/scheduling_unit_draft')]
        scheduling_unit_blueprint_path = '%s/scheduling_unit_blueprint/%s/' % (base_path, scheduling_unit_blueprint.id,)

        # return a response with the new serialized SchedulingUnitBlueprintSerializer, and a Location to the new instance in the header
        return Response(serializers.SchedulingUnitBlueprintSerializer(scheduling_unit_blueprint, context={'request':request}).data,
                        status=status.HTTP_201_CREATED,
                        headers={'Location': scheduling_unit_blueprint_path})
    @swagger_auto_schema(responses={201: 'The Created SchedulingUnitBlueprint, see Location in Response header',
                                    403: 'forbidden'},
                         operation_description="Carve this SchedulingUnitDraft and its TaskDraft(s) in stone, and make blueprint(s) out of it and create their subtask(s)")
    @action(methods=['post'], detail=True, url_name="create_blueprints_and_subtasks", name="Create Blueprints-Tree")
Jorrit Schaap's avatar
Jorrit Schaap committed
    def create_blueprints_and_subtasks(self, request, pk=None):
        scheduling_unit_draft = get_object_or_404(models.SchedulingUnitDraft, pk=pk)
        scheduling_unit_blueprint = create_task_blueprints_and_subtasks_from_scheduling_unit_draft(scheduling_unit_draft)

        # url path magic to construct the new scheduling_unit_blueprint_path url
        scheduling_unit_draft_path = request._request.path
        base_path = scheduling_unit_draft_path[:scheduling_unit_draft_path.find('/scheduling_unit_draft')]
        scheduling_unit_blueprint_path = '%s/scheduling_unit_blueprint/%s/' % (base_path, scheduling_unit_blueprint.id,)

        # return a response with the new serialized SchedulingUnitBlueprintSerializer, and a Location to the new instance in the header
        return Response(serializers.SchedulingUnitBlueprintSerializer(scheduling_unit_blueprint, context={'request':request}).data,
                        status=status.HTTP_201_CREATED,
                        headers={'Location': scheduling_unit_blueprint_path})
    @swagger_auto_schema(responses={201: 'The updated scheduling_unit_draft with references to its created task_drafts',
                         operation_description="Create Task Drafts from SchedulingUnitDraft.")
    @action(methods=['post'], detail=True, url_name="create_task_drafts", name="Create Task Drafts from Requirement doc")
    def create_task_drafts(self, request, pk=None):
        scheduling_unit_draft = get_object_or_404(models.SchedulingUnitDraft, pk=pk)
        create_task_drafts_from_scheduling_unit_draft(scheduling_unit_draft)
        # just return as a response the serialized scheduling_unit_draft (with references to the created task_draft(s)
        return Response(serializers.SchedulingUnitDraftSerializer(scheduling_unit_draft,
                        context={'request':request}).data,
                        status=status.HTTP_201_CREATED)


class SchedulingUnitDraftExtendedViewSet(SchedulingUnitDraftViewSet):
    serializer_class = serializers.SchedulingUnitDraftExtendedSerializer


class SchedulingUnitDraftNestedViewSet(LOFARNestedViewSet):
    queryset = models.SchedulingUnitDraft.objects.all()
    serializer_class = serializers.SchedulingUnitDraftSerializer

        if 'scheduling_set_id' in self.kwargs:
            scheduling_set = get_object_or_404(models.SchedulingSet, pk=self.kwargs['scheduling_set_id'])
            return scheduling_set.scheduling_unit_drafts.all()
        else:
            return models.SchedulingUnitDraft.objects.all()

class TaskDraftCopyViewSet(LOFARCopyViewSet):
    queryset = models.TaskDraft.objects.all()
    serializer_class = serializers.TaskDraftSerializer

    @swagger_auto_schema(responses={201: 'The new Task Draft',
                                    403: 'forbidden'},
                         operation_description="Copy a Task Draft to a new Task Draft")
    def create(self, request, *args, **kwargs):
        if 'task_draft_id' in kwargs:
            task_draft = get_object_or_404(models.TaskDraft, pk=kwargs["task_draft_id"])
            body_unicode = request.body.decode('utf-8')
            body_data = json.loads(body_unicode)
            copy_reason = body_data.get('copy_reason', None)
            try:
                copy_reason_obj = models.CopyReason.objects.get(value=copy_reason)
            except ObjectDoesNotExist:
                logger.info("CopyReason matching query does not exist.")
                #if a non valid copy_reason is specified, set copy_reason to None
                copy_reason = None

            task_draft_copy = copy_task_draft(task_draft,copy_reason)


            # url path magic to construct the new task_draft_path url
            task_draft_path = request._request.path
            base_path = task_draft_path[:task_draft_path.find('/task_draft')]
            task_draft_copy_path = '%s/task_draft/%s/' % (base_path, task_draft_copy.id,)

            # return a response with the new serialized SchedulingUnitBlueprintSerializer, and a Location to the new instance in the header
            return Response(serializers.TaskDraftSerializer(task_draft_copy, context={'request':request}).data,
                            status=status.HTTP_201_CREATED,
                            headers={'Location': task_draft_copy_path})
        else:
            content = {'Error': 'scheduling_unit_draft_id is missing'}
            return Response(content, status=status.HTTP_404_NOT_FOUND)

class SchedulingUnitDraftCopyViewSet(LOFARCopyViewSet):
    queryset = models.SchedulingUnitDraft.objects.all()
    serializer_class = serializers.SchedulingUnitDraftCopySerializer

    @swagger_auto_schema(responses={201: 'The new scheduling_unit_draft',
                                    403: 'forbidden'},
                         operation_description="Copy a Scheduling Unit Draft to a new Scheduling Unit Draft")
    def create(self, request, *args, **kwargs):
        if 'scheduling_unit_draft_id' in kwargs:
            scheduling_unit_draft = get_object_or_404(models.SchedulingUnitDraft, pk=kwargs['scheduling_unit_draft_id'])
            scheduling_set = scheduling_unit_draft.scheduling_set

            body_unicode = request.body.decode('utf-8')
            body_data = json.loads(body_unicode)
            copy_reason = body_data.get('copy_reason', None)
            try:
                copy_reason_obj = models.CopyReason.objects.get(value=copy_reason)
            except ObjectDoesNotExist:
                logger.info("CopyReason matching query does not exist.")
                #if a non valid copy_reason is specified, set copy_reason to None
                copy_reason = None
            scheduling_set_id = body_data.get('scheduling_set_id', None)
            logger.info(scheduling_set_id)
            if scheduling_set_id is not None:
                try:
                    scheduling_set =  models.SchedulingSet.objects.get(id=scheduling_set_id)
                except ObjectDoesNotExist:
                    logger.info("scheduling Set does not exist.")

            scheduling_unit_draft_copy = copy_scheduling_unit_draft(scheduling_unit_draft,scheduling_set,copy_reason)
            # url path magic to construct the new scheduling_unit_draft_path url
            scheduling_unit_draft_path = request._request.path
            base_path = scheduling_unit_draft_path[:scheduling_unit_draft_path.find('/scheduling_unit_draft')]
            scheduling_unit_draft_copy_path = '%s/scheduling_unit_draft/%s/' % (base_path, scheduling_unit_draft_copy.id,)

            # return a response with the new serialized SchedulingUnitBlueprintSerializer, and a Location to the new instance in the header
            return Response(serializers.SchedulingUnitDraftSerializer(scheduling_unit_draft_copy, context={'request':request}).data,
                            status=status.HTTP_201_CREATED,
                            headers={'Location': scheduling_unit_draft_copy_path})
        else:
            content = {'Error': 'scheduling_unit_draft_id is missing'}
            return Response(content, status=status.HTTP_404_NOT_FOUND)


class SchedulingUnitDraftCopyFromSchedulingSetViewSet(LOFARCopyViewSet):
    queryset = models.SchedulingUnitDraft.objects.all()
    serializer_class = serializers.SchedulingUnitDraftCopyFromSchedulingSetSerializer

    def get_queryset(self):
        if 'scheduling_set_id' in self.kwargs:
            scheduling_set = get_object_or_404(models.SchedulingSet, pk=self.kwargs['scheduling_set_id'])
            return scheduling_set.scheduling_unit_drafts.all()
        else:
            return models.SchedulingUnitDraft.objects.all()
    @swagger_auto_schema(responses={201: "The TaskDrafts copied from the TaskDrafts in this Scheduling Unit Set",
                                    403: 'forbidden'},
                         operation_description="Create a copy of all the TaskDrafts in this Scheduling Unit Set.")
    def create(self, request, *args, **kwargs):
        if 'scheduling_set_id' in kwargs:
            scheduling_set = get_object_or_404(models.SchedulingSet, pk=kwargs['scheduling_set_id'])
            scheduling_unit_drafts = scheduling_set.scheduling_unit_drafts.all()
            body_unicode = request.body.decode('utf-8')
            body_data = json.loads(body_unicode)
            copy_reason = body_data.get('copy_reason', None)
            try:
                copy_reason_obj = models.CopyReason.objects.get(value=copy_reason)
            except ObjectDoesNotExist:
                logger.info("CopyReason matching query does not exist.")
                #if a non valid copy_reason is specified, set copy_reason to None
                copy_reason = None

            scheduling_unit_draft_copy_path=[]
            for scheduling_unit_draft in scheduling_unit_drafts:
                scheduling_unit_draft_copy = copy_scheduling_unit_draft(scheduling_unit_draft,scheduling_set,copy_reason)
                # url path magic to construct the new scheduling_unit_draft url
                copy_scheduling_unit_draft_path = request._request.path
                base_path = copy_scheduling_unit_draft_path[:copy_scheduling_unit_draft_path.find('/copy_scheduling_unit_drafts')]
                scheduling_unit_draft_copy_path += ['%s/copy_scheduling_unit_drafts/%s/' % (base_path, scheduling_unit_draft_copy.id,)]

            # just return as a response the serialized scheduling_set (with references to the created copy_scheduling_unit_draft(s)
            return Response(serializers.SchedulingSetSerializer(scheduling_set, context={'request':request}).data,status=status.HTTP_201_CREATED)
        else:
            content = {'Error': 'scheduling_set_id is missing'}
            return Response(content, status=status.HTTP_404_NOT_FOUND)

class SchedulingUnitBlueprintCopyToSchedulingUnitDraftViewSet(LOFARCopyViewSet):
    queryset = models.SchedulingUnitBlueprint.objects.all()
    serializer_class = serializers.SchedulingUnitBlueprintCopyToSchedulingUnitDraftSerializer
    @swagger_auto_schema(responses={201: "The copy of the SchedulingUnitDraft",
                                    403: 'forbidden'},
                         operation_description="Create a SchedulingUnitDraft from the SchedulingUnitBlueprint")
    def create(self, request, *args, **kwargs):
        if 'scheduling_unit_blueprint_id' in kwargs:
            scheduling_unit_blueprint = get_object_or_404(models.SchedulingUnitBlueprint, pk=kwargs['scheduling_unit_blueprint_id'])

            body_unicode = request.body.decode('utf-8')
            body_data = json.loads(body_unicode)
            copy_reason = body_data.get('copy_reason', None)
            try:
                copy_reason_obj = models.CopyReason.objects.get(value=copy_reason)
            except ObjectDoesNotExist:
                logger.info("CopyReason matching query does not exist.")
                #if a non valid copy_reason is specified, set copy_reason to None
                copy_reason = None

            scheduling_unit_draft = create_scheduling_unit_draft_from_scheduling_unit_blueprint(scheduling_unit_blueprint,copy_reason)

            # return a response with the new serialized scheduling_unit_blueprint (with references to the created task_blueprint(s) and (scheduled) subtasks)
            return Response(serializers.SchedulingUnitDraftSerializer(scheduling_unit_draft, context={'request':request}).data,
                            status=status.HTTP_201_CREATED)
        else:
            content = {'Error': 'scheduling_unit_draft_id is missing'}
            return Response(content, status=status.HTTP_404_NOT_FOUND)
class TaskBlueprintCopyToTaskDraftViewSet(LOFARCopyViewSet):
    queryset = models.SchedulingUnitBlueprint.objects.all()
    serializer_class = serializers.SchedulingUnitBlueprintCopyToSchedulingUnitDraftSerializer
    @swagger_auto_schema(responses={201: "The TaskDraft created from this TaskBlueprint",
                                    403: 'forbidden'},
                         operation_description="Copy this TaskBlueprint to a new TaskDraft.")
    def create(self, request, *args, **kwargs):
        if 'task_blueprint_id' in kwargs:
            task_blueprint = get_object_or_404(models.TaskBlueprint, pk=kwargs['task_blueprint_id'])
            task_draft = copy_task_blueprint_to_task_draft(task_blueprint)

            # return a response with the new serialized scheduling_unit_blueprint (with references to the created task_blueprint(s) and (scheduled) subtasks)
            return Response(serializers.TaskDraftSerializer(task_draft, context={'request':request}).data,
                        status=status.HTTP_201_CREATED)

        else:
            content = {'Error': 'task_blueprint_id is missing'}
            return Response(content, status=status.HTTP_404_NOT_FOUND)
class SchedulingUnitBlueprintPropertyFilter(property_filters.PropertyFilterSet):
    start_time = property_filters.PropertyIsoDateTimeFromToRangeFilter(field_name='start_time')
    stop_time = property_filters.PropertyIsoDateTimeFromToRangeFilter(field_name='stop_time')
    project = property_filters.PropertyCharFilter(field_name='project')
    status = property_filters.PropertyCharFilter(field_name='status')
        fields = '__all__'
        filter_overrides = {
            models.JSONField: {
                'filter_class': property_filters.CharFilter,
            },
            models.ArrayField: {
                'filter_class': property_filters.CharFilter,
                'extra': lambda f: {'lookup_expr': 'icontains'}
            },
        }
class SchedulingUnitBlueprintViewSet(LOFARViewSet):
    queryset = models.SchedulingUnitBlueprint.objects.all()
    serializer_class = serializers.SchedulingUnitBlueprintSerializer
    filter_class = SchedulingUnitBlueprintPropertyFilter  # note that this breaks other filter backends from LOFARViewSet
    # prefetch all reverse related references from other models on their related_name to avoid a ton of duplicate queries
    queryset = queryset.prefetch_related('task_blueprints')

    # use select_related for forward related references
    queryset = queryset.select_related('requirements_template', 'draft')

    @swagger_auto_schema(responses={201: "This SchedulingUnitBlueprint, with references to its created TaskBlueprints and (scheduled) Subtasks.",
                                    403: 'forbidden'},
                         operation_description="Create TaskBlueprint(s) for this scheduling unit, create subtasks, and schedule the ones that are not dependend on predecessors.")
    @action(methods=['post'], detail=True, url_name="create_taskblueprints_subtasks_and_schedule", name="Create TaskBlueprint(s), their Subtask(s) and schedule them.")
    def create_taskblueprints_subtasks_and_schedule(self, request, pk=None):
        scheduling_unit_blueprint = get_object_or_404(models.SchedulingUnitBlueprint, pk=pk)
        scheduling_unit_blueprint = create_task_blueprints_and_subtasks_and_schedule_subtasks_from_scheduling_unit_blueprint(scheduling_unit_blueprint)

        # return a response with the new serialized scheduling_unit_blueprint (with references to the created task_blueprint(s) and (scheduled) subtasks)
        return Response(serializers.SchedulingUnitBlueprintSerializer(scheduling_unit_blueprint, context={'request':request}).data,
                        status=status.HTTP_201_CREATED)

    @swagger_auto_schema(responses={201: "This SchedulingUnitBlueprint, with references to its created TaskBlueprints and Subtasks.",
                                    403: 'forbidden'},
                         operation_description="Create TaskBlueprint(s) for this scheduling unit and create subtasks.")
    @action(methods=['post'], detail=True, url_name="create_taskblueprints_subtasks", name="Create TaskBlueprint(s) and their Subtask(s)")
    def create_taskblueprints_subtasks(self, request, pk=None):
        scheduling_unit_blueprint = get_object_or_404(models.SchedulingUnitBlueprint, pk=pk)
        scheduling_unit_blueprint = create_task_blueprints_and_subtasks_from_scheduling_unit_blueprint(scheduling_unit_blueprint)

        # return a response with the new serialized scheduling_unit_blueprint (with references to the created task_blueprint(s) and (scheduled) subtasks)
        return Response(serializers.SchedulingUnitBlueprintSerializer(scheduling_unit_blueprint, context={'request':request}).data,
                        status=status.HTTP_201_CREATED)

    @swagger_auto_schema(responses={201: "This SchedulingUnitBlueprint, with references to its created TaskBlueprints.",
                                    403: 'forbidden'},
                         operation_description="Create the TaskBlueprint(s).")
    @action(methods=['post'], detail=True, url_name="create_taskblueprints", name="Create TaskBlueprint(s)")
    def create_taskblueprints(self, request, pk=None):
        scheduling_unit_blueprint = get_object_or_404(models.SchedulingUnitBlueprint, pk=pk)
        scheduling_unit_blueprint = create_task_blueprints_from_scheduling_unit_blueprint(scheduling_unit_blueprint)

        # return a response with the new serialized scheduling_unit_blueprint (with references to the created task_blueprint(s) and (scheduled) subtasks)
        return Response(serializers.SchedulingUnitBlueprintSerializer(scheduling_unit_blueprint, context={'request':request}).data,
                        status=status.HTTP_201_CREATED)

    @swagger_auto_schema(responses={200: 'The available logging urls for all Subtasks of this SchedulingUnitBlueprint.',
                                    403: 'forbidden'},
                         operation_description="Get the subtask logging urls of this schedulingunit blueprint.")
    @action(methods=['get'], detail=True, url_name='get_all_subtasks_log_urls')
    def get_all_subtasks_log_urls(self, request, pk=None):
        subtasks = models.Subtask.objects.filter(task_blueprints__scheduling_unit_blueprint_id=pk)
        result = []
        for subtask in subtasks:
            if subtask.log_url != "":
                result.append({"subtaskid": subtask.id, "type": subtask.specifications_template.type.value, "log_url": subtask.log_url})
        # TypeError: In order to allow non-dict objects to be serialized set the safe parameter to False.
        # result is list of dict so thats why
        return JsonResponse(result, safe=False)

    @swagger_auto_schema(responses={200: 'The cancelled version of this scheduling_unit',
                                    403: 'forbidden',
                                    500: 'The subtask scheduling_unit not be cancelled'},
                         operation_description="Try to cancel this scheduling_unit.")
    @action(methods=['post'], detail=True, url_name="cancel")
    def cancel(self, request, pk=None):
        scheduling_unit_blueprint = get_object_or_404(models.SchedulingUnitBlueprint, pk=pk)
        from lofar.sas.tmss.tmss.tmssapp.tasks import cancel_scheduling_unit_blueprint
        scheduling_unit_blueprint = cancel_scheduling_unit_blueprint(scheduling_unit_blueprint)
        serializer = self.get_serializer(scheduling_unit_blueprint)
        return RestResponse(serializer.data)


    @swagger_auto_schema(responses={200: "All Subtasks in this SchedulingUnitBlueprint",
                                    403: 'forbidden'},
                         operation_description="Get all subtasks for this scheduling_unit")
    @action(methods=['get'], detail=True, url_name="subtasks", name="all subtasks in this scheduling_unit")
    def subtasks(self, request, pk=None):
        subtasks = models.Subtask.objects.all().filter(task_blueprint__scheduling_unit_blueprint_id=pk). \
                                                select_related('state', 'specifications_template', 'specifications_template__type', 'cluster', 'created_or_updated_by_user').all()

        # return a response with the new serialized scheduling_unit_blueprint (with references to the created task_blueprint(s) and (scheduled) subtasks)
        return Response(serializers.SubtaskSerializer(subtasks, many=True, context={'request':request}).data,
                        status=status.HTTP_200_OK)

    @swagger_auto_schema(responses={201: "This SchedulingUnitBlueprint, with references to the created Cleanup TaskBlueprints.",
                                    403: 'forbidden'},
                         operation_description="Create a cleanup task for this scheduling unit.")
    @action(methods=['post'], detail=True, url_name="create_cleanuptask", name="Create a cleanup task for this scheduling unit")
    def create_cleanuptask_for_scheduling_unit_blueprint(self, request, pk=None):
        scheduling_unit_blueprint = get_object_or_404(models.SchedulingUnitBlueprint, pk=pk)
        scheduling_unit_blueprint = create_cleanuptask_for_scheduling_unit_blueprint(scheduling_unit_blueprint)

        # return a response with the new serialized scheduling_unit_blueprint (with references to the created task_blueprint and subtask)
        return Response(serializers.SchedulingUnitBlueprintSerializer(scheduling_unit_blueprint, context={'request':request}).data,
                        status=status.HTTP_201_CREATED)

class SchedulingUnitBlueprintExtendedViewSet(SchedulingUnitBlueprintViewSet):
    serializer_class = serializers.SchedulingUnitBlueprintExtendedSerializer


class SchedulingUnitBlueprintNestedViewSet(LOFARNestedViewSet):
    queryset = models.SchedulingUnitBlueprint.objects.all()
    serializer_class = serializers.SchedulingUnitBlueprintSerializer

        if 'scheduling_unit_draft_id' in self.kwargs:
            scheduling_unit_draft = get_object_or_404(models.SchedulingUnitDraft, pk=self.kwargs['scheduling_unit_draft_id'])
Jorrit Schaap's avatar
Jorrit Schaap committed
            return scheduling_unit_draft.scheduling_unit_blueprints.all()
        else:
            return models.SchedulingUnitBlueprint.objects.all()

    queryset = models.TaskDraft.objects.all()
    serializer_class = serializers.TaskDraftSerializer
    # prefetch all reverse related references from other models on their related_name to avoid a ton of duplicate queries
    queryset = queryset.prefetch_related('first_scheduling_relation') \
                       .prefetch_related('second_scheduling_relation')\
                       .prefetch_related('produced_by')\
                       .prefetch_related('consumed_by')\
                       .prefetch_related('task_blueprints')\
                       .prefetch_related('copied_from')

    # prefetch nested references in reverse models to avoid duplicate lookup queries
    queryset = queryset.prefetch_related('first_scheduling_relation__placement') \
                       .prefetch_related('second_scheduling_relation__placement')

    # select all references to other models to avoid even more duplicate queries
    queryset = queryset.select_related('copies') \
                       .select_related('copy_reason')

    # # do not permit listing if the queryset contains objects that the user has not permission for.
    # # Note: this is not required if we apply correct filtering, but it's a nice check that we do filter correctly.
    # # Do not check on other actions, as the queryset might contain items that will be filtered later.
    # # todo: see if there is something like a get_filtered_queryset that always only includes what will be returned
    # #  to the user, so we can always check object permissions on everything.
    # # todo: this causes a recursion error if has_permission is called from has_object_permission in TMSSPermissions
    # #  Probably a non-issue since we do the filtering anyway.
    # def get_queryset(self):
    #     qs = super().get_queryset()
    #     if self.action == 'list':
    #         qs = self.filter_queryset(qs)
    #         for obj in qs:
    #             self.check_object_permissions(self.request, obj)
    #     return qs
    @swagger_auto_schema(responses={201: 'The created task blueprint, see Location in Response header',
                                    403: 'forbidden'},
                         operation_description="Carve this draft task specification in stone, and make an (uneditable) blueprint out of it.")
    @action(methods=['post'], detail=True, url_name="create_task_blueprint", name="Create TaskBlueprint")
    def create_task_blueprint(self, request, pk=None):
        task_draft = get_object_or_404(models.TaskDraft, pk=pk)
        task_blueprint = create_task_blueprint_from_task_draft(task_draft)

        # url path magic to construct the new task_blueprint_path url
        task_draft_path = request._request.path
        base_path = task_draft_path[:task_draft_path.find('/task_draft')]
        task_blueprint_path = '%s/task_blueprint/%s/' % (base_path, task_blueprint.id,)

        # return a response with the new serialized TaskBlueprint, and a Location to the new instance in the header
        return Response(serializers.TaskBlueprintSerializer(task_blueprint, context={'request':request}).data,
                        status=status.HTTP_201_CREATED,
                        headers={'Location': task_blueprint_path})
    @swagger_auto_schema(responses={201: "This TaskBlueprint, with its created (and some scheduled) subtasks",
                                    403: 'forbidden'},
                         operation_description="Create subtasks, and schedule the ones that are not dependend on predecessors.")
    @action(methods=['post'], detail=True, url_name="create_task_blueprint_subtasks_and_schedule", name="Create TaskBlueprint, its Subtask(s) and Schedule")
    def create_task_blueprint_subtasks_and_schedule(self, request, pk=None):
        task_draft = get_object_or_404(models.TaskDraft, pk=pk)
        task_blueprint = create_task_blueprint_and_subtasks_and_schedule_subtasks_from_task_draft(task_draft)

        # url path magic to construct the new task_blueprint_path url
        task_draft_path = request._request.path
        base_path = task_draft_path[:task_draft_path.find('/task_draft')]
        task_blueprint_path = '%s/task_blueprint/%s/' % (base_path, task_blueprint.id,)

        # return a response with the new serialized TaskBlueprint, and a Location to the new instance in the header
        return Response(serializers.TaskBlueprintSerializer(task_blueprint, context={'request':request}).data,
                        status=status.HTTP_201_CREATED,
                        headers={'Location': task_blueprint_path})


    @swagger_auto_schema(responses={201: "This TaskBlueprint, with its created subtask(s)",
                                    403: 'forbidden'},
                         operation_description="Create subtasks.")
    @action(methods=['post'], detail=True, url_name="create_task_blueprint_subtasks", name="Create TaskBlueprint and its Subtask(s)")
    def create_task_blueprint_and_subtasks(self, request, pk=None):
        task_draft = get_object_or_404(models.TaskDraft, pk=pk)
        task_blueprint = create_task_blueprint_and_subtasks_from_task_draft(task_draft)

        # url path magic to construct the new task_blueprint_path url
        task_draft_path = request._request.path
        base_path = task_draft_path[:task_draft_path.find('/task_draft')]
        task_blueprint_path = '%s/task_blueprint/%s/' % (base_path, task_blueprint.id,)

        # return a response with the new serialized TaskBlueprint, and a Location to the new instance in the header
        return Response(serializers.TaskBlueprintSerializer(task_blueprint, context={'request':request}).data,
                        status=status.HTTP_201_CREATED,
                        headers={'Location': task_blueprint_path})


    @swagger_auto_schema(responses={200: 'The predecessor task draft of this task draft',
                                    403: 'forbidden'},
                         operation_description="Get the predecessor task draft of this task draft.")
    @action(methods=['get'], detail=True, url_name="predecessors")
    def predecessors(self, request, pk=None):
        task_draft = get_object_or_404(models.TaskDraft, pk=pk)
        predecessors = self.filter_queryset(task_draft.predecessors)
        serializer = self.get_serializer(predecessors, many=True)
        return Response(serializer.data)

    @swagger_auto_schema(responses={200: 'The successor subtasks of this subtask',
                                    403: 'forbidden'},
                         operation_description="Get the successor subtasks of this subtask.")
    @action(methods=['get'], detail=True, url_name="successors")
    def successors(self, request, pk=None):
        task_draft = get_object_or_404(models.TaskDraft, pk=pk)
        successors = self.filter_queryset(task_draft.successors)