fix: fix various issues with adding templates, reduce reused code

This commit is contained in:
Travis Abendshien
2026-06-24 17:56:32 -07:00
parent 1db6f716ff
commit c88cfc9968
10 changed files with 73 additions and 85 deletions

View File

@@ -1314,22 +1314,23 @@ class Library:
return direct_tags, descendant_tags
def add_field_template(self, field_template: BaseFieldTemplate) -> bool:
def add_field_template(self, field_template: BaseFieldTemplate) -> BaseFieldTemplate | None:
"""Add a new field template to the library."""
if not (isinstance(field_template, (TextFieldTemplate, DatetimeFieldTemplate))):
logger.error("[Library] BaseFieldTemplate attempted to be added to the library.")
return False
return None
with Session(self.engine) as session:
try:
session.add(field_template)
session.flush()
make_transient(field_template)
session.commit()
return field_template
except IntegrityError as e:
logger.error(e)
session.rollback()
return False
return True
return None
def update_field_template(self, old_field_type: str, field_template: BaseFieldTemplate) -> bool:
"""Update a field template in the library.

View File

@@ -24,7 +24,7 @@ class EditFieldTemplateModal(EditFieldTemplateModalView):
def __init__(self, field_template: BaseFieldTemplate | None = None) -> None:
super().__init__()
self.__field_id: int = field_template.id if field_template else -1
self.__field_id: int | None = field_template.id if field_template else None
self.__field_name: str = ""
self.__field_type: str | None = field_template.class_name if field_template else None
self.__text_field_is_multiline: bool = False
@@ -47,7 +47,7 @@ class EditFieldTemplateModal(EditFieldTemplateModalView):
# Indicates a new template, set default values
if field_template is None:
self.__field_name = Translations["field_template.new"]
self.__field_type = None
self.__field_type = list(EditFieldTemplateModal.field_type_map.keys())[0] # First index
return
# Populate common values for any field type
else:

View File

@@ -69,9 +69,32 @@ class FieldTemplateSearchPanel(SearchPanel[BaseFieldTemplate]):
return len(self.__lib.field_templates)
@override
def on_item_create(self) -> None:
# TODO: Allow creation of field templates
pass
def on_item_create(self, add_to_entry: bool = False) -> None:
"""Opens panel to create a new field template and optionally add it to an entry.
Populates name field using current search query.
Args:
add_to_entry (bool): Should this item be added to currently selected entries?
"""
query: str = self.get_search_query()
logger.info("[FieldTemplateSearch] Create and Add Field Template", name=query)
panel: EditFieldTemplateModal = EditFieldTemplateModal()
modal: PanelModal = PanelModal(
panel,
Translations["field_template.new"],
Translations["field_template.add"]
if add_to_entry
else Translations["field_template.new"],
has_save=True,
)
if query.strip():
panel.name_field.setText(query)
modal.saved.connect(lambda: self.create_item(panel, choose_item=add_to_entry))
modal.show()
@override
def on_item_edit(self, item: BaseFieldTemplate) -> None:
@@ -89,6 +112,8 @@ class FieldTemplateSearchPanel(SearchPanel[BaseFieldTemplate]):
@override
def _on_item_remove(self, item: BaseFieldTemplate) -> None:
if self.is_chooser:
return
message_box = QMessageBox(
QMessageBox.Icon.Question,
@@ -105,29 +130,6 @@ class FieldTemplateSearchPanel(SearchPanel[BaseFieldTemplate]):
self.__lib.remove_field_template(item)
self.update_items(self.get_search_query())
@override
def on_item_create_and_add(self) -> None:
"""Opens "Create Field Template" panel to create a new field template.
Populates name field using current search query.
"""
query: str = self.get_search_query()
logger.info("[FieldTemplateSearch] Create and Add Field Template", name=query)
panel: EditFieldTemplateModal = EditFieldTemplateModal()
modal: PanelModal = PanelModal(
panel,
Translations["field_template.new"],
Translations["field.add"],
has_save=True,
)
if query.strip():
panel.name_field.setText(query)
modal.saved.connect(lambda: self.create_item(panel, choose_item=True))
modal.show()
@override
def _on_item_chosen(self, item: BaseFieldTemplate) -> None:
self.field_template_chosen.emit(item)
@@ -146,6 +148,8 @@ class FieldTemplateSearchPanel(SearchPanel[BaseFieldTemplate]):
if item is None:
return
field_template_widget.has_remove = not self.is_chooser
# Disconnect previous callbacks
with catch_warnings(record=True):
field_template_widget.on_edit.disconnect()

View File

@@ -16,6 +16,7 @@ class FieldTemplateWidget(FieldTemplateWidgetView):
super().__init__()
self.__field_template: BaseFieldTemplate | None = None
self.has_remove: bool = False
# Add actions
edit_action = QAction(self)
@@ -36,12 +37,14 @@ class FieldTemplateWidget(FieldTemplateWidgetView):
@override
def enterEvent(self, event: QEnterEvent) -> None:
self._delete_button.setHidden(False)
if self.has_remove:
self._delete_button.setHidden(False)
self.update()
return super().enterEvent(event)
@override
def leaveEvent(self, event: QEvent) -> None:
self._delete_button.setHidden(True)
if self.has_remove:
self._delete_button.setHidden(True)
self.update()
return super().leaveEvent(event)

View File

@@ -42,11 +42,11 @@ class PreviewPanel(PreviewPanelView):
self.__add_tag_modal.tsp.item_chosen.connect(self._add_tag_to_selected)
def _add_field_to_selected(self, template: BaseFieldTemplate) -> None:
self._fields.add_field_to_selected(template)
self._containers.add_field_to_selected(template)
if len(self._selected) == 1:
self._fields.update_from_entry(self._selected[0])
self._containers.update_from_entry(self._selected[0])
def _add_tag_to_selected(self, tag_id: int) -> None:
self._fields.add_tags_to_selected(tag_id)
self._containers.add_tags_to_selected(tag_id)
if len(self._selected) == 1:
self._fields.update_from_entry(self._selected[0])
self._containers.update_from_entry(self._selected[0])

View File

@@ -136,14 +136,14 @@ class SearchPanel[T](PanelWidget):
# Create and add item if no search results
if len(self._search_results) <= 0:
self.on_item_create_and_add()
self.on_item_create(add_to_entry=True)
elif self.is_chooser:
self._on_item_chosen(self._search_results[0])
self.clear_search_query()
self.update_items()
def on_item_create(self) -> None:
def on_item_create(self, add_to_entry: bool = False) -> None: # pyright: ignore[reportUnusedParameter]
raise NotImplementedError()
def on_item_edit(self, item: T) -> None: # pyright: ignore[reportUnusedParameter]
@@ -152,9 +152,6 @@ class SearchPanel[T](PanelWidget):
def _on_item_remove(self, item: T) -> None: # pyright: ignore[reportUnusedParameter]
raise NotImplementedError()
def on_item_create_and_add(self) -> None:
raise NotImplementedError()
def _on_item_chosen(self, item: T) -> None: # pyright: ignore[reportUnusedParameter]
raise NotImplementedError()

View File

@@ -76,7 +76,14 @@ class TagSearchPanel(SearchPanel[Tag]):
return len(self.__lib.tags)
@override
def on_item_create(self) -> None:
def on_item_create(self, add_to_entry: bool = False) -> None:
"""Opens panel to create a new tag and optionally add it to an entry.
Populates name field using current search query.
Args:
add_to_entry (bool): Should this item be added to currently selected entries?
"""
# TODO: Move this to a top-level import
from tagstudio.qt.mixed.build_tag import BuildTagPanel # here due to circular imports
@@ -86,13 +93,14 @@ class TagSearchPanel(SearchPanel[Tag]):
modal: PanelModal = PanelModal(
panel,
Translations["tag.new"],
Translations["tag.add"] if add_to_entry else Translations["tag.new"],
has_save=True,
)
if query.strip():
panel.name_field.setText(query)
modal.saved.connect(lambda: self.create_item(panel))
modal.saved.connect(lambda: self.create_item(panel, choose_item=add_to_entry))
modal.show()
@override
@@ -134,33 +142,6 @@ class TagSearchPanel(SearchPanel[Tag]):
self.__lib.remove_tag(item.id)
self.update_items(self.get_search_query())
@override
def on_item_create_and_add(self) -> None:
"""Opens "Create Tag" panel to create and add a new tag.
Populates name field using current search query.
"""
# TODO: Move this to a top-level import
from tagstudio.qt.mixed.build_tag import BuildTagPanel # here due to circular imports
query: str = self.get_search_query()
logger.info("Create and Add Tag", name=query)
panel: BuildTagPanel = BuildTagPanel(self.__lib)
modal: PanelModal = PanelModal(
panel,
Translations["tag.new"],
Translations["tag.add"],
has_save=True,
)
if query.strip():
panel.name_field.setText(query)
modal.saved.connect(lambda: self.create_item(panel, choose_item=True))
modal.show()
@override
def _on_item_chosen(self, item: Tag) -> None:
self.item_chosen.emit(item.id)

View File

@@ -233,19 +233,19 @@ class FieldContainers(QWidget):
)
self.lib.add_field_to_entries(entry_id, field_template.to_field())
def add_tags_to_selected(self, tags: int | list[int]) -> None:
def add_tags_to_selected(self, tag_ids: int | list[int]) -> None:
"""Add list of tags to one or more selected items.
Uses the current driver selection, NOT the field containers cache.
"""
if isinstance(tags, int):
tags = [tags]
if isinstance(tag_ids, int):
tag_ids = [tag_ids]
logger.info(
"[FieldContainers][add_tags_to_selected]",
selected=self.driver.selected,
tags=tags,
tag_ids=tag_ids,
)
self.driver.add_tags_to_selected_callback(tags)
self.driver.add_tags_to_selected_callback(tag_ids)
def write_container(self, index: int, field: BaseField, is_mixed: bool = False) -> None:
"""Update/Create data for a FieldContainer.

View File

@@ -67,7 +67,7 @@ class PreviewPanelView(QWidget):
self.__thumb = PreviewThumb(self.lib, driver)
self.__file_attrs = FileAttributes(self.lib, driver)
self._fields = FieldContainers(
self._containers = FieldContainers(
self.lib, driver
) # TODO: this should be name mangled, but is still needed on the controller side atm
@@ -107,7 +107,7 @@ class PreviewPanelView(QWidget):
preview_layout.addWidget(self.__thumb)
info_layout.addWidget(self.__file_attrs)
info_layout.addWidget(self._fields)
info_layout.addWidget(self._containers)
splitter.addWidget(preview_section)
splitter.addWidget(info_section)
@@ -148,7 +148,7 @@ class PreviewPanelView(QWidget):
self.__thumb.hide_preview()
self.__file_attrs.update_stats()
self.__file_attrs.update_date_label()
self._fields.hide_containers()
self._containers.hide_containers()
self.add_buttons_enabled = False
@@ -163,7 +163,7 @@ class PreviewPanelView(QWidget):
stats: FileAttributeData = self.__thumb.display_file(filepath)
self.__file_attrs.update_stats(filepath, stats)
self.__file_attrs.update_date_label(filepath)
self._fields.update_from_entry(entry_id)
self._containers.update_from_entry(entry_id)
self._set_selection_callback()
@@ -175,7 +175,7 @@ class PreviewPanelView(QWidget):
self.__thumb.hide_preview() # TODO: Render mixed selection
self.__file_attrs.update_multi_selection(len(selected))
self.__file_attrs.update_date_label()
self._fields.hide_containers() # TODO: Allow for mixed editing
self._containers.hide_containers() # TODO: Allow for mixed editing
self._set_selection_callback()
@@ -205,7 +205,7 @@ class PreviewPanelView(QWidget):
@property
def field_containers_widget(self) -> FieldContainers: # needed for the tests
"""Getter for the field containers widget."""
return self._fields
return self._containers
@property
def preview_thumb(self) -> PreviewThumb:

View File

@@ -139,7 +139,9 @@ class SearchPanelView(PanelWidget):
)
self.create_button.clicked.connect(controller.on_item_create)
self.create_and_add_button.clicked.connect(controller.on_item_create_and_add)
self.create_and_add_button.clicked.connect(
lambda: controller.on_item_create(add_to_entry=True)
)
def set_limit_items(self, limit_items: list[tuple[str, int]]) -> None:
# Remove existing limit items