Job status was implemented (#153)

main
Boris Sekachev 7 years ago committed by Nikita Manovich
parent 89234496ce
commit 920247dc31

@ -0,0 +1,23 @@
# Generated by Django 2.0.9 on 2018-10-24 15:17
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('engine', '0011_add_task_source_and_safecharfield'),
]
operations = [
migrations.AddField(
model_name='job',
name='status',
field=models.CharField(default='annotation', max_length=32),
),
migrations.AlterField(
model_name='task',
name='status',
field=models.CharField(default='annotation', max_length=32),
),
]

@ -8,12 +8,26 @@ from django.conf import settings
from django.contrib.auth.models import User from django.contrib.auth.models import User
from io import StringIO
from enum import Enum
import shlex import shlex
import csv import csv
from io import StringIO
import re import re
import os import os
class StatusChoice(Enum):
ANNOTATION = 'annotation'
VALIDATION = 'validation'
COMPLETED = 'completed'
@classmethod
def choices(self):
return tuple((x.name, x.value) for x in self)
def __str__(self):
return self.value
class SafeCharField(models.CharField): class SafeCharField(models.CharField):
def get_prep_value(self, value): def get_prep_value(self, value):
value = super().get_prep_value(value) value = super().get_prep_value(value)
@ -30,11 +44,11 @@ class Task(models.Model):
bug_tracker = models.CharField(max_length=2000, default="") bug_tracker = models.CharField(max_length=2000, default="")
created_date = models.DateTimeField(auto_now_add=True) created_date = models.DateTimeField(auto_now_add=True)
updated_date = models.DateTimeField(auto_now_add=True) updated_date = models.DateTimeField(auto_now_add=True)
status = models.CharField(max_length=32, default="annotate")
overlap = models.PositiveIntegerField(default=0) overlap = models.PositiveIntegerField(default=0)
z_order = models.BooleanField(default=False) z_order = models.BooleanField(default=False)
flipped = models.BooleanField(default=False) flipped = models.BooleanField(default=False)
source = SafeCharField(max_length=256, default="unknown") source = SafeCharField(max_length=256, default="unknown")
status = models.CharField(max_length=32, default=StatusChoice.ANNOTATION)
# Extend default permission model # Extend default permission model
class Meta: class Meta:
@ -81,6 +95,7 @@ class Segment(models.Model):
class Job(models.Model): class Job(models.Model):
segment = models.ForeignKey(Segment, on_delete=models.CASCADE) segment = models.ForeignKey(Segment, on_delete=models.CASCADE)
annotator = models.ForeignKey(User, null=True, on_delete=models.SET_NULL) annotator = models.ForeignKey(User, null=True, on_delete=models.SET_NULL)
status = models.CharField(max_length=32, default=StatusChoice.ANNOTATION)
# TODO: add sub-issue number for the task # TODO: add sub-issue number for the task
class Label(models.Model): class Label(models.Model):

@ -512,12 +512,24 @@ function setupMenu(job, shapeCollectionModel, annotationParser, aamModel, player
})(); })();
$('#statTaskName').text(job.slug); $('#statTaskName').text(job.slug);
$('#statTaskStatus').text(job.status);
$('#statFrames').text(`[${job.start}-${job.stop}]`); $('#statFrames').text(`[${job.start}-${job.stop}]`);
$('#statOverlap').text(job.overlap); $('#statOverlap').text(job.overlap);
$('#statZOrder').text(job.z_order); $('#statZOrder').text(job.z_order);
$('#statFlipped').text(job.flipped); $('#statFlipped').text(job.flipped);
$('#statTaskStatus').prop("value", job.status).on('change', (e) => {
$.ajax({
type: 'POST',
url: 'save/job/status',
data: JSON.stringify({
jid: window.cvat.job.id,
status: e.target.value
}),
contentType: "application/json; charset=utf-8",
error: (data) => {
showMessage(`Can not change job status. Code: ${data.status}. Message: ${data.responeText || data.statusText}`);
}
});
});
let shortkeys = window.cvat.config.shortkeys; let shortkeys = window.cvat.config.shortkeys;
$('#helpButton').on('click', () => { $('#helpButton').on('click', () => {

@ -18,6 +18,8 @@ _SCRIPT_DIR = os.path.realpath(os.path.dirname(__file__))
_MEDIA_MIMETYPES_FILE = os.path.join(_SCRIPT_DIR, "media.mimetypes") _MEDIA_MIMETYPES_FILE = os.path.join(_SCRIPT_DIR, "media.mimetypes")
mimetypes.init(files=[_MEDIA_MIMETYPES_FILE]) mimetypes.init(files=[_MEDIA_MIMETYPES_FILE])
from cvat.apps.engine.models import StatusChoice
import django_rq import django_rq
from django.conf import settings from django.conf import settings
from django.db import transaction from django.db import transaction
@ -164,7 +166,7 @@ def get(tid):
job_indexes = [segment.job_set.first().id for segment in db_segments] job_indexes = [segment.job_set.first().id for segment in db_segments]
response = { response = {
"status": db_task.status.capitalize(), "status": db_task.status,
"spec": { "spec": {
"labels": { db_label.id:db_label.name for db_label in db_labels }, "labels": { db_label.id:db_label.name for db_label in db_labels },
"attributes": attributes "attributes": attributes
@ -185,6 +187,27 @@ def get(tid):
return response return response
def save_job_status(jid, status, user):
db_job = models.Job.objects.select_related("segment__task").select_for_update().get(pk = jid)
db_task = db_job.segment.task
status = StatusChoice(status)
slogger.job[jid].info('changing job status from {} to {} by an user {}'.format(db_job.status, status, user))
db_job.status = status.value
db_job.save()
db_segments = list(db_task.segment_set.prefetch_related('job_set').select_for_update().all())
db_jobs = [db_segment.job_set.first() for db_segment in db_segments]
if len(list(filter(lambda x: StatusChoice(x.status) == StatusChoice.ANNOTATION, db_jobs))) > 0:
db_task.status = StatusChoice.ANNOTATION
elif len(list(filter(lambda x: StatusChoice(x.status) == StatusChoice.VALIDATION, db_jobs))) > 0:
db_task.status = StatusChoice.VALIDATION
else:
db_task.status = StatusChoice.COMPLETED
db_task.save()
def get_job(jid): def get_job(jid):
"""Get the job as dictionary of attributes""" """Get the job as dictionary of attributes"""
db_job = models.Job.objects.select_related("segment__task").get(id=jid) db_job = models.Job.objects.select_related("segment__task").get(id=jid)
@ -205,7 +228,7 @@ def get_job(jid):
attributes[db_label.id][db_attrspec.id] = db_attrspec.text attributes[db_label.id][db_attrspec.id] = db_attrspec.text
response = { response = {
"status": db_task.status.capitalize(), "status": db_job.status,
"labels": { db_label.id:db_label.name for db_label in db_labels }, "labels": { db_label.id:db_label.name for db_label in db_labels },
"stop": db_segment.stop_frame, "stop": db_segment.stop_frame,
"taskid": db_task.id, "taskid": db_task.id,

@ -306,7 +306,9 @@
</table> </table>
</div> </div>
<center> <button id="closeSettignsButton" class="regular h1" style="margin-top: 15px;"> Close </button> </center> <center>
<button id="closeSettignsButton" class="regular h1" style="margin-top: 15px;"> Close </button>
</center>
</div> </div>
</div> </div>
@ -325,7 +327,13 @@
<div style="float:left; width: 70%; height: 100%; text-align: left;" class="selectable"> <div style="float:left; width: 70%; height: 100%; text-align: left;" class="selectable">
<center> <center>
<label id="statTaskName" class="semiBold h2"> </label> <br> <label id="statTaskName" class="semiBold h2"> </label> <br>
<label id="statTaskStatus" class="regular h2"> </label> <center>
<select id="statTaskStatus" class="regular h2" style="outline: none; border-radius: 10px; background:#B0C4DE; border: 1px solid black;">
{% for status in status_list %}
<option value="{{status}}"> {{status}} </option>
{% endfor %}
</select>
</center>
</center> </center>
<center> <center>
<table style="width: 100%"> <table style="width: 100%">

@ -22,5 +22,6 @@ urlpatterns = [
path('save/annotation/task/<int:tid>', views.save_annotation_for_task), path('save/annotation/task/<int:tid>', views.save_annotation_for_task),
path('get/annotation/job/<int:jid>', views.get_annotation), path('get/annotation/job/<int:jid>', views.get_annotation),
path('get/username', views.get_username), path('get/username', views.get_username),
path('save/exception/<int:jid>', views.catch_client_exception) path('save/exception/<int:jid>', views.catch_client_exception),
path('save/job/status', views.save_job_status),
] ]

@ -20,6 +20,7 @@ from cvat.apps.authentication.decorators import login_required
from requests.exceptions import RequestException from requests.exceptions import RequestException
import logging import logging
from .log import slogger, clogger from .log import slogger, clogger
from cvat.apps.engine.models import StatusChoice
############################# High Level server API ############################# High Level server API
@login_required @login_required
@ -36,7 +37,8 @@ def dispatch_request(request):
"""An entry point to dispatch legacy requests""" """An entry point to dispatch legacy requests"""
if request.method == 'GET' and 'id' in request.GET: if request.method == 'GET' and 'id' in request.GET:
return render(request, 'engine/annotation.html', { return render(request, 'engine/annotation.html', {
'js_3rdparty': JS_3RDPARTY.get('engine', []) 'js_3rdparty': JS_3RDPARTY.get('engine', []),
'status_list': [str(i) for i in StatusChoice]
}) })
else: else:
return redirect('/dashboard/') return redirect('/dashboard/')
@ -273,6 +275,23 @@ def save_annotation_for_task(request, tid):
return HttpResponse() return HttpResponse()
@login_required
@permission_required(perm=['engine.view_task', 'engine.change_task'], raise_exception=True)
def save_job_status(request):
try:
data = json.loads(request.body.decode('utf-8'))
jid = data['jid']
status = data['status']
slogger.job[jid].info("changing job status request")
task.save_job_status(jid, status, request.user.username)
except Exception as e:
if jid:
slogger.job[jid].error("cannot change status", exc_info=True)
else:
slogger.glob.error("cannot change status", exc_info=True)
return HttpResponseBadRequest(str(e))
return HttpResponse()
@login_required @login_required
def get_username(request): def get_username(request):
response = {'username': request.user.username} response = {'username': request.user.username}

Loading…
Cancel
Save