From 00c34602e2a4789c6e042d8485d062f2371086a3 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 21 May 2025 23:15:44 +0200 Subject: [PATCH] Add setting to define a minimum amount of characters required for a comment on every publish --- client/ayon_core/tools/publisher/abstract.py | 25 ++++++++++++ client/ayon_core/tools/publisher/control.py | 17 ++++++++- client/ayon_core/tools/publisher/window.py | 40 +++++++++++++++++++- server/settings/tools.py | 9 +++++ 4 files changed, 88 insertions(+), 3 deletions(-) diff --git a/client/ayon_core/tools/publisher/abstract.py b/client/ayon_core/tools/publisher/abstract.py index 4ed91813d3..6d0027d35d 100644 --- a/client/ayon_core/tools/publisher/abstract.py +++ b/client/ayon_core/tools/publisher/abstract.py @@ -1,4 +1,5 @@ from abc import ABC, abstractmethod +from dataclasses import dataclass, asdict from typing import ( Optional, Dict, @@ -28,6 +29,19 @@ if TYPE_CHECKING: from .models import CreatorItem, PublishErrorInfo, InstanceItem +@dataclass +class CommentDef: + """Comment attribute definition.""" + minimum_chars_required: int + + def to_data(self): + return asdict(self) + + @classmethod + def from_data(cls, data): + return cls(**data) + + class CardMessageTypes: standard = None info = "info" @@ -135,6 +149,17 @@ class AbstractPublisherCommon(ABC): pass + @abstractmethod + def get_comment_def(self) -> CommentDef: + """Get comment attribute definition. + + This can define how the Comment field should behave, like having + a minimum amount of required characters before being allowed to + publish. + + """ + pass + class AbstractPublisherBackend(AbstractPublisherCommon): @abstractmethod diff --git a/client/ayon_core/tools/publisher/control.py b/client/ayon_core/tools/publisher/control.py index 98fdda08cf..ef2e122692 100644 --- a/client/ayon_core/tools/publisher/control.py +++ b/client/ayon_core/tools/publisher/control.py @@ -20,7 +20,8 @@ from .models import ( from .abstract import ( AbstractPublisherBackend, AbstractPublisherFrontend, - CardMessageTypes + CardMessageTypes, + CommentDef, ) @@ -601,3 +602,17 @@ class PublisherController( def _start_publish(self, up_validation): self._publish_model.set_publish_up_validation(up_validation) self._publish_model.start_publish(wait=True) + + def get_comment_def(self) -> CommentDef: + # Take the cached settings from the Create Context + settings = self.get_create_context().get_current_project_settings() + comment_minimum_required_chars: int = ( + settings + .get("core", {}) + .get("tools", {}) + .get("publish", {}) + .get("comment_minimum_required_chars", 0) + ) + return CommentDef( + minimum_chars_required=comment_minimum_required_chars + ) diff --git a/client/ayon_core/tools/publisher/window.py b/client/ayon_core/tools/publisher/window.py index ed5b909a55..a967aef21c 100644 --- a/client/ayon_core/tools/publisher/window.py +++ b/client/ayon_core/tools/publisher/window.py @@ -245,6 +245,12 @@ class PublisherWindow(QtWidgets.QDialog): show_timer.setInterval(1) show_timer.timeout.connect(self._on_show_timer) + comment_invalid_timer = QtCore.QTimer() + comment_invalid_timer.setInterval(2500) + comment_invalid_timer.timeout.connect( + self._on_comment_invalid_timeout + ) + errors_dialog_message_timer = QtCore.QTimer() errors_dialog_message_timer.setInterval(100) errors_dialog_message_timer.timeout.connect( @@ -395,6 +401,7 @@ class PublisherWindow(QtWidgets.QDialog): self._app_event_listener_installed = False self._show_timer = show_timer + self._comment_invalid_timer = comment_invalid_timer self._show_counter = 0 self._window_is_visible = False @@ -823,15 +830,44 @@ class PublisherWindow(QtWidgets.QDialog): self._controller.set_comment(self._comment_input.text()) def _on_validate_clicked(self): - if self._save_changes(False): + if self._start_publish_preflight() and self._save_changes(False): self._set_publish_comment() self._controller.validate() def _on_publish_clicked(self): - if self._save_changes(False): + if self._start_publish_preflight() and self._save_changes(False): self._set_publish_comment() self._controller.publish() + def _start_publish_preflight(self) -> bool: + # Validate comment length + comment_def = self.controller.get_comment_def() + if comment_def.minimum_chars_required: + char_count = len(self._comment_input.text()) + if char_count < comment_def.minimum_chars_required: + self._overlay_object.add_message( + "Please enter a comment of at least " + f"{comment_def.minimum_chars_required} characters", + message_type="error" + ) + self._invalidate_comment_field() + return False + return True + + def _invalidate_comment_field(self): + self._comment_invalid_timer.start() + self._comment_input.setStyleSheet( + "border-style: solid;" + "border-width: 1px;" + "border-color: #DD2020" + ) + # Set focus so user can start typing and is pointed towards the field + self._comment_input.setFocus() + + def _on_comment_invalid_timeout(self): + # Reset style + self._comment_input.setStyleSheet("") + def _set_footer_enabled(self, enabled): self._save_btn.setEnabled(True) self._reset_btn.setEnabled(True) diff --git a/server/settings/tools.py b/server/settings/tools.py index 6b07910454..2b33efaa8b 100644 --- a/server/settings/tools.py +++ b/server/settings/tools.py @@ -342,6 +342,14 @@ class CustomStagingDirProfileModel(BaseSettingsModel): class PublishToolModel(BaseSettingsModel): + comment_minimum_required_chars: int = SettingsField( + 0, + title="Publish comment minimum required characters", + description=( + "Minimum number of characters required in the comment field " + "before the publisher UI is allowed to continue publishing" + ) + ) template_name_profiles: list[PublishTemplateNameProfile] = SettingsField( default_factory=list, title="Template name profiles" @@ -570,6 +578,7 @@ DEFAULT_TOOLS_VALUES = { "product_type_filter_profiles": [] }, "publish": { + "comment_minimum_required_chars": 0, "template_name_profiles": [ { "product_types": [],