1
0
Fork 0
mirror of https://github.com/retspen/webvirtcloud synced 2026-11-11 04:36:48 +00:00

feat(operations): jobs API

This commit is contained in:
catborise 2026-10-13 19:33:48 +03:00
parent 079e2ca5ca
commit 9c125aedd2
7 changed files with 218 additions and 2 deletions

View file

View file

@ -0,0 +1,43 @@
from django.utils.translation import gettext as _
from rest_framework import serializers
from operations.jobs import KINDS, label_of
from operations.models import Operation
from operations.utils import shown_label
class OperationSerializer(serializers.ModelSerializer):
label = serializers.SerializerMethodField()
resource_label = serializers.SerializerMethodField()
cancellable = serializers.SerializerMethodField()
class Meta:
model = Operation
fields = ["id", "kind", "label", "username", "resource", "resource_label", "state", "phase",
"progress_done", "progress_total", "result", "error", "created_at", "started_at",
"finished_at", "cancel_requested", "cancellable", "resolution", "resolution_note"]
read_only_fields = fields
def get_label(self, op) -> str:
return str(_(label_of(op.kind)))
def get_resource_label(self, op) -> str:
return shown_label(op, self.context["request"].user)
def get_cancellable(self, op) -> bool:
kind = KINDS.get(op.kind)
return op.state == Operation.QUEUED or bool(kind and kind.cancellable and op.state == Operation.RUNNING)
class OperationSummarySerializer(serializers.Serializer):
kind = serializers.CharField()
label = serializers.CharField()
state = serializers.CharField()
since = serializers.DateTimeField()
class ResourceJobsSerializer(serializers.Serializer):
jobs = OperationSerializer(many=True)
others = OperationSummarySerializer(many=True)
short_operation = serializers.BooleanField()
worker_last_seen = serializers.DateTimeField(allow_null=True)

View file

@ -0,0 +1,58 @@
from django.http import Http404
from django.utils import timezone
from drf_spectacular.utils import OpenApiParameter, extend_schema
from rest_framework import mixins, viewsets
from rest_framework.decorators import action
from rest_framework.exceptions import ValidationError
from rest_framework.pagination import PageNumberPagination
from rest_framework.response import Response
from operations import jobs
from operations.models import Claim, Operation, last_seen
from operations.utils import may_see, summary, visible
from .serializers import OperationSerializer, ResourceJobsSerializer
SHOWN_STATES = Operation.ACTIVE + (Operation.UNCERTAIN,)
class Pages(PageNumberPagination):
page_size = 50
class OperationViewSet(mixins.ListModelMixin, mixins.RetrieveModelMixin, viewsets.GenericViewSet):
serializer_class = OperationSerializer
pagination_class = Pages
def get_queryset(self):
if getattr(self, "swagger_fake_view", False): # schema generation has no user
return Operation.objects.none()
user = self.request.user
qs = Operation.objects.all() if user.is_superuser else Operation.objects.filter(user=user)
fields = ("state", "kind", "resource", "username") if user.is_superuser else ("state", "kind", "resource")
for field in fields:
if value := self.request.query_params.get(field):
qs = qs.filter(**{field: value})
return qs
@extend_schema(request=None, responses=OperationSerializer)
@action(detail=True, methods=["post"])
def cancel(self, request, pk=None):
op = self.get_object()
return Response(OperationSerializer(jobs.cancel(op.pk, request.user), context={"request": request}).data)
@extend_schema(parameters=[OpenApiParameter("key", str, required=True)], responses=ResourceJobsSerializer)
@action(detail=False, methods=["get"])
def resource(self, request):
key = request.query_params.get("key", "")
if not key:
raise ValidationError({"key": "required"})
if not visible(request.user, key):
raise Http404
active = list(Operation.objects.filter(claims__resource=key, state__in=SHOWN_STATES).distinct())
mine = [op for op in active if may_see(op, request.user)]
others = [summary(op, request.user) for op in active if not may_see(op, request.user)]
short = Claim.objects.filter(resource=key, operation__isnull=True, expires_at__gt=timezone.now()).exists()
return Response(ResourceJobsSerializer(
{"jobs": mine, "others": others, "short_operation": short, "worker_last_seen": last_seen()},
context={"request": request}).data)