Make the validate_formatter use real instances of book metadata. This avoids type errors during validation in, for example, the Kobo driver..

This commit is contained in:
Charles Haley 2025-06-28 11:52:27 +01:00
parent da02180c4c
commit fabb3f2849
3 changed files with 51 additions and 40 deletions

View File

@ -885,3 +885,39 @@ def field_from_string(field, raw, field_metadata):
if val is object: if val is object:
val = raw val = raw
return val return val
def get_model_metadata_instance():
'''
Get a metadata instance that contains all the fields in the current database
with the fields to a plausible value. This function must only be used in
the GUI thread.
'''
from calibre.gui2 import is_gui_thread
if not is_gui_thread():
raise ValueError('get_model_metadata_instance() must only be used in the GUI thread')
mi = Metadata(_('Title'), [_('Author')])
mi.author_sort = _('Author Sort')
mi.series = ngettext('Series', 'Series', 1)
mi.series_index = 3
mi.rating = 4.0
mi.tags = [_('Tag 1'), _('Tag 2')]
mi.languages = ['eng']
mi.id = -1
from calibre.gui2.ui import get_gui
from calibre.utils.date import DEFAULT_DATE
fm = get_gui().current_db.new_api.field_metadata
mi.set_all_user_metadata(fm.custom_field_metadata())
for col in mi.get_all_user_metadata(False):
if fm[col]['datatype'] == 'datetime':
mi.set(col, DEFAULT_DATE)
elif fm[col]['datatype'] in ('int', 'float', 'rating'):
mi.set(col, 2)
elif fm[col]['datatype'] == 'bool':
mi.set(col, False)
elif fm[col]['is_multiple']:
mi.set(col, [col])
else:
mi.set(col, col, 1)
return mi

View File

@ -44,7 +44,7 @@ from qt.core import (
from calibre import sanitize_file_name from calibre import sanitize_file_name
from calibre.constants import config_dir, iswindows from calibre.constants import config_dir, iswindows
from calibre.ebooks.metadata.book.base import Metadata from calibre.ebooks.metadata.book.base import get_model_metadata_instance
from calibre.ebooks.metadata.book.formatter import SafeFormat from calibre.ebooks.metadata.book.formatter import SafeFormat
from calibre.gui2 import choose_files, choose_save_file, error_dialog, gprefs, info_dialog, pixmap_to_data, question_dialog, safe_open_url from calibre.gui2 import choose_files, choose_save_file, error_dialog, gprefs, info_dialog, pixmap_to_data, question_dialog, safe_open_url
from calibre.gui2.dialogs.template_dialog_ui import Ui_TemplateDialog from calibre.gui2.dialogs.template_dialog_ui import Ui_TemplateDialog
@ -52,13 +52,12 @@ from calibre.gui2.dialogs.template_general_info import GeneralInformationDialog
from calibre.gui2.widgets2 import Dialog, HTMLDisplay from calibre.gui2.widgets2 import Dialog, HTMLDisplay
from calibre.library.coloring import color_row_key, displayable_columns from calibre.library.coloring import color_row_key, displayable_columns
from calibre.utils.config_base import tweaks from calibre.utils.config_base import tweaks
from calibre.utils.date import DEFAULT_DATE
from calibre.utils.ffml_processor import MARKUP_ERROR, FFMLProcessor from calibre.utils.ffml_processor import MARKUP_ERROR, FFMLProcessor
from calibre.utils.formatter import PythonTemplateContext, StopException from calibre.utils.formatter import PythonTemplateContext, StopException
from calibre.utils.formatter_functions import StoredObjectType, formatter_functions from calibre.utils.formatter_functions import StoredObjectType, formatter_functions
from calibre.utils.icu import lower as icu_lower from calibre.utils.icu import lower as icu_lower
from calibre.utils.icu import sort_key from calibre.utils.icu import sort_key
from calibre.utils.localization import localize_user_manual_link, ngettext from calibre.utils.localization import localize_user_manual_link
from calibre.utils.resources import get_path as P from calibre.utils.resources import get_path as P
@ -718,40 +717,11 @@ class TemplateDialog(QDialog, Ui_TemplateDialog):
the contents of the field selectors for editing rules. the contents of the field selectors for editing rules.
''' '''
self.fm = fm self.fm = fm
from calibre.gui2.ui import get_gui
if mi: if mi:
if not isinstance(mi, (tuple, list)): if not isinstance(mi, (tuple, list)):
mi = (mi, ) mi = (mi, )
else: else:
mi = Metadata(_('Title'), [_('Author')]) mi = (get_model_metadata_instance(), )
mi.author_sort = _('Author Sort')
mi.series = ngettext('Series', 'Series', 1)
mi.series_index = 3
mi.rating = 4.0
mi.tags = [_('Tag 1'), _('Tag 2')]
mi.languages = ['eng']
mi.id = -1
if self.fm is not None:
mi.set_all_user_metadata(self.fm.custom_field_metadata())
else:
# No field metadata. Grab a copy from the current library so
# that we can validate any custom column names. The values for
# the columns will all be empty, which in some very unusual
# cases might cause formatter errors. We can live with that.
fm = get_gui().current_db.new_api.field_metadata
mi.set_all_user_metadata(fm.custom_field_metadata())
for col in mi.get_all_user_metadata(False):
if fm[col]['datatype'] == 'datetime':
mi.set(col, DEFAULT_DATE)
elif fm[col]['datatype'] in ('int', 'float', 'rating'):
mi.set(col, 2)
elif fm[col]['datatype'] == 'bool':
mi.set(col, False)
elif fm[col]['is_multiple']:
mi.set(col, [col])
else:
mi.set(col, col, 1)
mi = (mi, )
self.mi = mi self.mi = mi
tv = self.template_value tv = self.template_value
tv.setColumnCount(3) tv.setColumnCount(3)

View File

@ -2025,17 +2025,22 @@ class TemplateFormatter(string.Formatter):
self.restore_state(state) self.restore_state(state)
class ValidateFormatter(TemplateFormatter): class ValidateFormatter:
'''
Provides a formatter that substitutes the validation string for every value
''' '''
Provides a formatter that uses a fake book. This class must be used only
in the GUI thread.
def get_value(self, key, args, kwargs): It is a class instead of a function for compatibility reasons.
return self._validation_string '''
def validate(self, template): def validate(self, template):
from calibre.ebooks.metadata.book.base import Metadata from calibre.gui2 import is_gui_thread
return self.unsafe_format(template, {}, Metadata('')) if not is_gui_thread():
raise ValueError('A ValidateFormatter must only be used in the GUI thread')
from calibre.ebooks.metadata.book.base import get_model_metadata_instance
from calibre.ebooks.metadata.book.formatter import SafeFormat
return SafeFormat().unsafe_format(template, {}, get_model_metadata_instance())
validation_formatter = ValidateFormatter() validation_formatter = ValidateFormatter()