diff --git a/src/calibre/ebooks/metadata/book/base.py b/src/calibre/ebooks/metadata/book/base.py index 91ad613af8..c957015e67 100644 --- a/src/calibre/ebooks/metadata/book/base.py +++ b/src/calibre/ebooks/metadata/book/base.py @@ -885,3 +885,39 @@ def field_from_string(field, raw, field_metadata): if val is object: val = raw 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 diff --git a/src/calibre/gui2/dialogs/template_dialog.py b/src/calibre/gui2/dialogs/template_dialog.py index bc15ec77b8..27dee21d11 100644 --- a/src/calibre/gui2/dialogs/template_dialog.py +++ b/src/calibre/gui2/dialogs/template_dialog.py @@ -44,7 +44,7 @@ from qt.core import ( from calibre import sanitize_file_name 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.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 @@ -52,13 +52,12 @@ from calibre.gui2.dialogs.template_general_info import GeneralInformationDialog from calibre.gui2.widgets2 import Dialog, HTMLDisplay from calibre.library.coloring import color_row_key, displayable_columns 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.formatter import PythonTemplateContext, StopException from calibre.utils.formatter_functions import StoredObjectType, formatter_functions from calibre.utils.icu import lower as icu_lower 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 @@ -718,40 +717,11 @@ class TemplateDialog(QDialog, Ui_TemplateDialog): the contents of the field selectors for editing rules. ''' self.fm = fm - from calibre.gui2.ui import get_gui if mi: if not isinstance(mi, (tuple, list)): mi = (mi, ) else: - 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 - 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, ) + mi = (get_model_metadata_instance(), ) self.mi = mi tv = self.template_value tv.setColumnCount(3) diff --git a/src/calibre/utils/formatter.py b/src/calibre/utils/formatter.py index 2c22c377d0..2f911aef38 100644 --- a/src/calibre/utils/formatter.py +++ b/src/calibre/utils/formatter.py @@ -2025,17 +2025,22 @@ class TemplateFormatter(string.Formatter): self.restore_state(state) -class ValidateFormatter(TemplateFormatter): - ''' - Provides a formatter that substitutes the validation string for every value +class ValidateFormatter: ''' + 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): - return self._validation_string + It is a class instead of a function for compatibility reasons. + ''' def validate(self, template): - from calibre.ebooks.metadata.book.base import Metadata - return self.unsafe_format(template, {}, Metadata('')) + from calibre.gui2 import is_gui_thread + 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()