change how context validation happens on instances

This commit is contained in:
Jakub Trllo 2024-09-10 18:29:48 +02:00
parent 7653e098ce
commit 66353ec8c4
9 changed files with 213 additions and 122 deletions

View file

@ -6,7 +6,8 @@ import traceback
import collections import collections
import inspect import inspect
from contextlib import contextmanager from contextlib import contextmanager
from typing import Optional import typing
from typing import Optional, Iterable, Dict
import pyblish.logic import pyblish.logic
import pyblish.api import pyblish.api
@ -31,13 +32,15 @@ from .exceptions import (
HostMissRequiredMethod, HostMissRequiredMethod,
) )
from .changes import TrackChangesItem from .changes import TrackChangesItem
from .structures import PublishAttributes, ConvertorItem from .structures import PublishAttributes, ConvertorItem, InstanceContextInfo
from .creator_plugins import ( from .creator_plugins import (
Creator, Creator,
AutoCreator, AutoCreator,
discover_creator_plugins, discover_creator_plugins,
discover_convertor_plugins, discover_convertor_plugins,
) )
if typing.TYPE_CHECKING:
from .structures import CreatedInstance
# Import of functions and classes that were moved to different file # Import of functions and classes that were moved to different file
# TODO Should be removed in future release - Added 24/08/28, 0.4.3-dev.1 # TODO Should be removed in future release - Added 24/08/28, 0.4.3-dev.1
@ -183,6 +186,10 @@ class CreateContext:
# Shared data across creators during collection phase # Shared data across creators during collection phase
self._collection_shared_data = None self._collection_shared_data = None
# Context validation cache
self._folder_id_by_folder_path = {}
self._task_names_by_folder_path = {}
self.thumbnail_paths_by_instance_id = {} self.thumbnail_paths_by_instance_id = {}
# Trigger reset if was enabled # Trigger reset if was enabled
@ -202,17 +209,19 @@ class CreateContext:
"""Access to global publish attributes.""" """Access to global publish attributes."""
return self._publish_attributes return self._publish_attributes
def get_instance_by_id(self, instance_id): def get_instance_by_id(
self, instance_id: str
) -> Optional["CreatedInstance"]:
"""Receive instance by id. """Receive instance by id.
Args: Args:
instance_id (str): Instance id. instance_id (str): Instance id.
Returns: Returns:
Union[CreatedInstance, None]: Instance or None if instance with Optional[CreatedInstance]: Instance or None if instance with
given id is not available. given id is not available.
"""
"""
return self._instances_by_id.get(instance_id) return self._instances_by_id.get(instance_id)
def get_sorted_creators(self, identifiers=None): def get_sorted_creators(self, identifiers=None):
@ -224,8 +233,8 @@ class CreateContext:
Returns: Returns:
List[BaseCreator]: Sorted creator plugins by 'order' value. List[BaseCreator]: Sorted creator plugins by 'order' value.
"""
"""
if identifiers is not None: if identifiers is not None:
identifiers = set(identifiers) identifiers = set(identifiers)
creators = [ creators = [
@ -491,6 +500,8 @@ class CreateContext:
# Give ability to store shared data for collection phase # Give ability to store shared data for collection phase
self._collection_shared_data = {} self._collection_shared_data = {}
self._folder_id_by_folder_path = {}
self._task_names_by_folder_path = {}
def reset_finalization(self): def reset_finalization(self):
"""Cleanup of attributes after reset.""" """Cleanup of attributes after reset."""
@ -715,7 +726,7 @@ class CreateContext:
self._original_context_data, self.context_data_to_store() self._original_context_data, self.context_data_to_store()
) )
def creator_adds_instance(self, instance): def creator_adds_instance(self, instance: "CreatedInstance"):
"""Creator adds new instance to context. """Creator adds new instance to context.
Instances should be added only from creators. Instances should be added only from creators.
@ -942,7 +953,7 @@ class CreateContext:
def _remove_instance(self, instance): def _remove_instance(self, instance):
self._instances_by_id.pop(instance.id, None) self._instances_by_id.pop(instance.id, None)
def creator_removed_instance(self, instance): def creator_removed_instance(self, instance: "CreatedInstance"):
"""When creator removes instance context should be acknowledged. """When creator removes instance context should be acknowledged.
If creator removes instance conext should know about it to avoid If creator removes instance conext should know about it to avoid
@ -990,7 +1001,7 @@ class CreateContext:
[], [],
self._bulk_instances_to_process self._bulk_instances_to_process
) )
self.validate_instances_context(instances_to_validate) self.get_instances_context_info(instances_to_validate)
def reset_instances(self): def reset_instances(self):
"""Reload instances""" """Reload instances"""
@ -1079,26 +1090,70 @@ class CreateContext:
if failed_info: if failed_info:
raise CreatorsCreateFailed(failed_info) raise CreatorsCreateFailed(failed_info)
def validate_instances_context(self, instances=None): def get_instances_context_info(
"""Validate 'folder' and 'task' instance context.""" self, instances: Optional[Iterable["CreatedInstance"]] = None
) -> Dict[str, InstanceContextInfo]:
"""Validate 'folder' and 'task' instance context.
Args:
instances (Optional[Iterable[CreatedInstance]]): Instances to
validate. If not provided all instances are validated.
Returns:
Dict[str, InstanceContextInfo]: Validation results by instance id.
"""
# Use all instances from context if 'instances' are not passed # Use all instances from context if 'instances' are not passed
if instances is None: if instances is None:
instances = tuple(self._instances_by_id.values()) instances = self._instances_by_id.values()
instances = tuple(instances)
info_by_instance_id = {
instance.id: InstanceContextInfo(
instance.get("folderPath"),
instance.get("task"),
False,
False,
)
for instance in instances
}
# Skip if instances are empty # Skip if instances are empty
if not instances: if not info_by_instance_id:
return return info_by_instance_id
project_name = self.project_name project_name = self.project_name
task_names_by_folder_path = {} to_validate = []
task_names_by_folder_path = collections.defaultdict(set)
for instance in instances: for instance in instances:
folder_path = instance.get("folderPath") context_info = info_by_instance_id[instance.id]
task_name = instance.get("task") if instance.has_promised_context:
if folder_path: context_info.folder_is_valid = True
task_names_by_folder_path[folder_path] = set() context_info.task_is_valid = True
if task_name: continue
task_names_by_folder_path[folder_path].add(task_name) # TODO allow context promise
folder_path = context_info.folder_path
if not folder_path:
continue
if folder_path in self._folder_id_by_folder_path:
folder_id = self._folder_id_by_folder_path[folder_path]
if folder_id is None:
continue
context_info.folder_is_valid = True
task_name = context_info.task_name
if task_name is not None:
tasks_cache = self._task_names_by_folder_path.get(folder_path)
if tasks_cache is not None:
context_info.task_is_valid = task_name in tasks_cache
continue
to_validate.append(instance)
task_names_by_folder_path[folder_path].add(task_name)
if not to_validate:
return info_by_instance_id
# Backwards compatibility for cases where folder name is set instead # Backwards compatibility for cases where folder name is set instead
# of folder path # of folder path
@ -1120,7 +1175,9 @@ class CreateContext:
fields={"id", "path"} fields={"id", "path"}
): ):
folder_id = folder_entity["id"] folder_id = folder_entity["id"]
folder_paths_by_id[folder_id] = folder_entity["path"] folder_path = folder_entity["path"]
folder_paths_by_id[folder_id] = folder_path
self._folder_id_by_folder_path[folder_path] = folder_id
folder_entities_by_name = collections.defaultdict(list) folder_entities_by_name = collections.defaultdict(list)
if folder_names: if folder_names:
@ -1131,8 +1188,10 @@ class CreateContext:
): ):
folder_id = folder_entity["id"] folder_id = folder_entity["id"]
folder_name = folder_entity["name"] folder_name = folder_entity["name"]
folder_paths_by_id[folder_id] = folder_entity["path"] folder_path = folder_entity["path"]
folder_paths_by_id[folder_id] = folder_path
folder_entities_by_name[folder_name].append(folder_entity) folder_entities_by_name[folder_name].append(folder_entity)
self._folder_id_by_folder_path[folder_path] = folder_id
tasks_entities = ayon_api.get_tasks( tasks_entities = ayon_api.get_tasks(
project_name, project_name,
@ -1145,12 +1204,11 @@ class CreateContext:
folder_id = task_entity["folderId"] folder_id = task_entity["folderId"]
folder_path = folder_paths_by_id[folder_id] folder_path = folder_paths_by_id[folder_id]
task_names_by_folder_path[folder_path].add(task_entity["name"]) task_names_by_folder_path[folder_path].add(task_entity["name"])
self._task_names_by_folder_path.update(task_names_by_folder_path)
for instance in instances: for instance in to_validate:
if not instance.has_valid_folder or not instance.has_valid_task:
continue
folder_path = instance["folderPath"] folder_path = instance["folderPath"]
task_name = instance.get("task")
if folder_path and "/" not in folder_path: if folder_path and "/" not in folder_path:
folder_entities = folder_entities_by_name.get(folder_path) folder_entities = folder_entities_by_name.get(folder_path)
if len(folder_entities) == 1: if len(folder_entities) == 1:
@ -1158,15 +1216,16 @@ class CreateContext:
instance["folderPath"] = folder_path instance["folderPath"] = folder_path
if folder_path not in task_names_by_folder_path: if folder_path not in task_names_by_folder_path:
instance.set_folder_invalid(True)
continue continue
context_info = info_by_instance_id[instance.id]
context_info.folder_is_valid = True
task_name = instance["task"] if (
if not task_name: not task_name
continue or task_name in task_names_by_folder_path[folder_path]
):
if task_name not in task_names_by_folder_path[folder_path]: context_info.task_is_valid = True
instance.set_task_invalid(True) return info_by_instance_id
def save_changes(self): def save_changes(self):
"""Save changes. Update all changed values.""" """Save changes. Update all changed values."""

View file

@ -1,6 +1,7 @@
import copy import copy
import collections import collections
from uuid import uuid4 from uuid import uuid4
from typing import Optional
from ayon_core.lib.attribute_definitions import ( from ayon_core.lib.attribute_definitions import (
UnknownDef, UnknownDef,
@ -396,6 +397,24 @@ class PublishAttributes:
) )
class InstanceContextInfo:
def __init__(
self,
folder_path: Optional[str],
task_name: Optional[str],
folder_is_valid: bool,
task_is_valid: bool,
):
self.folder_path: Optional[str] = folder_path
self.task_name: Optional[str] = task_name
self.folder_is_valid: bool = folder_is_valid
self.task_is_valid: bool = task_is_valid
@property
def is_valid(self) -> bool:
return self.folder_is_valid and self.task_is_valid
class CreatedInstance: class CreatedInstance:
"""Instance entity with data that will be stored to workfile. """Instance entity with data that will be stored to workfile.
@ -528,9 +547,6 @@ class CreatedInstance:
if not self._data.get("instance_id"): if not self._data.get("instance_id"):
self._data["instance_id"] = str(uuid4()) self._data["instance_id"] = str(uuid4())
self._folder_is_valid = self.has_set_folder
self._task_is_valid = self.has_set_task
def __str__(self): def __str__(self):
return ( return (
"<CreatedInstance {product[name]}" "<CreatedInstance {product[name]}"
@ -699,6 +715,17 @@ class CreatedInstance:
def publish_attributes(self): def publish_attributes(self):
return self._data["publish_attributes"] return self._data["publish_attributes"]
@property
def has_promised_context(self) -> bool:
"""Get context data that are promised to be set by creator.
Returns:
bool: Has context that won't bo validated. Artist can't change
value when set to True.
"""
return self._data.get("has_promised_context", False)
def data_to_store(self): def data_to_store(self):
"""Collect data that contain json parsable types. """Collect data that contain json parsable types.
@ -826,46 +853,3 @@ class CreatedInstance:
obj.publish_attributes.deserialize_attributes(publish_attributes) obj.publish_attributes.deserialize_attributes(publish_attributes)
return obj return obj
# Context validation related methods/properties
@property
def has_set_folder(self):
"""Folder path is set in data."""
return "folderPath" in self._data
@property
def has_set_task(self):
"""Task name is set in data."""
return "task" in self._data
@property
def has_valid_context(self):
"""Context data are valid for publishing."""
return self.has_valid_folder and self.has_valid_task
@property
def has_valid_folder(self):
"""Folder set in context exists in project."""
if not self.has_set_folder:
return False
return self._folder_is_valid
@property
def has_valid_task(self):
"""Task set in context exists in project."""
if not self.has_set_task:
return False
return self._task_is_valid
def set_folder_invalid(self, invalid):
# TODO replace with `set_folder_path`
self._folder_is_valid = not invalid
def set_task_invalid(self, invalid):
# TODO replace with `set_task_name`
self._task_is_valid = not invalid

View file

@ -322,6 +322,12 @@ class AbstractPublisherFrontend(AbstractPublisherCommon):
) -> Dict[str, Union[CreatedInstance, None]]: ) -> Dict[str, Union[CreatedInstance, None]]:
pass pass
@abstractmethod
def get_instances_context_info(
self, instance_ids: Optional[Iterable[str]] = None
):
pass
@abstractmethod @abstractmethod
def get_existing_product_names(self, folder_path: str) -> List[str]: def get_existing_product_names(self, folder_path: str) -> List[str]:
pass pass

View file

@ -190,6 +190,9 @@ class PublisherController(
def get_instances_by_id(self, instance_ids=None): def get_instances_by_id(self, instance_ids=None):
return self._create_model.get_instances_by_id(instance_ids) return self._create_model.get_instances_by_id(instance_ids)
def get_instances_context_info(self, instance_ids=None):
return self._create_model.get_instances_context_info(instance_ids)
def get_convertor_items(self): def get_convertor_items(self):
return self._create_model.get_convertor_items() return self._create_model.get_convertor_items()

View file

@ -306,6 +306,14 @@ class CreateModel:
for instance_id in instance_ids for instance_id in instance_ids
} }
def get_instances_context_info(
self, instance_ids: Optional[Iterable[str]] = None
):
instances = self.get_instances_by_id(instance_ids).values()
return self._create_context.get_instances_context_info(
instances
)
def get_convertor_items(self) -> Dict[str, ConvertorItem]: def get_convertor_items(self) -> Dict[str, ConvertorItem]:
return self._create_context.convertor_items_by_id return self._create_context.convertor_items_by_id

View file

@ -217,20 +217,22 @@ class InstanceGroupWidget(BaseGroupWidget):
def update_icons(self, group_icons): def update_icons(self, group_icons):
self._group_icons = group_icons self._group_icons = group_icons
def update_instance_values(self): def update_instance_values(self, context_info_by_id):
"""Trigger update on instance widgets.""" """Trigger update on instance widgets."""
for widget in self._widgets_by_id.values(): for instance_id, widget in self._widgets_by_id.items():
widget.update_instance_values() widget.update_instance_values(context_info_by_id[instance_id])
def update_instances(self, instances): def update_instances(self, instances, context_info_by_id):
"""Update instances for the group. """Update instances for the group.
Args: Args:
instances(list<CreatedInstance>): List of instances in instances (list[CreatedInstance]): List of instances in
CreateContext. CreateContext.
""" context_info_by_id (Dict[str, InstanceContextInfo]): Instance
context info by instance id.
"""
# Store instances by id and by product name # Store instances by id and by product name
instances_by_id = {} instances_by_id = {}
instances_by_product_name = collections.defaultdict(list) instances_by_product_name = collections.defaultdict(list)
@ -249,13 +251,14 @@ class InstanceGroupWidget(BaseGroupWidget):
widget_idx = 1 widget_idx = 1
for product_names in sorted_product_names: for product_names in sorted_product_names:
for instance in instances_by_product_name[product_names]: for instance in instances_by_product_name[product_names]:
context_info = context_info_by_id[instance.id]
if instance.id in self._widgets_by_id: if instance.id in self._widgets_by_id:
widget = self._widgets_by_id[instance.id] widget = self._widgets_by_id[instance.id]
widget.update_instance(instance) widget.update_instance(instance, context_info)
else: else:
group_icon = self._group_icons[instance.creator_identifier] group_icon = self._group_icons[instance.creator_identifier]
widget = InstanceCardWidget( widget = InstanceCardWidget(
instance, group_icon, self instance, context_info, group_icon, self
) )
widget.selected.connect(self._on_widget_selection) widget.selected.connect(self._on_widget_selection)
widget.active_changed.connect(self._on_active_changed) widget.active_changed.connect(self._on_active_changed)
@ -388,7 +391,7 @@ class ConvertorItemCardWidget(CardWidget):
self._icon_widget = icon_widget self._icon_widget = icon_widget
self._label_widget = label_widget self._label_widget = label_widget
def update_instance_values(self): def update_instance_values(self, context_info):
pass pass
@ -397,7 +400,7 @@ class InstanceCardWidget(CardWidget):
active_changed = QtCore.Signal(str, bool) active_changed = QtCore.Signal(str, bool)
def __init__(self, instance, group_icon, parent): def __init__(self, instance, context_info, group_icon, parent):
super().__init__(parent) super().__init__(parent)
self._id = instance.id self._id = instance.id
@ -458,7 +461,7 @@ class InstanceCardWidget(CardWidget):
self._active_checkbox = active_checkbox self._active_checkbox = active_checkbox
self._expand_btn = expand_btn self._expand_btn = expand_btn
self.update_instance_values() self.update_instance_values(context_info)
def set_active_toggle_enabled(self, enabled): def set_active_toggle_enabled(self, enabled):
self._active_checkbox.setEnabled(enabled) self._active_checkbox.setEnabled(enabled)
@ -480,13 +483,13 @@ class InstanceCardWidget(CardWidget):
if checkbox_value != new_value: if checkbox_value != new_value:
self._active_checkbox.setChecked(new_value) self._active_checkbox.setChecked(new_value)
def update_instance(self, instance): def update_instance(self, instance, context_info):
"""Update instance object and update UI.""" """Update instance object and update UI."""
self.instance = instance self.instance = instance
self.update_instance_values() self.update_instance_values(context_info)
def _validate_context(self): def _validate_context(self, context_info):
valid = self.instance.has_valid_context valid = context_info.is_valid
self._icon_widget.setVisible(valid) self._icon_widget.setVisible(valid)
self._context_warning.setVisible(not valid) self._context_warning.setVisible(not valid)
@ -519,11 +522,11 @@ class InstanceCardWidget(CardWidget):
QtCore.Qt.NoTextInteraction QtCore.Qt.NoTextInteraction
) )
def update_instance_values(self): def update_instance_values(self, context_info):
"""Update instance data""" """Update instance data"""
self._update_product_name() self._update_product_name()
self.set_active(self.instance["active"]) self.set_active(self.instance["active"])
self._validate_context() self._validate_context(context_info)
def _set_expanded(self, expanded=None): def _set_expanded(self, expanded=None):
if expanded is None: if expanded is None:
@ -694,6 +697,8 @@ class InstanceCardView(AbstractInstanceView):
self._update_convertor_items_group() self._update_convertor_items_group()
context_info_by_id = self._controller.get_instances_context_info()
# Prepare instances by group and identifiers by group # Prepare instances by group and identifiers by group
instances_by_group = collections.defaultdict(list) instances_by_group = collections.defaultdict(list)
identifiers_by_group = collections.defaultdict(set) identifiers_by_group = collections.defaultdict(set)
@ -747,7 +752,7 @@ class InstanceCardView(AbstractInstanceView):
widget_idx += 1 widget_idx += 1
group_widget.update_instances( group_widget.update_instances(
instances_by_group[group_name] instances_by_group[group_name], context_info_by_id
) )
group_widget.set_active_toggle_enabled( group_widget.set_active_toggle_enabled(
self._active_toggle_enabled self._active_toggle_enabled
@ -814,8 +819,9 @@ class InstanceCardView(AbstractInstanceView):
def refresh_instance_states(self): def refresh_instance_states(self):
"""Trigger update of instances on group widgets.""" """Trigger update of instances on group widgets."""
context_info_by_id = self._controller.get_instances_context_info()
for widget in self._widgets_by_group.values(): for widget in self._widgets_by_group.values():
widget.update_instance_values() widget.update_instance_values(context_info_by_id)
def _on_active_changed(self, group_name, instance_id, value): def _on_active_changed(self, group_name, instance_id, value):
group_widget = self._widgets_by_group[group_name] group_widget = self._widgets_by_group[group_name]

View file

@ -115,7 +115,7 @@ class InstanceListItemWidget(QtWidgets.QWidget):
active_changed = QtCore.Signal(str, bool) active_changed = QtCore.Signal(str, bool)
double_clicked = QtCore.Signal() double_clicked = QtCore.Signal()
def __init__(self, instance, parent): def __init__(self, instance, context_info, parent):
super().__init__(parent) super().__init__(parent)
self.instance = instance self.instance = instance
@ -151,7 +151,7 @@ class InstanceListItemWidget(QtWidgets.QWidget):
self._has_valid_context = None self._has_valid_context = None
self._set_valid_property(instance.has_valid_context) self._set_valid_property(context_info.is_valid)
def mouseDoubleClickEvent(self, event): def mouseDoubleClickEvent(self, event):
widget = self.childAt(event.pos()) widget = self.childAt(event.pos())
@ -188,12 +188,12 @@ class InstanceListItemWidget(QtWidgets.QWidget):
if checkbox_value != new_value: if checkbox_value != new_value:
self._active_checkbox.setChecked(new_value) self._active_checkbox.setChecked(new_value)
def update_instance(self, instance): def update_instance(self, instance, context_info):
"""Update instance object.""" """Update instance object."""
self.instance = instance self.instance = instance
self.update_instance_values() self.update_instance_values(context_info)
def update_instance_values(self): def update_instance_values(self, context_info):
"""Update instance data propagated to widgets.""" """Update instance data propagated to widgets."""
# Check product name # Check product name
label = self.instance.label label = self.instance.label
@ -202,7 +202,7 @@ class InstanceListItemWidget(QtWidgets.QWidget):
# Check active state # Check active state
self.set_active(self.instance["active"]) self.set_active(self.instance["active"])
# Check valid states # Check valid states
self._set_valid_property(self.instance.has_valid_context) self._set_valid_property(context_info.is_valid)
def _on_active_change(self): def _on_active_change(self):
new_value = self._active_checkbox.isChecked() new_value = self._active_checkbox.isChecked()
@ -583,6 +583,8 @@ class InstanceListView(AbstractInstanceView):
self._update_convertor_items_group() self._update_convertor_items_group()
context_info_by_id = self._controller.get_instances_context_info()
# Prepare instances by their groups # Prepare instances by their groups
instances_by_group_name = collections.defaultdict(list) instances_by_group_name = collections.defaultdict(list)
group_names = set() group_names = set()
@ -643,13 +645,15 @@ class InstanceListView(AbstractInstanceView):
elif activity != instance["active"]: elif activity != instance["active"]:
activity = -1 activity = -1
context_info = context_info_by_id[instance_id]
self._group_by_instance_id[instance_id] = group_name self._group_by_instance_id[instance_id] = group_name
# Remove instance id from `to_remove` if already exists and # Remove instance id from `to_remove` if already exists and
# trigger update of widget # trigger update of widget
if instance_id in to_remove: if instance_id in to_remove:
to_remove.remove(instance_id) to_remove.remove(instance_id)
widget = self._widgets_by_id[instance_id] widget = self._widgets_by_id[instance_id]
widget.update_instance(instance) widget.update_instance(instance, context_info)
continue continue
# Create new item and store it as new # Create new item and store it as new
@ -695,7 +699,8 @@ class InstanceListView(AbstractInstanceView):
group_item.appendRows(new_items) group_item.appendRows(new_items)
for item, instance in new_items_with_instance: for item, instance in new_items_with_instance:
if not instance.has_valid_context: context_info = context_info_by_id[instance.id]
if not context_info.is_valid:
expand_groups.add(group_name) expand_groups.add(group_name)
item_index = self._instance_model.index( item_index = self._instance_model.index(
item.row(), item.row(),
@ -704,7 +709,7 @@ class InstanceListView(AbstractInstanceView):
) )
proxy_index = self._proxy_model.mapFromSource(item_index) proxy_index = self._proxy_model.mapFromSource(item_index)
widget = InstanceListItemWidget( widget = InstanceListItemWidget(
instance, self._instance_view instance, context_info, self._instance_view
) )
widget.set_active_toggle_enabled( widget.set_active_toggle_enabled(
self._active_toggle_enabled self._active_toggle_enabled
@ -870,8 +875,10 @@ class InstanceListView(AbstractInstanceView):
def refresh_instance_states(self): def refresh_instance_states(self):
"""Trigger update of all instances.""" """Trigger update of all instances."""
for widget in self._widgets_by_id.values(): context_info_by_id = self._controller.get_instances_context_info()
widget.update_instance_values() for instance_id, widget in self._widgets_by_id.items():
context_info = context_info_by_id[instance_id]
widget.update_instance_values(context_info)
def _on_active_changed(self, changed_instance_id, new_value): def _on_active_changed(self, changed_instance_id, new_value):
selected_instance_ids, _, _ = self.get_selected_items() selected_instance_ids, _, _ = self.get_selected_items()

View file

@ -1206,7 +1206,6 @@ class GlobalAttrsWidget(QtWidgets.QWidget):
except TaskNotSetError: except TaskNotSetError:
invalid_tasks = True invalid_tasks = True
instance.set_task_invalid(True)
product_names.add(instance["productName"]) product_names.add(instance["productName"])
continue continue
@ -1216,11 +1215,9 @@ class GlobalAttrsWidget(QtWidgets.QWidget):
if folder_path is not None: if folder_path is not None:
instance["folderPath"] = folder_path instance["folderPath"] = folder_path
instance.set_folder_invalid(False)
if task_name is not None: if task_name is not None:
instance["task"] = task_name or None instance["task"] = task_name or None
instance.set_task_invalid(False)
instance["productName"] = new_product_name instance["productName"] = new_product_name
@ -1768,9 +1765,16 @@ class ProductAttributesWidget(QtWidgets.QWidget):
self.bottom_separator = bottom_separator self.bottom_separator = bottom_separator
def _on_instance_context_changed(self): def _on_instance_context_changed(self):
instance_ids = {
instance.id
for instance in self._current_instances
}
context_info_by_id = self._controller.get_instances_context_info(
instance_ids
)
all_valid = True all_valid = True
for instance in self._current_instances: for instance_id, context_info in context_info_by_id.items():
if not instance.has_valid_context: if not context_info.is_valid:
all_valid = False all_valid = False
break break
@ -1795,9 +1799,17 @@ class ProductAttributesWidget(QtWidgets.QWidget):
convertor_identifiers(List[str]): Identifiers of convert items. convertor_identifiers(List[str]): Identifiers of convert items.
""" """
instance_ids = {
instance.id
for instance in instances
}
context_info_by_id = self._controller.get_instances_context_info(
instance_ids
)
all_valid = True all_valid = True
for instance in instances: for context_info in context_info_by_id.values():
if not instance.has_valid_context: if not context_info.is_valid:
all_valid = False all_valid = False
break break

View file

@ -913,12 +913,18 @@ class PublisherWindow(QtWidgets.QDialog):
self._set_footer_enabled(True) self._set_footer_enabled(True)
return return
active_instances_by_id = {
instance.id: instance
for instance in self._controller.get_instances()
if instance["active"]
}
context_info_by_id = self._controller.get_instances_context_info(
active_instances_by_id.keys()
)
all_valid = None all_valid = None
for instance in self._controller.get_instances(): for instance_id, instance in active_instances_by_id.items():
if not instance["active"]: context_info = context_info_by_id[instance_id]
continue if not context_info.is_valid:
if not instance.has_valid_context:
all_valid = False all_valid = False
break break