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)