From 690254553b1fb5210dda62122c6e5a0883e33996 Mon Sep 17 00:00:00 2001 From: Nikita Manovich Date: Thu, 31 Jan 2019 19:36:37 +0300 Subject: [PATCH] Implemented GET tasks/ID/status and POST tasks/ is in progress. --- .../static/dashboard/js/dashboard.js | 40 ++++++++++--------- .../migrations/0021_task_image_quality.py | 18 +++++++++ cvat/apps/engine/models.py | 3 +- cvat/apps/engine/serializers.py | 18 ++++++--- cvat/apps/engine/urls.py | 5 ++- cvat/apps/engine/views.py | 38 ++++++++++++++++-- 6 files changed, 92 insertions(+), 30 deletions(-) create mode 100644 cvat/apps/engine/migrations/0021_task_image_quality.py diff --git a/cvat/apps/dashboard/static/dashboard/js/dashboard.js b/cvat/apps/dashboard/static/dashboard/js/dashboard.js index d751d046f7e6..c2656374213d 100644 --- a/cvat/apps/dashboard/static/dashboard/js/dashboard.js +++ b/cvat/apps/dashboard/static/dashboard/js/dashboard.js @@ -20,7 +20,7 @@ /* Server requests */ function createTaskRequest(oData, onSuccessRequest, onSuccessCreate, onError, onComplete, onUpdateStatus) { $.ajax({ - url: "/create/task", + url: "/api/v1/tasks/", type: "POST", data: oData, contentType: false, @@ -42,7 +42,7 @@ function createTaskRequest(oData, onSuccessRequest, onSuccessCreate, onError, on let requestInterval = setInterval(function() { $.ajax({ - url: "/check/task/" + tid, + url: "/api/v1/tasks/{}/status".format(tid), success: receiveStatus, error: function(data) { clearInterval(requestInterval); @@ -54,20 +54,20 @@ function createTaskRequest(oData, onSuccessRequest, onSuccessCreate, onError, on function receiveStatus(data) { if (done) return; - if (data["state"] === "created") { + if (data.state === "Finished") { done = true; clearInterval(requestInterval); onComplete(); onSuccessCreate(tid); } - else if (data["state"] === "error") { + else if (data.state === "Failed") { done = true; clearInterval(requestInterval); onComplete(); - onError(data.stderr); + onError(data.message); } - else if (data["state"] === "started" && "status" in data) { - onUpdateStatus(data["status"]); + else if (data["state"] === "Started" && data.message != "") { + onUpdateStatus(data.message); } } } @@ -75,13 +75,12 @@ function createTaskRequest(oData, onSuccessRequest, onSuccessCreate, onError, on function updateTaskRequest(labels) { - let oData = new FormData(); - oData.append("labels", labels); + let data = new LabelsInfo(labels); $.ajax({ - url: "/update/task/" + window.cvat.dashboard.taskID, - type: "POST", - data: oData, + url: "/api/v1/tasks/" + window.cvat.dashboard.taskID, + type: "PATCH", + data: data, contentType: false, processData: false, success: function() { @@ -509,25 +508,28 @@ function setupTaskCreator() { } let taskData = new FormData(); - taskData.append("task_name", name); - taskData.append("bug_tracker_link", bugTrackerLink); + taskData.append("name", name); + taskData.append("bug_tracker", bugTrackerLink); taskData.append("labels", labels); - taskData.append("flip_flag", flipImages); + taskData.append("flipped", flipImages); taskData.append("z_order", zOrder); - taskData.append("storage", source); if (customSegmentSize.prop("checked")) { taskData.append("segment_size", segmentSize); } if (customOverlapSize.prop("checked")) { - taskData.append("overlap_size", overlapSize); + taskData.append("overlap", overlapSize); } if (customCompressQuality.prop("checked")) { - taskData.append("compress_quality", compressQuality); + taskData.append("image_quality", compressQuality); } for (let file of files) { - taskData.append("data", file); + if (source === "local") { + taskData.append("client_files", file); + } else { + taskData.append("server_files", file); + } } submitCreate.prop("disabled", true); diff --git a/cvat/apps/engine/migrations/0021_task_image_quality.py b/cvat/apps/engine/migrations/0021_task_image_quality.py new file mode 100644 index 000000000000..4b9aa51b0389 --- /dev/null +++ b/cvat/apps/engine/migrations/0021_task_image_quality.py @@ -0,0 +1,18 @@ +# Generated by Django 2.1.5 on 2019-01-31 16:21 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('engine', '0020_remove_task_path'), + ] + + operations = [ + migrations.AddField( + model_name='task', + name='image_quality', + field=models.PositiveSmallIntegerField(default=50), + ), + ] diff --git a/cvat/apps/engine/models.py b/cvat/apps/engine/models.py index 0ade948973b9..e5a38bdad175 100644 --- a/cvat/apps/engine/models.py +++ b/cvat/apps/engine/models.py @@ -46,9 +46,10 @@ class Task(models.Model): created_date = models.DateTimeField(auto_now_add=True) updated_date = models.DateTimeField(auto_now_add=True) overlap = models.PositiveIntegerField(default=0) - segment_size = models.PositiveIntegerField(default=sys.maxsize) + segment_size = models.PositiveIntegerField() z_order = models.BooleanField(default=False) flipped = models.BooleanField(default=False) + image_quality = models.PositiveSmallIntegerField(default=50) # FIXME: remote source field source = SafeCharField(max_length=256, default="unknown") status = models.CharField(max_length=32, choices=StatusChoice.choices(), diff --git a/cvat/apps/engine/serializers.py b/cvat/apps/engine/serializers.py index 35f4315e24e8..862c451614e0 100644 --- a/cvat/apps/engine/serializers.py +++ b/cvat/apps/engine/serializers.py @@ -71,22 +71,28 @@ class Meta: def to_internal_value(self, data): return { 'file' : data } +class RequestStatusSerializer(serializers.Serializer): + state = serializers.ChoiceField(choices=["Unknown", + "Queued", "Started", "Finished", "Failed"]) + message = serializers.CharField(allow_blank=True, default="") + class TaskSerializer(serializers.ModelSerializer): - labels = LabelSerializer(many=True, source='label_set') + labels = LabelSerializer(many=True, source='label_set', partial=True) segments = SegmentSerializer(many=True, source='segment_set', read_only=True) client_files = ClientFileSerializer(many=True, source='clientfile_set', - write_only=True) + write_only=True, partial=True) server_files = ServerFileSerializer(many=True, source='serverfile_set', - write_only=True) + write_only=True, partial=True) remote_files = RemoteFileSerializer(many=True, source='remotefile_set', - write_only=True) + write_only=True, partial=True) + image_quality = serializers.IntegerField(min_value=0, max_value=100) class Meta: model = Task fields = ('url', 'id', 'name', 'size', 'mode', 'owner', 'assignee', 'bug_tracker', 'created_date', 'updated_date', 'overlap', 'segment_size', 'z_order', 'flipped', 'status', 'labels', 'segments', - 'server_files', 'client_files', 'remote_files') + 'server_files', 'client_files', 'remote_files', 'image_quality') read_only_fields = ('size', 'mode', 'created_date', 'updated_date', 'overlap', 'status', 'segment_size') ordering = ['-id'] @@ -96,6 +102,8 @@ def create(self, validated_data): client_files = validated_data.pop('clientfile_set') server_files = validated_data.pop('serverfile_set') remote_files = validated_data.pop('remotefile_set') + if not validated_data.get('segment_size'): + validated_data['segment_size'] = 0 db_task = Task.objects.create(size=0, **validated_data) for label in labels: attributes = label.pop('attributespec_set') diff --git a/cvat/apps/engine/urls.py b/cvat/apps/engine/urls.py index b193de7e3f47..1c781f543b6f 100644 --- a/cvat/apps/engine/urls.py +++ b/cvat/apps/engine/urls.py @@ -40,6 +40,9 @@ path( # GET, DELETE, PATCH REST_API_PREFIX + 'tasks/', views.TaskDetail.as_view(), name='task-detail'), + path( # GET + REST_API_PREFIX + 'tasks//status', views.TaskStatus.as_view(), + name='task-status'), # GET meta information for all frames path(REST_API_PREFIX + 'tasks//frames/meta', views.get_image_meta_cache, name='image-meta-cache'), @@ -79,8 +82,6 @@ views.dummy_view, name='plugin-request-detail'), - path('create/task', views.create_task), #### - path('check/task/', views.check_task), #### path('delete/task/', views.delete_task), #### path('update/task/', views.update_task), #### path('dump/annotation/task/', views.dump_annotation), ### diff --git a/cvat/apps/engine/views.py b/cvat/apps/engine/views.py index f2704641ed35..aaac4d19d848 100644 --- a/cvat/apps/engine/views.py +++ b/cvat/apps/engine/views.py @@ -9,7 +9,7 @@ from ast import literal_eval from django.http import HttpResponse, HttpResponseBadRequest, JsonResponse -from django.shortcuts import redirect, render +from django.shortcuts import redirect, render, get_object_or_404 from django.conf import settings from rules.contrib.views import permission_required, objectgetter from django.views.decorators.gzip import gzip_page @@ -19,7 +19,8 @@ from rest_framework.response import Response from rest_framework.reverse import reverse from rest_framework.renderers import JSONRenderer - +from rest_framework import status +import django_rq from . import annotation, task, models @@ -30,7 +31,8 @@ from .log import slogger, clogger from cvat.apps.engine.models import StatusChoice, Task, Job from cvat.apps.engine.serializers import (TaskSerializer, UserSerializer, - ExceptionSerializer, AboutSerializer, JobSerializer, ImageMetaSerializer) + ExceptionSerializer, AboutSerializer, JobSerializer, ImageMetaSerializer, + RequestStatusSerializer) from django.contrib.auth.models import User # Server REST API @@ -61,6 +63,36 @@ class TaskDetail(generics.RetrieveUpdateDestroyAPIView): queryset = Task.objects.all() serializer_class = TaskSerializer +class TaskStatus(APIView): + serializer_class = RequestStatusSerializer + + def get(self, request, version, pk): + db_task = get_object_or_404(Task, pk=pk) + response = self._get_response(queue="default", + job_id="api/{}/tasks/{}".format(version, pk)) + serializer = TaskStatus.serializer_class(data=response) + + if serializer.is_valid(raise_exception=True): + return Response(serializer.data) + + def _get_response(self, queue, job_id): + queue = django_rq.get_queue(queue) + job = queue.fetch_job(job_id) + response = {} + if job is None or job.is_finished: + response = { "state": "Finished" } + elif job.is_queued: + response = { "state": "Queued" } + elif job.is_failed: + response = { "state": "Failed", "message": job.exc_info } + else: + response = { "state": "Started" } + if 'status' in job.meta: + response['message'] = job.meta['status'] + + return response + + class JobList(generics.ListAPIView): queryset = Job.objects.all() serializer_class = JobSerializer