Skip to content
Snippets Groups Projects
views.py 10 KiB
Newer Older
Fanna Lautenbach's avatar
Fanna Lautenbach committed
import time
from typing import Tuple

Mattia Mancini's avatar
Mattia Mancini committed
from django.contrib.auth.models import User
Nico Vermaas's avatar
Nico Vermaas committed
from django.core.exceptions import ObjectDoesNotExist
Fanna Lautenbach's avatar
Fanna Lautenbach committed
from django.http import HttpResponseRedirect
from django.shortcuts import redirect, render
Nico Vermaas's avatar
Nico Vermaas committed
from django.urls import reverse
Fanna Lautenbach's avatar
Fanna Lautenbach committed
from django.views.generic import CreateView, DeleteView, DetailView, UpdateView
from django.views.generic.list import ListView
Nico Vermaas's avatar
Nico Vermaas committed
from django_filters import rest_framework as filters
from rest_framework import generics, status, viewsets
from rest_framework.decorators import action
from rest_framework.response import Response
Fanna Lautenbach's avatar
Fanna Lautenbach committed
from rest_framework.reverse import reverse_lazy
from rest_framework.schemas.openapi import AutoSchema
Nico Vermaas's avatar
Nico Vermaas committed

Nico Vermaas's avatar
Nico Vermaas committed
from .forms import WorkSpecificationForm
Fanna Lautenbach's avatar
Fanna Lautenbach committed
from .models import (
    ATDBProcessingSite,
    DataFilterType,
    DataLocation,
    DataProduct,
    DataProductFilter,
    WorkSpecification,
)
from .serializers import (
    ATDBProcessingSiteSerializer,
    DataLocationSerializer,
    DataProductFlatSerializer,
    DataProductSerializer,
    WorkSpecificationSerializer,
)
from .tasks import insert_task_into_atdb
Nico Vermaas's avatar
Nico Vermaas committed

Mattia Mancini's avatar
Mattia Mancini committed

Fanna Lautenbach's avatar
Fanna Lautenbach committed
def compute_size_of_inputs(inputs: dict) -> Tuple[int, int, int]:
    total_size = 0
    number_of_files = 0

Fanna Lautenbach's avatar
Fanna Lautenbach committed
    if isinstance(inputs, dict) and "size" in inputs:
        total_size = inputs["size"]
Fanna Lautenbach's avatar
Fanna Lautenbach committed
    elif (
        isinstance(inputs, dict)
        or isinstance(inputs, list)
        or isinstance(inputs, tuple)
    ):
        values = inputs
        if isinstance(inputs, dict):
            values = inputs.values()
        for value in values:
Fanna Lautenbach's avatar
Fanna Lautenbach committed
            item_total, item_count, _ = compute_size_of_inputs(value)
            total_size += item_total
            number_of_files += item_count
Fanna Lautenbach's avatar
Fanna Lautenbach committed

    average_file_size = total_size / number_of_files if number_of_files else 0
    return total_size, number_of_files, average_file_size


def format_size(num, suffix="B"):
    if num == 0:
        return "-"
    for unit in ["", "K", "M", "G", "T", "P", "E", "Z"]:
        if abs(num) < 1024.0:
            return f"{num:3.1f}{unit}{suffix}"
        num /= 1024.0
    return f"{num:.1f}Yi{suffix}"
Mattia Mancini's avatar
Mattia Mancini committed
class DynamicFilterSet(filters.FilterSet):
Nico Vermaas's avatar
Nico Vermaas committed
    class Meta:
Mattia Mancini's avatar
Mattia Mancini committed
        filter_class = None

    def __init__(self, *args, **kwargs):
        self._load_filters()
        super().__init__(*args, **kwargs)

    def _load_filters(self):
        if self.Meta.filter_class is None:
Fanna Lautenbach's avatar
Fanna Lautenbach committed
            raise Exception("Define filter_class meta attribute")
Mattia Mancini's avatar
Mattia Mancini committed
        for item in self.Meta.filter_class.objects.all():
            field_obj = self.Meta.model._meta.get_field(item.field)
            filter_class, *_ = self.filter_for_lookup(field_obj, item.lookup_type)
            self.base_filters[item.name] = filter_class(item.field)
Nico Vermaas's avatar
Nico Vermaas committed

Mattia Mancini's avatar
Mattia Mancini committed

# --- Filters ---
class DataProductFilterSet(DynamicFilterSet):
    class Meta:
        model = DataProduct
        filter_class = DataProductFilter
Nico Vermaas's avatar
Nico Vermaas committed
        fields = {
            "obs_id": ["exact", "icontains"],
Nico Vermaas's avatar
Nico Vermaas committed
        }

Mattia Mancini's avatar
Mattia Mancini committed

# ---------- GUI Views -----------
Nico Vermaas's avatar
Nico Vermaas committed

Fanna Lautenbach's avatar
Fanna Lautenbach committed
def api(request):
    atdb_hosts = ATDBProcessingSite.objects.values("name", "url")
Fanna Lautenbach's avatar
Fanna Lautenbach committed
    return render(request, "lofardata/api.html", {"atdb_hosts": atdb_hosts})
Fanna Lautenbach's avatar
Fanna Lautenbach committed
def preprocess_filters_specification_view(specification):
    dataproduct_filters = DataProductFilter.objects.all()
    for dataproduct_filter in dataproduct_filters:
        if (
            specification is not None
            and specification.filters
            and dataproduct_filter.field in specification.filters
        ):
            dataproduct_filter.default = specification.filters[dataproduct_filter.field]
        else:
            dataproduct_filter.default = ""

        if dataproduct_filter.filter_type == DataFilterType.DROPDOWN:
            dataproduct_filter.choices = DataProduct.objects.distinct(
                dataproduct_filter.field
            ).values_list(dataproduct_filter.field)
    return dataproduct_filters
Fanna Lautenbach's avatar
Fanna Lautenbach committed
class Specifications(ListView):
    serializer_class = WorkSpecificationSerializer
    template_name = "lofardata/index.html"
    model = WorkSpecification
    ordering = ["-created_on"]

    def get_queryset(self):
        queryset = WorkSpecification.objects.all()
        current_user: User = self.request.user
        if current_user.is_staff or current_user.is_superuser:
            return queryset.order_by("-created_on")
        return queryset.filter(created_by=current_user.id).order_by("-created_on")
Fanna Lautenbach's avatar
Fanna Lautenbach committed
class WorkSpecificationCreateUpdateView(UpdateView):
    template_name = "lofardata/workspecification/create_update.html"
    model = WorkSpecification
    form_class = WorkSpecificationForm
Fanna Lautenbach's avatar
Fanna Lautenbach committed
    def get_object(self, queryset=None):
        if self.kwargs.__len__() == 0 or self.kwargs["pk"] is None:
            specification = WorkSpecification()
        else:
            specification = WorkSpecification.objects.get(pk=self.kwargs["pk"])
        return specification

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        try:
            specification = WorkSpecification.objects.get(pk=context["object"].pk)
        except ObjectDoesNotExist:
            specification = None
        context["filters"] = preprocess_filters_specification_view(specification)
        return context

    def create_successor(self, specification):
        successor = WorkSpecification()
        successor.predecessor_specification = specification
        successor.processing_site = specification.processing_site
        successor.save()
        return self.get_success_url(pk=successor.pk)

    def form_valid(self, form):
        action_ = form.data["action"]
        specification = form.instance
        if action_ == "Submit":
Nico Vermaas's avatar
Nico Vermaas committed
            specification.async_task_result = None
            specification.is_ready = False
Fanna Lautenbach's avatar
Fanna Lautenbach committed
        if action_ == "Send":
            insert_task_into_atdb.delay(specification.pk)
        if action_ == "Successor":
Nico Vermaas's avatar
Nico Vermaas committed
            specification.save()
            successor = WorkSpecification()
            successor.predecessor_specification = specification
            successor.processing_site = specification.processing_site
Fanna Lautenbach's avatar
Fanna Lautenbach committed
            successor.selected_workflow = specification.selected_workflow
            return HttpResponseRedirect(self.create_successor(specification))
Fanna Lautenbach's avatar
Fanna Lautenbach committed
        return super().form_valid(form)
Fanna Lautenbach's avatar
Fanna Lautenbach committed
    def get_success_url(self, **kwargs):
        if kwargs.__len__() == 0 or kwargs["pk"] is None:
            return reverse_lazy("index")
Nico Vermaas's avatar
Nico Vermaas committed
        else:
Fanna Lautenbach's avatar
Fanna Lautenbach committed
            return reverse_lazy("specification-update", kwargs={"pk": kwargs["pk"]})
Fanna Lautenbach's avatar
Fanna Lautenbach committed

class WorkSpecificationDetailView(DetailView):
    template_name = "lofardata/workspecification/detail.html"
    model = WorkSpecification

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        specification = WorkSpecification.objects.get(pk=context["object"].pk)
        context["filters"] = preprocess_filters_specification_view(specification)
        total_input_size, number_of_files, average_file_size = compute_size_of_inputs(
            specification.inputs
        )
        context["number_of_files"] = number_of_files
        context["total_input_size"] = format_size(total_input_size)
        context["size_per_task"] = format_size(
            average_file_size * specification.batch_size
            if specification.batch_size > 0
            else total_input_size
        )
        return context


class WorkSpecificationDeleteView(DeleteView):
    template_name = "lofardata/workspecification/delete.html"
    model = WorkSpecification
    success_url = reverse_lazy("index")
Fanna Lautenbach's avatar
Fanna Lautenbach committed
class WorkSpecificationInputsView(DetailView):
    template_name = "lofardata/workspecification/inputs.html"
    model = WorkSpecification


class WorkSpecificationATDBTasksView(DetailView):
    template_name = "lofardata/workspecification/tasks.html"
    model = WorkSpecification
# ---------- REST API views ----------
Mattia Mancini's avatar
Mattia Mancini committed
class DataProductView(generics.ListCreateAPIView):
    model = DataProduct
    serializer_class = DataProductSerializer
Nico Vermaas's avatar
Nico Vermaas committed

    queryset = DataProduct.objects.all().order_by("obs_id")
Nico Vermaas's avatar
Nico Vermaas committed

    # using the Django Filter Backend - https://django-filter.readthedocs.io/en/latest/index.html
    filter_backends = (filters.DjangoFilterBackend,)
Mattia Mancini's avatar
Mattia Mancini committed
    filter_class = DataProductFilterSet


Nico Vermaas's avatar
Nico Vermaas committed
class ATDBProcessingSiteView(viewsets.ReadOnlyModelViewSet):
    model = ATDBProcessingSite
    serializer_class = ATDBProcessingSiteSerializer

Fanna Lautenbach's avatar
Fanna Lautenbach committed
    queryset = ATDBProcessingSite.objects.all().order_by("pk")
class DataProductDetailsView(generics.RetrieveUpdateDestroyAPIView):
    model = DataProduct
    serializer_class = DataProductSerializer
    queryset = DataProduct.objects.all()


Mattia Mancini's avatar
Mattia Mancini committed
class DataLocationView(generics.ListCreateAPIView):
    model = DataLocation
    serializer_class = DataLocationSerializer
    queryset = DataLocation.objects.all().order_by("name")
class InsertWorkSpecificationSchema(AutoSchema):
    def get_operation_id_base(self, path, method, action):
        return "createDataProductMulti"
Mattia Mancini's avatar
Mattia Mancini committed
class InsertMultiDataproductView(generics.CreateAPIView):
    """
    Add single DataProduct
    """
Mattia Mancini's avatar
Mattia Mancini committed
    queryset = DataProduct.objects.all()
    serializer_class = DataProductFlatSerializer
    schema = InsertWorkSpecificationSchema()
Mattia Mancini's avatar
Mattia Mancini committed

    def get_serializer(self, *args, **kwargs):
        """if an array is passed, set serializer to many"""
        if isinstance(kwargs.get("data", {}), list):
            kwargs["many"] = True
Mattia Mancini's avatar
Mattia Mancini committed
        return super().get_serializer(*args, **kwargs)
class WorkSpecificationViewset(viewsets.ModelViewSet):
Mattia Mancini's avatar
Mattia Mancini committed
    queryset = WorkSpecification.objects.all()
    serializer_class = WorkSpecificationSerializer

    def get_queryset(self):
        current_user: User = self.request.user
        if not current_user.is_staff or not current_user.is_superuser:
            return self.queryset.filter(created_by=current_user.id)
Mattia Mancini's avatar
Mattia Mancini committed
        else:
            return self.queryset

    @action(detail=True, methods=["POST"])
    def submit(self, request, pk=None) -> Response:
        # TODO: check that there are some matches in the request?
Fanna Lautenbach's avatar
Fanna Lautenbach committed
        insert_task_into_atdb.delay(pk)

        time.sleep(1)   # allow for some time to pass

        return redirect("specification-detail", pk=pk)