diff --git a/cvat/apps/dataset_manager/task.py b/cvat/apps/dataset_manager/task.py index 12f6f977431..6c5d2b0781c 100644 --- a/cvat/apps/dataset_manager/task.py +++ b/cvat/apps/dataset_manager/task.py @@ -477,11 +477,11 @@ def _delete(self, data=None): def delete(self, data=None): deleted_data = self._delete(data) - handle_annotations_change(self.db_job, deleted_data, "delete") - if not self._data_is_empty(deleted_data): self._set_updated_date() + handle_annotations_change(self.db_job, deleted_data, "delete") + @staticmethod def _extend_attributes(attributeval_set, default_attribute_values): shape_attribute_specs_set = set(attr.spec_id for attr in attributeval_set) diff --git a/cvat/apps/engine/backup.py b/cvat/apps/engine/backup.py index 3f345231e50..6733c035400 100644 --- a/cvat/apps/engine/backup.py +++ b/cvat/apps/engine/backup.py @@ -385,7 +385,7 @@ def serialize_task(): for field in ('url', 'owner', 'assignee'): task_serializer.fields.pop(field) - task_labels = LabelSerializer(self._db_task.get_labels(), many=True) + task_labels = LabelSerializer(self._db_task.get_labels(prefetch=True), many=True) task = self._prepare_task_meta(task_serializer.data) task['labels'] = [self._prepare_label_meta(l) for l in task_labels.data if not l['has_parent']] @@ -790,7 +790,7 @@ def serialize_project(): for field in ('assignee', 'owner', 'url'): project_serializer.fields.pop(field) - project_labels = LabelSerializer(self._db_project.get_labels(), many=True).data + project_labels = LabelSerializer(self._db_project.get_labels(prefetch=True), many=True).data project = self._prepare_project_meta(project_serializer.data) project['labels'] = [self._prepare_label_meta(l) for l in project_labels if not l['has_parent']] diff --git a/cvat/apps/engine/models.py b/cvat/apps/engine/models.py index aab67ac13af..9af447662d6 100644 --- a/cvat/apps/engine/models.py +++ b/cvat/apps/engine/models.py @@ -343,8 +343,11 @@ class Project(TimestampedModel): target_storage = models.ForeignKey('Storage', null=True, default=None, blank=True, on_delete=models.SET_NULL, related_name='+') - def get_labels(self): - return self.label_set.filter(parent__isnull=True) + def get_labels(self, prefetch=False): + queryset = self.label_set.filter(parent__isnull=True).select_related('skeleton') + return queryset.prefetch_related( + 'attributespec_set', 'sublabels__attributespec_set', + ) if prefetch else queryset def get_dirname(self): return os.path.join(settings.PROJECTS_ROOT, str(self.id)) @@ -426,11 +429,15 @@ class Task(TimestampedModel): class Meta: default_permissions = () - def get_labels(self): + def get_labels(self, prefetch=False): project = self.project if project: - return project.get_labels() - return self.label_set.filter(parent__isnull=True) + return project.get_labels(prefetch) + + queryset = self.label_set.filter(parent__isnull=True).select_related('skeleton') + return queryset.prefetch_related( + 'attributespec_set', 'sublabels__attributespec_set', + ) if prefetch else queryset def get_dirname(self): return os.path.join(settings.TASKS_ROOT, str(self.id)) @@ -725,10 +732,10 @@ def get_bug_tracker(self): project = task.project return task.bug_tracker or getattr(project, 'bug_tracker', None) - def get_labels(self): + def get_labels(self, prefetch=False): task = self.segment.task project = task.project - return project.get_labels() if project else task.get_labels() + return project.get_labels(prefetch) if project else task.get_labels(prefetch) class Meta: default_permissions = () diff --git a/cvat/apps/engine/serializers.py b/cvat/apps/engine/serializers.py index 086b0a8f270..766da57da47 100644 --- a/cvat/apps/engine/serializers.py +++ b/cvat/apps/engine/serializers.py @@ -1360,10 +1360,10 @@ class Meta: def to_representation(self, instance): response = super().to_representation(instance) - task_subsets = set(instance.tasks.values_list('subset', flat=True)) + task_subsets = {task.subset for task in instance.tasks.all()} task_subsets.discard('') response['task_subsets'] = list(task_subsets) - response['dimension'] = instance.tasks.first().dimension if instance.tasks.count() else None + response['dimension'] = getattr(instance.tasks.first(), 'dimension', None) return response class ProjectWriteSerializer(serializers.ModelSerializer): diff --git a/cvat/apps/engine/views.py b/cvat/apps/engine/views.py index 1fb34b0e669..e43930caaa8 100644 --- a/cvat/apps/engine/views.py +++ b/cvat/apps/engine/views.py @@ -265,12 +265,13 @@ class ProjectViewSet(viewsets.GenericViewSet, mixins.ListModelMixin, mixins.RetrieveModelMixin, mixins.CreateModelMixin, mixins.DestroyModelMixin, PartialUpdateModelMixin, UploadMixin, DatasetMixin, BackupMixin, CsrfWorkaroundMixin ): - queryset = models.Project.objects.select_related( - 'assignee', 'owner', 'target_storage', 'source_storage', 'annotation_guide', - ).prefetch_related('tasks').all() - # NOTE: The search_fields attribute should be a list of names of text # type fields on the model,such as CharField or TextField + queryset = models.Project.objects.select_related( + 'owner', 'assignee', 'organization', + 'annotation_guide', 'source_storage', 'target_storage', + ) + search_fields = ('name', 'owner', 'assignee', 'status') filter_fields = list(search_fields) + ['id', 'updated_date'] simple_filters = list(search_fields) @@ -290,10 +291,13 @@ def get_serializer_class(self): def get_queryset(self): queryset = super().get_queryset() + if self.action in ('list', 'retrieve', 'partial_update', 'update') : + queryset = queryset.prefetch_related('tasks') + + if self.action == 'list': + perm = ProjectPermission.create_scope_list(self.request) + return perm.filter(queryset) - if self.action == 'list': - perm = ProjectPermission.create_scope_list(self.request) - queryset = perm.filter(queryset) return queryset @transaction.atomic @@ -630,7 +634,7 @@ def append_backup_chunk(self, request, file_id): def preview(self, request, pk): self._object = self.get_object() # call check_object_permissions as well - first_task = self._object.tasks.order_by('-id').first() + first_task = self._object.tasks.select_related('data__video').order_by('-id').first() if not first_task: return HttpResponseNotFound('Project image preview not found') @@ -869,6 +873,8 @@ def get_queryset(self): if self.action == 'list': perm = TaskPermission.create_scope_list(self.request) queryset = perm.filter(queryset) + elif self.action == 'preview': + queryset = Task.objects.select_related('data') return queryset @@ -2362,41 +2368,42 @@ def get_queryset(self): code=status.HTTP_400_BAD_REQUEST ) - if job_id: + if job_id or task_id or project_id: + if job_id: + instance = Job.objects.select_related( + 'assignee', 'segment__task__organization', + 'segment__task__owner', 'segment__task__assignee', + 'segment__task__project__organization', + 'segment__task__project__owner', + 'segment__task__project__assignee', + ).get(id=job_id) + elif task_id: + instance = Task.objects.select_related( + 'owner', 'assignee', 'organization', + 'project__owner', 'project__assignee', 'project__organization', + ).get(id=task_id) + elif project_id: + instance = Project.objects.select_related( + 'owner', 'assignee', 'organization', + ).get(id=project_id) + # NOTE: This filter is too complex to be implemented by other means # It requires the following filter query: # ( # project__task__segment__job__id = job_id # OR # task__segment__job__id = job_id - # ) - job = Job.objects.get(id=job_id) - self.check_object_permissions(self.request, job) - queryset = job.get_labels() - elif task_id: - # NOTE: This filter is too complex to be implemented by other means - # It requires the following filter query: - # ( - # project__task__id = task_id # OR - # task_id = task_id + # project__task__id = task_id # ) - task = Task.objects.get(id=task_id) - self.check_object_permissions(self.request, task) - queryset = task.get_labels() - elif project_id: - # NOTE: this check is to make behavior consistent with other source filters - project = Project.objects.get(id=project_id) - self.check_object_permissions(self.request, project) - queryset = project.get_labels() + self.check_object_permissions(self.request, instance) + queryset = instance.get_labels(prefetch=True) else: # In other cases permissions are checked already queryset = super().get_queryset() perm = LabelPermission.create_scope_list(self.request) - queryset = perm.filter(queryset) - - # Include only 1st level labels in list responses - queryset = queryset.filter(parent__isnull=True) + # Include only 1st level labels in list responses + queryset = perm.filter(queryset).filter(parent__isnull=True) else: queryset = super().get_queryset() diff --git a/cvat/apps/events/handlers.py b/cvat/apps/events/handlers.py index 198bcbb45cf..4e92013d5f6 100644 --- a/cvat/apps/events/handlers.py +++ b/cvat/apps/events/handlers.py @@ -4,7 +4,6 @@ import datetime import traceback -from copy import deepcopy from typing import Optional, Union import rq @@ -374,19 +373,21 @@ def handle_delete(scope, instance, store_in_deletion_cache=False, **kwargs): ) def handle_annotations_change(instance, annotations, action, **kwargs): - _annotations = deepcopy(annotations) - def filter_shape_data(shape): - data = { - "id": shape["id"], - "frame": shape["frame"], - "attributes": shape["attributes"], + def filter_data(data): + filtered_data = { + "id": data["id"], + "frame": data["frame"], + "attributes": data["attributes"], } + if label_id := data.get("label_id"): + filtered_data["label_id"] = label_id - label_id = shape.get("label_id", None) - if label_id: - data["label_id"] = label_id + return filtered_data - return data + def filter_track(track): + filtered_data = filter_data(track) + filtered_data["shapes"] = [filter_data(s) for s in track["shapes"]] + return filtered_data oid = organization_id(instance) oslug = organization_slug(instance) @@ -397,7 +398,7 @@ def filter_shape_data(shape): uname = user_name(instance) uemail = user_email(instance) - tags = [filter_shape_data(tag) for tag in _annotations.get("tags", [])] + tags = [filter_data(tag) for tag in annotations.get("tags", [])] if tags: record_server_event( scope=event_scope(action, "tags"), @@ -416,8 +417,8 @@ def filter_shape_data(shape): ) shapes_by_type = {shape_type[0]: [] for shape_type in ShapeType.choices()} - for shape in _annotations.get("shapes", []): - shapes_by_type[shape["type"]].append(filter_shape_data(shape)) + for shape in annotations.get("shapes", []): + shapes_by_type[shape["type"]].append(filter_data(shape)) scope = event_scope(action, "shapes") for shape_type, shapes in shapes_by_type.items(): @@ -440,13 +441,9 @@ def filter_shape_data(shape): ) tracks_by_type = {shape_type[0]: [] for shape_type in ShapeType.choices()} - for track in _annotations.get("tracks", []): - track_shapes = track.pop("shapes") - track = filter_shape_data(track) - track["shapes"] = [] - for track_shape in track_shapes: - track["shapes"].append(filter_shape_data(track_shape)) - tracks_by_type[track_shapes[0]["type"]].append(track) + for track in annotations.get("tracks", []): + filtered_track = filter_track(track) + tracks_by_type[track["shapes"][0]["type"]].append(filtered_track) scope = event_scope(action, "tracks") for track_type, tracks in tracks_by_type.items(): diff --git a/cvat/apps/iam/middleware.py b/cvat/apps/iam/middleware.py index 0e389fae308..2139e639cc6 100644 --- a/cvat/apps/iam/middleware.py +++ b/cvat/apps/iam/middleware.py @@ -33,9 +33,9 @@ def get_organization(request): org_slug = org_slug if org_slug is not None else org_header if org_slug: - organization = Organization.objects.get(slug=org_slug) + organization = Organization.objects.select_related('owner').get(slug=org_slug) elif org_id: - organization = Organization.objects.get(id=int(org_id)) + organization = Organization.objects.select_related('owner').get(id=int(org_id)) except Organization.DoesNotExist: raise NotFound(f'{org_slug or org_id} organization does not exist.') diff --git a/cvat/apps/iam/permissions.py b/cvat/apps/iam/permissions.py index b4e802378f9..cc062516aa0 100644 --- a/cvat/apps/iam/permissions.py +++ b/cvat/apps/iam/permissions.py @@ -49,7 +49,7 @@ def get_organization(request, obj): raise exc try: - return Organization.objects.get(id=organization_id) + return Organization.objects.select_related('owner').get(id=organization_id) except Organization.DoesNotExist: return None diff --git a/cvat/apps/lambda_manager/views.py b/cvat/apps/lambda_manager/views.py index a6e37933f32..3c417f5dc12 100644 --- a/cvat/apps/lambda_manager/views.py +++ b/cvat/apps/lambda_manager/views.py @@ -253,7 +253,7 @@ def mandatory_arg(name: str) -> Any: mapping = data.get("mapping", {}) model_labels = self.labels - task_labels = db_task.get_labels().prefetch_related('attributespec_set') + task_labels = db_task.get_labels(prefetch=True) def labels_compatible(model_label: Dict, task_label: Label) -> bool: model_type = model_label['type'] @@ -948,7 +948,7 @@ def convert_labels(db_labels): labels[label.name]['attributes'][attr['name']] = attr['id'] return labels - labels = convert_labels(db_task.get_labels().prefetch_related('attributespec_set')) + labels = convert_labels(db_task.get_labels(prefetch=True)) if function.kind == LambdaType.DETECTOR: cls._call_detector(function, db_task, labels, quality,