Skip to content
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
48 changes: 26 additions & 22 deletions cvat-ui/src/components/task-page/job-list.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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 => (
<div>
<Button
type='link'
onClick={(e: React.MouseEvent): void => {
e.preventDefault();
push(`/tasks/${taskId}/jobs/${id}`);
}}
href={`/tasks/${taskId}/jobs/${id}`}
<CVATTooltip
trigger='hover'
title={jobInstance.assignee ? '' : 'You need to assign yourself to the job before open it.'}
>
{`Job #${id}`}
</Button>
<Button
type='link'
disabled={!jobInstance.assignee}
onClick={(e: React.MouseEvent): void => {
e.preventDefault();
push(`/tasks/${taskId}/jobs/${jobInstance.id}`);
}}
href={`/tasks/${taskId}/jobs/${jobInstance.id}`}
>
{`Job #${jobInstance.id}`}
</Button>
</CVATTooltip>
</div>
),
},
Expand All @@ -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';
Expand All @@ -122,12 +127,12 @@ function JobListComponent(props: Props & RouteComponentProps): JSX.Element {
</Text>
);
},
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',
Expand All @@ -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 => (
<UserSelector
Expand All @@ -157,10 +162,10 @@ function JobListComponent(props: Props & RouteComponentProps): 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,
},
];

Expand All @@ -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;
Expand Down
39 changes: 39 additions & 0 deletions cvat/apps/engine/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down