Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Job status was implemented #153

Merged
merged 4 commits into from
Oct 24, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions cvat/apps/engine/migrations/0012_auto_20181024_1817.py
Original file line number Diff line number Diff line change
@@ -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),
),
]
19 changes: 17 additions & 2 deletions cvat/apps/engine/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,26 @@

from django.contrib.auth.models import User

from io import StringIO
from enum import Enum

import shlex
import csv
from io import StringIO
import re
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):
def get_prep_value(self, value):
value = super().get_prep_value(value)
Expand All @@ -30,11 +44,11 @@ class Task(models.Model):
bug_tracker = models.CharField(max_length=2000, default="")
created_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)
z_order = models.BooleanField(default=False)
flipped = models.BooleanField(default=False)
source = SafeCharField(max_length=256, default="unknown")
status = models.CharField(max_length=32, default=StatusChoice.ANNOTATION)

# Extend default permission model
class Meta:
Expand Down Expand Up @@ -81,6 +95,7 @@ class Segment(models.Model):
class Job(models.Model):
segment = models.ForeignKey(Segment, on_delete=models.CASCADE)
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

class Label(models.Model):
Expand Down
16 changes: 14 additions & 2 deletions cvat/apps/engine/static/engine/js/annotationUI.js
Original file line number Diff line number Diff line change
Expand Up @@ -512,12 +512,24 @@ function setupMenu(job, shapeCollectionModel, annotationParser, aamModel, player
})();

$('#statTaskName').text(job.slug);
$('#statTaskStatus').text(job.status);
$('#statFrames').text(`[${job.start}-${job.stop}]`);
$('#statOverlap').text(job.overlap);
$('#statZOrder').text(job.z_order);
$('#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;
$('#helpButton').on('click', () => {
Expand Down
27 changes: 25 additions & 2 deletions cvat/apps/engine/task.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
_MEDIA_MIMETYPES_FILE = os.path.join(_SCRIPT_DIR, "media.mimetypes")
mimetypes.init(files=[_MEDIA_MIMETYPES_FILE])

from cvat.apps.engine.models import StatusChoice

import django_rq
from django.conf import settings
from django.db import transaction
Expand Down Expand Up @@ -164,7 +166,7 @@ def get(tid):
job_indexes = [segment.job_set.first().id for segment in db_segments]

response = {
"status": db_task.status.capitalize(),
"status": db_task.status,
"spec": {
"labels": { db_label.id:db_label.name for db_label in db_labels },
"attributes": attributes
Expand All @@ -185,6 +187,27 @@ def get(tid):

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):
"""Get the job as dictionary of attributes"""
db_job = models.Job.objects.select_related("segment__task").get(id=jid)
Expand All @@ -205,7 +228,7 @@ def get_job(jid):
attributes[db_label.id][db_attrspec.id] = db_attrspec.text

response = {
"status": db_task.status.capitalize(),
"status": db_job.status,
"labels": { db_label.id:db_label.name for db_label in db_labels },
"stop": db_segment.stop_frame,
"taskid": db_task.id,
Expand Down
12 changes: 10 additions & 2 deletions cvat/apps/engine/templates/engine/annotation.html
Original file line number Diff line number Diff line change
Expand Up @@ -306,7 +306,9 @@
</table>
</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>

Expand All @@ -325,7 +327,13 @@
<div style="float:left; width: 70%; height: 100%; text-align: left;" class="selectable">
<center>
<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>
<table style="width: 100%">
Expand Down
3 changes: 2 additions & 1 deletion cvat/apps/engine/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,6 @@
path('save/annotation/task/<int:tid>', views.save_annotation_for_task),
path('get/annotation/job/<int:jid>', views.get_annotation),
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),
]
21 changes: 20 additions & 1 deletion cvat/apps/engine/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
from requests.exceptions import RequestException
import logging
from .log import slogger, clogger
from cvat.apps.engine.models import StatusChoice

############################# High Level server API
@login_required
Expand All @@ -36,7 +37,8 @@ def dispatch_request(request):
"""An entry point to dispatch legacy requests"""
if request.method == 'GET' and 'id' in request.GET:
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:
return redirect('/dashboard/')
Expand Down Expand Up @@ -273,6 +275,23 @@ def save_annotation_for_task(request, tid):

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
def get_username(request):
response = {'username': request.user.username}
Expand Down