diff --git a/cvat-ui/src/components/task-page/job-list.tsx b/cvat-ui/src/components/task-page/job-list.tsx
index 9418418ed771..8c8d0ecb76fc 100644
--- a/cvat-ui/src/components/task-page/job-list.tsx
+++ b/cvat-ui/src/components/task-page/job-list.tsx
@@ -6,7 +6,7 @@ import React from 'react';
import { RouteComponentProps } from 'react-router';
import { withRouter } from 'react-router-dom';
import { Row, Col } from 'antd/lib/grid';
-import { QuestionCircleOutlined, CopyOutlined } from '@ant-design/icons';
+import { CopyOutlined } from '@ant-design/icons';
import { ColumnFilterItem } from 'antd/lib/table/interface';
import Table from 'antd/lib/table';
import Button from 'antd/lib/button';
@@ -81,18 +81,24 @@ function JobListComponent(props: Props & RouteComponentProps): JSX.Element {
title: 'Job',
dataIndex: 'job',
key: 'job',
- render: (id: number): JSX.Element => (
+ render: (jobInstance: any): JSX.Element => (
-
+
+
),
},
@@ -107,8 +113,7 @@ function JobListComponent(props: Props & RouteComponentProps): JSX.Element {
dataIndex: 'status',
key: 'status',
className: 'cvat-job-item-status',
- render: (jobInstance: any): JSX.Element => {
- const { status } = jobInstance;
+ render: (status: string): JSX.Element => {
let progressColor = null;
if (status === 'completed') {
progressColor = 'cvat-job-completed-color';
@@ -122,12 +127,12 @@ function JobListComponent(props: Props & RouteComponentProps): JSX.Element {
);
},
- sorter: sorter('status.status'),
+ sorter: sorter('status'),
filters: [
{ text: 'annotation', value: 'annotation' },
{ text: 'completed', value: 'completed' },
],
- onFilter: (value: string | number | boolean, record: any) => record.status.status === value,
+ onFilter: (value: string | number | boolean, record: any) => record.status === value,
},
{
title: 'Started on',
@@ -143,8 +148,8 @@ function JobListComponent(props: Props & RouteComponentProps): JSX.Element {
},
{
title: 'Assignee',
- dataIndex: 'assignee',
- key: 'assignee',
+ dataIndex: 'job',
+ key: 'job',
className: 'cvat-job-item-assignee',
render: (jobInstance: any): JSX.Element => (
),
- sorter: sorter('assignee.assignee.username'),
+ sorter: sorter('job.assignee.username'),
filters: collectUsers('assignee'),
onFilter: (value: string | number | boolean, record: any) =>
- (record.assignee.assignee?.username || false) === value,
+ (record.job.assignee?.username || false) === value,
},
];
@@ -175,12 +180,11 @@ function JobListComponent(props: Props & RouteComponentProps): JSX.Element {
const now = moment(moment.now());
acc.push({
key: job.id,
- job: job.id,
+ job,
frames: `${job.startFrame}-${job.stopFrame}`,
- status: job,
+ status: job.status,
started: `${created.format('MMMM Do YYYY HH:MM')}`,
duration: `${moment.duration(now.diff(created)).humanize()}`,
- assignee: job,
});
return acc;
diff --git a/cvat/apps/engine/serializers.py b/cvat/apps/engine/serializers.py
index 317caba4f01c..e74f45e1b188 100644
--- a/cvat/apps/engine/serializers.py
+++ b/cvat/apps/engine/serializers.py
@@ -151,6 +151,45 @@ class Meta:
'status', 'start_frame', 'stop_frame', 'task_id')
read_only_fields = ('assignee', )
+ def update(self, instance, validated_data):
+ from rest_framework.utils import model_meta
+
+ serializers.raise_errors_on_nested_writes('update', self, validated_data)
+ info = model_meta.get_field_info(instance)
+
+ validated_assignee_id = validated_data.get('assignee_id', None)
+ validated_job_status = validated_data.get('status', None)
+
+ if validated_job_status is not None:
+ if validated_job_status == 'completed' and instance.assignee_id is None:
+ raise serializers.ValidationError('Job must have an assignee to be finished.')
+ elif validated_assignee_id != instance.assignee_id:
+ assignee = models.User.objects.get(id=validated_data.get('assignee_id', None))
+ if assignee.is_staff or assignee.is_superuser:
+ raise serializers.ValidationError('Admins could not be assigned to the job.')
+
+ # Simply set each attribute on the instance, and then save it.
+ # Note that unlike `.create()` we don't need to treat many-to-many
+ # relationships as being a special case. During updates we already
+ # have an instance pk for the relationships to be associated with.
+ m2m_fields = []
+ for attr, value in validated_data.items():
+ if attr in info.relations and info.relations[attr].to_many:
+ m2m_fields.append((attr, value))
+ else:
+ setattr(instance, attr, value)
+
+ instance.save()
+
+ # Note that many-to-many fields are set after updating instance.
+ # Setting m2m fields triggers signals which could potentially change
+ # updated instance and we do not want it to collide with .update()
+ for attr, value in m2m_fields:
+ field = getattr(instance, attr)
+ field.set(value)
+
+ return instance
+
class SimpleJobSerializer(serializers.ModelSerializer):
assignee = BasicUserSerializer(allow_null=True)
assignee_id = serializers.IntegerField(write_only=True, allow_null=True)