From 13e88e70a2f21ceabc31a99ff1e2b0c28af04bb2 Mon Sep 17 00:00:00 2001 From: Aleks Berland Date: Thu, 30 Oct 2025 13:32:26 -0400 Subject: [PATCH 1/6] Fix for missing workfiles details with "Published" filter on - Implemented `get_published_workfile_info` and `get_published_workfile_version_comment` methods in the WorkfilesModel. - Updated AbstractWorkfilesFrontend to define these methods as abstract. - Enhanced BaseWorkfileController to call the new methods. - Modified SidePanelWidget to handle published workfile context and display relevant information. --- client/ayon_core/tools/workfiles/abstract.py | 27 ++++ client/ayon_core/tools/workfiles/control.py | 10 ++ .../tools/workfiles/models/workfiles.py | 64 ++++++++- .../tools/workfiles/widgets/side_panel.py | 126 +++++++++++++----- 4 files changed, 189 insertions(+), 38 deletions(-) diff --git a/client/ayon_core/tools/workfiles/abstract.py b/client/ayon_core/tools/workfiles/abstract.py index 863d6bb9bc..e7a038575a 100644 --- a/client/ayon_core/tools/workfiles/abstract.py +++ b/client/ayon_core/tools/workfiles/abstract.py @@ -787,6 +787,33 @@ class AbstractWorkfilesFrontend(AbstractWorkfilesCommon): """ pass + @abstractmethod + def get_published_workfile_info(self, representation_id: str): + """Get published workfile info by representation ID. + + Args: + representation_id (str): Representation id. + + Returns: + Optional[PublishedWorkfileInfo]: Published workfile info or None + if not found. + + """ + pass + + @abstractmethod + def get_published_workfile_version_comment(self, representation_id: str): + """Get version comment for published workfile. + + Args: + representation_id (str): Representation id. + + Returns: + Optional[str]: Version comment or None. + + """ + pass + @abstractmethod def get_workfile_info(self, folder_id, task_id, rootless_path): """Workfile info from database. diff --git a/client/ayon_core/tools/workfiles/control.py b/client/ayon_core/tools/workfiles/control.py index f0e0f0e416..13a88e325f 100644 --- a/client/ayon_core/tools/workfiles/control.py +++ b/client/ayon_core/tools/workfiles/control.py @@ -432,6 +432,16 @@ class BaseWorkfileController( folder_id, task_id ) + def get_published_workfile_info(self, representation_id): + return self._workfiles_model.get_published_workfile_info( + representation_id + ) + + def get_published_workfile_version_comment(self, representation_id): + return self._workfiles_model.get_published_workfile_version_comment( + representation_id + ) + def get_workfile_info(self, folder_id, task_id, rootless_path): return self._workfiles_model.get_workfile_info( folder_id, task_id, rootless_path diff --git a/client/ayon_core/tools/workfiles/models/workfiles.py b/client/ayon_core/tools/workfiles/models/workfiles.py index 5b5591fe43..5055c203be 100644 --- a/client/ayon_core/tools/workfiles/models/workfiles.py +++ b/client/ayon_core/tools/workfiles/models/workfiles.py @@ -79,6 +79,7 @@ class WorkfilesModel: # Published workfiles self._repre_by_id = {} + self._version_by_repre_id = {} self._published_workfile_items_cache = NestedCacheItem( levels=1, default_factory=list ) @@ -95,6 +96,7 @@ class WorkfilesModel: self._workarea_file_items_cache.reset() self._repre_by_id = {} + self._version_by_repre_id = {} self._published_workfile_items_cache.reset() self._workfile_entities_by_task_id = {} @@ -586,7 +588,7 @@ class WorkfilesModel: version_entities = list(ayon_api.get_versions( project_name, product_ids=product_ids, - fields={"id", "author", "taskId"}, + fields={"id", "author", "taskId", "attrib.comment"}, )) repre_entities = [] @@ -600,6 +602,20 @@ class WorkfilesModel: repre_entity["id"]: repre_entity for repre_entity in repre_entities }) + + # Map versions by representation ID for easy lookup + version_by_id = { + version_entity["id"]: version_entity + for version_entity in version_entities + } + for repre_entity in repre_entities: + repre_id = repre_entity["id"] + version_id = repre_entity.get("versionId") + if version_id and version_id in version_by_id: + self._version_by_repre_id[repre_id] = version_by_id[ + version_id + ] + project_entity = self._controller.get_project_entity(project_name) prepared_data = ListPublishedWorkfilesOptionalData( @@ -626,6 +642,52 @@ class WorkfilesModel: ] return items + def get_published_workfile_info( + self, representation_id: str + ) -> Optional[PublishedWorkfileInfo]: + """Get published workfile info by representation ID. + + Args: + representation_id (str): Representation id. + + Returns: + Optional[PublishedWorkfileInfo]: Published workfile info or None + if not found. + + """ + if not representation_id: + return None + + # Search through all cached published workfile items + cache_items = self._published_workfile_items_cache._data_by_key + for folder_cache in cache_items.values(): + if folder_cache.is_valid: + for item in folder_cache.get_data(): + if item.representation_id == representation_id: + return item + return None + + def get_published_workfile_version_comment( + self, representation_id: str + ) -> Optional[str]: + """Get version comment for published workfile. + + Args: + representation_id (str): Representation id. + + Returns: + Optional[str]: Version comment or None. + + """ + if not representation_id: + return None + + version_entity = self._version_by_repre_id.get(representation_id) + if version_entity: + attrib = version_entity.get("attrib") or {} + return attrib.get("comment") + return None + @property def _project_name(self) -> str: return self._controller.get_current_project_name() diff --git a/client/ayon_core/tools/workfiles/widgets/side_panel.py b/client/ayon_core/tools/workfiles/widgets/side_panel.py index b1b91d9721..2834f1dec8 100644 --- a/client/ayon_core/tools/workfiles/widgets/side_panel.py +++ b/client/ayon_core/tools/workfiles/widgets/side_panel.py @@ -1,17 +1,13 @@ import datetime -from qtpy import QtWidgets, QtCore +from qtpy import QtCore, QtWidgets def file_size_to_string(file_size): if not file_size: return "N/A" size = 0 - size_ending_mapping = { - "KB": 1024 ** 1, - "MB": 1024 ** 2, - "GB": 1024 ** 3 - } + size_ending_mapping = {"KB": 1024**1, "MB": 1024**2, "GB": 1024**3} ending = "B" for _ending, _size in size_ending_mapping.items(): if file_size < _size: @@ -70,7 +66,11 @@ class SidePanelWidget(QtWidgets.QWidget): btn_description_save.clicked.connect(self._on_save_click) controller.register_event_callback( - "selection.workarea.changed", self._on_selection_change + "selection.workarea.changed", self._on_workarea_selection_change + ) + controller.register_event_callback( + "selection.representation.changed", + self._on_representation_selection_change, ) self._details_input = details_input @@ -82,10 +82,11 @@ class SidePanelWidget(QtWidgets.QWidget): self._task_id = None self._filepath = None self._rootless_path = None + self._representation_id = None self._orig_description = "" self._controller = controller - self._set_context(None, None, None, None) + self._set_context(None, None, None, None, None) def set_published_mode(self, published_mode): """Change published mode. @@ -95,14 +96,21 @@ class SidePanelWidget(QtWidgets.QWidget): """ self._description_widget.setVisible(not published_mode) + # Clear the context when switching modes to avoid showing stale data + self._set_context(None, None, None, None, None) - def _on_selection_change(self, event): + def _on_workarea_selection_change(self, event): folder_id = event["folder_id"] task_id = event["task_id"] filepath = event["path"] rootless_path = event["rootless_path"] - self._set_context(folder_id, task_id, rootless_path, filepath) + self._set_context(folder_id, task_id, rootless_path, filepath, None) + + def _on_representation_selection_change(self, event): + representation_id = event["representation_id"] + + self._set_context(None, None, None, None, representation_id) def _on_description_change(self): text = self._description_input.toPlainText() @@ -118,23 +126,45 @@ class SidePanelWidget(QtWidgets.QWidget): self._orig_description = description self._btn_description_save.setEnabled(False) - def _set_context(self, folder_id, task_id, rootless_path, filepath): + def _set_context( + self, folder_id, task_id, rootless_path, filepath, representation_id + ): workfile_info = None - # Check if folder, task and file are selected + published_workfile_info = None + + # Check if folder, task and file are selected (workarea mode) if folder_id and task_id and rootless_path: workfile_info = self._controller.get_workfile_info( folder_id, task_id, rootless_path ) - enabled = workfile_info is not None + # Check if representation is selected (published mode) + elif representation_id: + published_workfile_info = ( + self._controller.get_published_workfile_info(representation_id) + ) + + # Get version comment for published workfiles + version_comment = None + if representation_id and published_workfile_info: + version_comment = ( + self._controller.get_published_workfile_version_comment( + representation_id + ) + ) + + enabled = ( + workfile_info is not None or published_workfile_info is not None + ) self._details_input.setEnabled(enabled) - self._description_input.setEnabled(enabled) - self._btn_description_save.setEnabled(enabled) + self._description_input.setEnabled(workfile_info is not None) + self._btn_description_save.setEnabled(workfile_info is not None) self._folder_id = folder_id self._task_id = task_id self._filepath = filepath self._rootless_path = rootless_path + self._representation_id = representation_id # Disable inputs and remove texts if any required arguments are # missing @@ -144,19 +174,28 @@ class SidePanelWidget(QtWidgets.QWidget): self._description_input.setPlainText("") return - description = workfile_info.description - size_value = file_size_to_string(workfile_info.file_size) + # Use published workfile info if available, otherwise use workarea + # info + info = ( + published_workfile_info + if published_workfile_info + else workfile_info + ) + + description = info.description if hasattr(info, "description") else "" + size_value = file_size_to_string(info.file_size) # Append html string datetime_format = "%b %d %Y %H:%M:%S" - file_created = workfile_info.file_created - modification_time = workfile_info.file_modified + file_created = info.file_created + modification_time = info.file_modified if file_created: file_created = datetime.datetime.fromtimestamp(file_created) if modification_time: modification_time = datetime.datetime.fromtimestamp( - modification_time) + modification_time + ) user_items_by_name = self._controller.get_user_items_by_name() @@ -167,34 +206,47 @@ class SidePanelWidget(QtWidgets.QWidget): return username created_lines = [] - if workfile_info.created_by: - created_lines.append( - convert_username(workfile_info.created_by) - ) - if file_created: - created_lines.append(file_created.strftime(datetime_format)) + # For published workfiles, use 'author' field + if published_workfile_info: + if published_workfile_info.author: + created_lines.append( + convert_username(published_workfile_info.author) + ) + if file_created: + created_lines.append(file_created.strftime(datetime_format)) + else: + # For workarea workfiles, use 'created_by' field + if workfile_info.created_by: + created_lines.append( + convert_username(workfile_info.created_by) + ) + if file_created: + created_lines.append(file_created.strftime(datetime_format)) if created_lines: created_lines.insert(0, "Created:") modified_lines = [] - if workfile_info.updated_by: - modified_lines.append( - convert_username(workfile_info.updated_by) - ) + # For workarea workfiles, show 'updated_by' + if workfile_info and workfile_info.updated_by: + modified_lines.append(convert_username(workfile_info.updated_by)) if modification_time: - modified_lines.append( - modification_time.strftime(datetime_format) - ) + modified_lines.append(modification_time.strftime(datetime_format)) if modified_lines: modified_lines.insert(0, "Modified:") - lines = ( + lines = [ "Size:", size_value, - "
".join(created_lines), - "
".join(modified_lines), - ) + ] + # Add version comment for published workfiles + if version_comment: + lines.append(f"Comment:
{version_comment}") + if created_lines: + lines.append("
".join(created_lines)) + if modified_lines: + lines.append("
".join(modified_lines)) + self._orig_description = description self._description_input.setPlainText(description) From 3338dbe4733b1d6d93f43a01284e5e9404fd8239 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Fri, 7 Nov 2025 15:51:30 +0100 Subject: [PATCH 2/6] selection keeps track about every value --- .../tools/workfiles/models/selection.py | 38 ++++++++++++------- 1 file changed, 25 insertions(+), 13 deletions(-) diff --git a/client/ayon_core/tools/workfiles/models/selection.py b/client/ayon_core/tools/workfiles/models/selection.py index 9a6440b2a1..65caa287d1 100644 --- a/client/ayon_core/tools/workfiles/models/selection.py +++ b/client/ayon_core/tools/workfiles/models/selection.py @@ -17,6 +17,8 @@ class SelectionModel(object): self._task_name = None self._task_id = None self._workfile_path = None + self._rootless_workfile_path = None + self._workfile_entity_id = None self._representation_id = None def get_selected_folder_id(self): @@ -62,39 +64,49 @@ class SelectionModel(object): def get_selected_workfile_path(self): return self._workfile_path + def get_selected_workfile_data(self): + return { + "project_name": self._controller.get_current_project_name(), + "path": self._workfile_path, + "rootless_path": self._rootless_workfile_path, + "folder_id": self._folder_id, + "task_name": self._task_name, + "task_id": self._task_id, + "workfile_entity_id": self._workfile_entity_id, + } + def set_selected_workfile_path( self, rootless_path, path, workfile_entity_id ): if path == self._workfile_path: return + self._rootless_workfile_path = rootless_path self._workfile_path = path + self._workfile_entity_id = workfile_entity_id self._controller.emit_event( "selection.workarea.changed", - { - "project_name": self._controller.get_current_project_name(), - "path": path, - "rootless_path": rootless_path, - "folder_id": self._folder_id, - "task_name": self._task_name, - "task_id": self._task_id, - "workfile_entity_id": workfile_entity_id, - }, + self.get_selected_workfile_data(), self.event_source ) def get_selected_representation_id(self): return self._representation_id + def get_selected_representation_data(self): + return { + "project_name": self._controller.get_current_project_name(), + "folder_id": self._folder_id, + "task_id": self._task_id, + "representation_id": self._representation_id, + } + def set_selected_representation_id(self, representation_id): if representation_id == self._representation_id: return self._representation_id = representation_id self._controller.emit_event( "selection.representation.changed", - { - "project_name": self._controller.get_current_project_name(), - "representation_id": representation_id, - }, + self.get_selected_representation_data(), self.event_source ) From ece086c03f9eede9ce8214afbb8a2261b2fbd8d5 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Fri, 7 Nov 2025 15:52:35 +0100 Subject: [PATCH 3/6] merge tho methods into one --- client/ayon_core/tools/workfiles/abstract.py | 45 +++++---- client/ayon_core/tools/workfiles/control.py | 17 ++-- .../tools/workfiles/models/workfiles.py | 91 +++++++++---------- 3 files changed, 82 insertions(+), 71 deletions(-) diff --git a/client/ayon_core/tools/workfiles/abstract.py b/client/ayon_core/tools/workfiles/abstract.py index e7a038575a..b41553acc4 100644 --- a/client/ayon_core/tools/workfiles/abstract.py +++ b/client/ayon_core/tools/workfiles/abstract.py @@ -1,8 +1,18 @@ +from __future__ import annotations + import os from abc import ABC, abstractmethod +import typing +from typing import Optional from ayon_core.style import get_default_entity_icon_color +if typing.TYPE_CHECKING: + from ayon_core.host import ( + WorkfileInfo, + PublishedWorkfileInfo, + ) + class FolderItem: """Item representing folder entity on a server. @@ -159,6 +169,17 @@ class WorkareaFilepathResult: self.filepath = filepath +class PublishedWorkfileWrap: + """Wrapper for workfile info that also contains version comment.""" + def __init__( + self, + info: Optional[PublishedWorkfileInfo] = None, + comment: Optional[str] = None, + ) -> None: + self.info = info + self.comment = comment + + class AbstractWorkfilesCommon(ABC): @abstractmethod def is_host_valid(self): @@ -788,32 +809,24 @@ class AbstractWorkfilesFrontend(AbstractWorkfilesCommon): pass @abstractmethod - def get_published_workfile_info(self, representation_id: str): + def get_published_workfile_info( + self, + folder_id: Optional[str], + representation_id: Optional[str], + ) -> PublishedWorkfileWrap: """Get published workfile info by representation ID. Args: - representation_id (str): Representation id. + folder_id (Optional[str]): Folder id. + representation_id (Optional[str]): Representation id. Returns: - Optional[PublishedWorkfileInfo]: Published workfile info or None + PublishedWorkfileWrap: Published workfile info or None if not found. """ pass - @abstractmethod - def get_published_workfile_version_comment(self, representation_id: str): - """Get version comment for published workfile. - - Args: - representation_id (str): Representation id. - - Returns: - Optional[str]: Version comment or None. - - """ - pass - @abstractmethod def get_workfile_info(self, folder_id, task_id, rootless_path): """Workfile info from database. diff --git a/client/ayon_core/tools/workfiles/control.py b/client/ayon_core/tools/workfiles/control.py index 13a88e325f..c399a1bf33 100644 --- a/client/ayon_core/tools/workfiles/control.py +++ b/client/ayon_core/tools/workfiles/control.py @@ -1,4 +1,7 @@ +from __future__ import annotations + import os +from typing import Optional import ayon_api @@ -18,6 +21,7 @@ from ayon_core.tools.common_models import ( from .abstract import ( AbstractWorkfilesBackend, AbstractWorkfilesFrontend, + PublishedWorkfileWrap, ) from .models import SelectionModel, WorkfilesModel @@ -432,14 +436,13 @@ class BaseWorkfileController( folder_id, task_id ) - def get_published_workfile_info(self, representation_id): + def get_published_workfile_info( + self, + folder_id: Optional[str], + representation_id: Optional[str], + ) -> PublishedWorkfileWrap: return self._workfiles_model.get_published_workfile_info( - representation_id - ) - - def get_published_workfile_version_comment(self, representation_id): - return self._workfiles_model.get_published_workfile_version_comment( - representation_id + folder_id, representation_id ) def get_workfile_info(self, folder_id, task_id, rootless_path): diff --git a/client/ayon_core/tools/workfiles/models/workfiles.py b/client/ayon_core/tools/workfiles/models/workfiles.py index 5055c203be..c15dda2b4f 100644 --- a/client/ayon_core/tools/workfiles/models/workfiles.py +++ b/client/ayon_core/tools/workfiles/models/workfiles.py @@ -39,6 +39,7 @@ from ayon_core.pipeline.workfile import ( from ayon_core.pipeline.version_start import get_versioning_start from ayon_core.tools.workfiles.abstract import ( WorkareaFilepathResult, + PublishedWorkfileWrap, AbstractWorkfilesBackend, ) @@ -79,7 +80,7 @@ class WorkfilesModel: # Published workfiles self._repre_by_id = {} - self._version_by_repre_id = {} + self._version_comment_by_id = {} self._published_workfile_items_cache = NestedCacheItem( levels=1, default_factory=list ) @@ -96,7 +97,7 @@ class WorkfilesModel: self._workarea_file_items_cache.reset() self._repre_by_id = {} - self._version_by_repre_id = {} + self._version_comment_by_id = {} self._published_workfile_items_cache.reset() self._workfile_entities_by_task_id = {} @@ -554,13 +555,13 @@ class WorkfilesModel: ) def get_published_file_items( - self, folder_id: str, task_id: str + self, folder_id: Optional[str], task_id: Optional[str] ) -> list[PublishedWorkfileInfo]: """Published workfiles for passed context. Args: - folder_id (str): Folder id. - task_id (str): Task id. + folder_id (Optional[str]): Folder id. + task_id (Optional[str]): Task id. Returns: list[PublishedWorkfileInfo]: List of files for published workfiles. @@ -604,17 +605,10 @@ class WorkfilesModel: }) # Map versions by representation ID for easy lookup - version_by_id = { - version_entity["id"]: version_entity + self._version_comment_by_id.update({ + version_entity["id"]: version_entity["attrib"].get("comment") for version_entity in version_entities - } - for repre_entity in repre_entities: - repre_id = repre_entity["id"] - version_id = repre_entity.get("versionId") - if version_id and version_id in version_by_id: - self._version_by_repre_id[repre_id] = version_by_id[ - version_id - ] + }) project_entity = self._controller.get_project_entity(project_name) @@ -643,50 +637,32 @@ class WorkfilesModel: return items def get_published_workfile_info( - self, representation_id: str - ) -> Optional[PublishedWorkfileInfo]: + self, + folder_id: Optional[str], + representation_id: Optional[str], + ) -> PublishedWorkfileWrap: """Get published workfile info by representation ID. Args: - representation_id (str): Representation id. + folder_id (Optional[str]): Folder id. + representation_id (Optional[str]): Representation id. Returns: - Optional[PublishedWorkfileInfo]: Published workfile info or None + PublishedWorkfileWrap: Published workfile info or None if not found. """ if not representation_id: - return None + return PublishedWorkfileWrap() # Search through all cached published workfile items - cache_items = self._published_workfile_items_cache._data_by_key - for folder_cache in cache_items.values(): - if folder_cache.is_valid: - for item in folder_cache.get_data(): - if item.representation_id == representation_id: - return item - return None - - def get_published_workfile_version_comment( - self, representation_id: str - ) -> Optional[str]: - """Get version comment for published workfile. - - Args: - representation_id (str): Representation id. - - Returns: - Optional[str]: Version comment or None. - - """ - if not representation_id: - return None - - version_entity = self._version_by_repre_id.get(representation_id) - if version_entity: - attrib = version_entity.get("attrib") or {} - return attrib.get("comment") - return None + for item in self.get_published_file_items(folder_id, None): + if item.representation_id == representation_id: + comment = self._get_published_workfile_version_comment( + representation_id + ) + return PublishedWorkfileWrap(item, comment) + return PublishedWorkfileWrap() @property def _project_name(self) -> str: @@ -704,6 +680,25 @@ class WorkfilesModel: self._current_username = get_ayon_username() return self._current_username + def _get_published_workfile_version_comment( + self, representation_id: str + ) -> Optional[str]: + """Get version comment for published workfile. + + Args: + representation_id (str): Representation id. + + Returns: + Optional[str]: Version comment or None. + + """ + if not representation_id: + return None + repre = self._repre_by_id.get(representation_id) + if not repre: + return None + return self._version_comment_by_id.get(repre["versionId"]) + # --- Host --- def _open_workfile(self, folder_id: str, task_id: str, filepath: str): # TODO move to workfiles pipeline From 0cc99003f6a35cb942bd9a486255ee0b2f6a1cda Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Fri, 7 Nov 2025 15:52:47 +0100 Subject: [PATCH 4/6] separate logic for workare and publishe workfiles --- .../tools/workfiles/widgets/side_panel.py | 235 ++++++++++-------- 1 file changed, 131 insertions(+), 104 deletions(-) diff --git a/client/ayon_core/tools/workfiles/widgets/side_panel.py b/client/ayon_core/tools/workfiles/widgets/side_panel.py index 2834f1dec8..f79dbca032 100644 --- a/client/ayon_core/tools/workfiles/widgets/side_panel.py +++ b/client/ayon_core/tools/workfiles/widgets/side_panel.py @@ -1,4 +1,5 @@ import datetime +from typing import Optional from qtpy import QtCore, QtWidgets @@ -7,7 +8,11 @@ def file_size_to_string(file_size): if not file_size: return "N/A" size = 0 - size_ending_mapping = {"KB": 1024**1, "MB": 1024**2, "GB": 1024**3} + size_ending_mapping = { + "KB": 1024**1, + "MB": 1024**2, + "GB": 1024**3, + } ending = "B" for _ending, _size in size_ending_mapping.items(): if file_size < _size: @@ -66,7 +71,8 @@ class SidePanelWidget(QtWidgets.QWidget): btn_description_save.clicked.connect(self._on_save_click) controller.register_event_callback( - "selection.workarea.changed", self._on_workarea_selection_change + "selection.workarea.changed", + self._on_workarea_selection_change ) controller.register_event_callback( "selection.representation.changed", @@ -86,9 +92,9 @@ class SidePanelWidget(QtWidgets.QWidget): self._orig_description = "" self._controller = controller - self._set_context(None, None, None, None, None) + self._set_context(False, None, None) - def set_published_mode(self, published_mode): + def set_published_mode(self, published_mode: bool) -> None: """Change published mode. Args: @@ -97,7 +103,19 @@ class SidePanelWidget(QtWidgets.QWidget): self._description_widget.setVisible(not published_mode) # Clear the context when switching modes to avoid showing stale data - self._set_context(None, None, None, None, None) + if published_mode: + self._set_publish_context( + self._folder_id, + self._task_id, + self._representation_id, + ) + else: + self._set_workarea_context( + self._folder_id, + self._task_id, + self._rootless_path, + self._filepath, + ) def _on_workarea_selection_change(self, event): folder_id = event["folder_id"] @@ -105,12 +123,16 @@ class SidePanelWidget(QtWidgets.QWidget): filepath = event["path"] rootless_path = event["rootless_path"] - self._set_context(folder_id, task_id, rootless_path, filepath, None) + self._set_workarea_context( + folder_id, task_id, rootless_path, filepath + ) def _on_representation_selection_change(self, event): + folder_id = event["folder_id"] + task_id = event["task_id"] representation_id = event["representation_id"] - self._set_context(None, None, None, None, representation_id) + self._set_publish_context(folder_id, task_id, representation_id) def _on_description_change(self): text = self._description_input.toPlainText() @@ -126,130 +148,135 @@ class SidePanelWidget(QtWidgets.QWidget): self._orig_description = description self._btn_description_save.setEnabled(False) - def _set_context( - self, folder_id, task_id, rootless_path, filepath, representation_id - ): - workfile_info = None - published_workfile_info = None + def _set_workarea_context( + self, + folder_id: Optional[str], + task_id: Optional[str], + rootless_path: Optional[str], + filepath: Optional[str], + ) -> None: + self._rootless_path = rootless_path + self._filepath = filepath - # Check if folder, task and file are selected (workarea mode) + workfile_info = None + # Check if folder, task and file are selected if folder_id and task_id and rootless_path: workfile_info = self._controller.get_workfile_info( folder_id, task_id, rootless_path ) - # Check if representation is selected (published mode) - elif representation_id: - published_workfile_info = ( - self._controller.get_published_workfile_info(representation_id) - ) - # Get version comment for published workfiles - version_comment = None - if representation_id and published_workfile_info: - version_comment = ( - self._controller.get_published_workfile_version_comment( - representation_id - ) - ) - - enabled = ( - workfile_info is not None or published_workfile_info is not None - ) - - self._details_input.setEnabled(enabled) - self._description_input.setEnabled(workfile_info is not None) - self._btn_description_save.setEnabled(workfile_info is not None) - - self._folder_id = folder_id - self._task_id = task_id - self._filepath = filepath - self._rootless_path = rootless_path - self._representation_id = representation_id - - # Disable inputs and remove texts if any required arguments are - # missing - if not enabled: + if workfile_info is None: self._orig_description = "" - self._details_input.setPlainText("") self._description_input.setPlainText("") + self._set_context(False, folder_id, task_id) return - # Use published workfile info if available, otherwise use workarea - # info - info = ( - published_workfile_info - if published_workfile_info - else workfile_info + self._set_context( + True, + folder_id, + task_id, + file_created = workfile_info.file_created, + file_modified=workfile_info.file_modified, + size_value=workfile_info.file_size, + created_by=workfile_info.created_by, + updated_by=workfile_info.updated_by, ) - description = info.description if hasattr(info, "description") else "" - size_value = file_size_to_string(info.file_size) + description = workfile_info.description + self._orig_description = description + self._description_input.setPlainText(description) + + def _set_publish_context( + self, + folder_id: Optional[str], + task_id: Optional[str], + representation_id: Optional[str], + ) -> None: + self._representation_id = representation_id + published_workfile_wrap = self._controller.get_published_workfile_info( + folder_id, + representation_id, + ) + info = published_workfile_wrap.info + comment = published_workfile_wrap.comment + if info is None: + self._set_context(False, folder_id, task_id) + return + + self._set_context( + True, + folder_id, + task_id, + file_created=info.file_created, + file_modified=info.file_modified, + size_value=info.file_size, + created_by=info.author, + comment=comment, + ) + + def _set_context( + self, + is_valid: bool, + folder_id: Optional[str], + task_id: Optional[str], + *, + file_created: Optional[int] = None, + file_modified: Optional[int] = None, + size_value: Optional[int] = None, + created_by: Optional[str] = None, + updated_by: Optional[str] = None, + comment: Optional[str] = None, + ) -> None: + self._folder_id = folder_id + self._task_id = task_id + + self._details_input.setEnabled(is_valid) + self._description_input.setEnabled(is_valid) + self._btn_description_save.setEnabled(is_valid) + if not is_valid: + self._details_input.setPlainText("") + return - # Append html string datetime_format = "%b %d %Y %H:%M:%S" - file_created = info.file_created - modification_time = info.file_modified if file_created: file_created = datetime.datetime.fromtimestamp(file_created) - if modification_time: - modification_time = datetime.datetime.fromtimestamp( - modification_time + if file_modified: + file_modified = datetime.datetime.fromtimestamp( + file_modified ) user_items_by_name = self._controller.get_user_items_by_name() - def convert_username(username): - user_item = user_items_by_name.get(username) + def convert_username(username_v): + user_item = user_items_by_name.get(username_v) if user_item is not None and user_item.full_name: return user_item.full_name - return username + return username_v - created_lines = [] - # For published workfiles, use 'author' field - if published_workfile_info: - if published_workfile_info.author: - created_lines.append( - convert_username(published_workfile_info.author) - ) - if file_created: - created_lines.append(file_created.strftime(datetime_format)) - else: - # For workarea workfiles, use 'created_by' field - if workfile_info.created_by: - created_lines.append( - convert_username(workfile_info.created_by) - ) - if file_created: - created_lines.append(file_created.strftime(datetime_format)) + lines = [] + if size_value is not None: + size_value = file_size_to_string(size_value) + lines.append(f"Size:
{size_value}") - if created_lines: - created_lines.insert(0, "Created:") - - modified_lines = [] - # For workarea workfiles, show 'updated_by' - if workfile_info and workfile_info.updated_by: - modified_lines.append(convert_username(workfile_info.updated_by)) - if modification_time: - modified_lines.append(modification_time.strftime(datetime_format)) - if modified_lines: - modified_lines.insert(0, "Modified:") - - lines = [ - "Size:", - size_value, - ] # Add version comment for published workfiles - if version_comment: - lines.append(f"Comment:
{version_comment}") - if created_lines: - lines.append("
".join(created_lines)) - if modified_lines: - lines.append("
".join(modified_lines)) + if comment: + lines.append(f"Comment:
{comment}") - self._orig_description = description - self._description_input.setPlainText(description) + if created_by or file_created: + lines.append("Created:") + if created_by: + lines.append(convert_username(created_by)) + if file_created: + lines.append(file_created.strftime(datetime_format)) + + if updated_by or file_modified: + lines.append("Modified:") + if updated_by: + lines.append(convert_username(updated_by)) + if file_modified: + lines.append(file_modified.strftime(datetime_format)) # Set as empty string self._details_input.setPlainText("") - self._details_input.appendHtml("
".join(lines)) + self._details_input.appendHtml("
".join(lines)) \ No newline at end of file From 48d2151d059b68a34e9c00ea989c6dd897fb9673 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Fri, 7 Nov 2025 16:11:31 +0100 Subject: [PATCH 5/6] ruff fixes --- client/ayon_core/tools/workfiles/widgets/side_panel.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/ayon_core/tools/workfiles/widgets/side_panel.py b/client/ayon_core/tools/workfiles/widgets/side_panel.py index f79dbca032..2929ac780d 100644 --- a/client/ayon_core/tools/workfiles/widgets/side_panel.py +++ b/client/ayon_core/tools/workfiles/widgets/side_panel.py @@ -175,7 +175,7 @@ class SidePanelWidget(QtWidgets.QWidget): True, folder_id, task_id, - file_created = workfile_info.file_created, + file_created=workfile_info.file_created, file_modified=workfile_info.file_modified, size_value=workfile_info.file_size, created_by=workfile_info.created_by, @@ -279,4 +279,4 @@ class SidePanelWidget(QtWidgets.QWidget): # Set as empty string self._details_input.setPlainText("") - self._details_input.appendHtml("
".join(lines)) \ No newline at end of file + self._details_input.appendHtml("
".join(lines)) From f8e4b29a6cd8200bdbc12d71e22b6fb94851626b Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Fri, 7 Nov 2025 16:16:08 +0100 Subject: [PATCH 6/6] remove unused import --- client/ayon_core/tools/workfiles/abstract.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/client/ayon_core/tools/workfiles/abstract.py b/client/ayon_core/tools/workfiles/abstract.py index b41553acc4..1b92c0d334 100644 --- a/client/ayon_core/tools/workfiles/abstract.py +++ b/client/ayon_core/tools/workfiles/abstract.py @@ -8,10 +8,7 @@ from typing import Optional from ayon_core.style import get_default_entity_icon_color if typing.TYPE_CHECKING: - from ayon_core.host import ( - WorkfileInfo, - PublishedWorkfileInfo, - ) + from ayon_core.host import PublishedWorkfileInfo class FolderItem: