This commit is contained in:
Kovid Goyal 2025-01-21 18:39:35 +05:30
commit abaf03984d
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
7 changed files with 85 additions and 43 deletions

View File

@ -739,8 +739,10 @@ To choose icons for values in categories, right-click on a value then choose `Ma
* ``category``: the lookup name of the category, for example ``authors``, ``series``, ``#mycolumn``. * ``category``: the lookup name of the category, for example ``authors``, ``series``, ``#mycolumn``.
* ``value``: the value of the item within the category. * ``value``: the value of the item within the category.
* ``count``: the number of books with this value. If the value is part of a hierarchy then the count includes the children.
* ``avg_rating``: the average rating for books with this value. If the value is part of a hierarchy then the average includes the children.
Book metadata such as title is not available. Book metadata such as title is not available. Template database functions such as book_count() and book_values() will work, but the performance might not be acceptable. Python templates have full access to the calibre database API.
For example, this template specifies that any value in the clicked-on category beginning with `History` will have an icon named ``flower.png``:: For example, this template specifies that any value in the clicked-on category beginning with `History` will have an icon named ``flower.png``::

View File

@ -1083,6 +1083,8 @@ def evaluate(book, context):
tv = self.template_value tv = self.template_value
l = self.template_value.selectionModel().selectedRows() l = self.template_value.selectionModel().selectedRows()
break_on_mi = 0 if len(l) == 0 else l[0].row() break_on_mi = 0 if len(l) == 0 else l[0].row()
from calibre.gui2.ui import get_gui
db = get_gui().current_db
for r,mi in enumerate(self.mi): for r,mi in enumerate(self.mi):
w = tv.cellWidget(r, 0) w = tv.cellWidget(r, 0)
w.setText(mi.get('title', _('No title provided'))) w.setText(mi.get('title', _('No title provided')))
@ -1096,7 +1098,8 @@ def evaluate(book, context):
mi, global_vars=self.global_vars, mi, global_vars=self.global_vars,
template_functions=self.all_functions, template_functions=self.all_functions,
break_reporter=self.break_reporter if r == break_on_mi else None, break_reporter=self.break_reporter if r == break_on_mi else None,
python_context_object=self.python_context_object) python_context_object=self.python_context_object,
database=db)
w = tv.cellWidget(r, 2) w = tv.cellWidget(r, 2)
w.setText(v.translate(translate_table)) w.setText(v.translate(translate_table))
w.setCursorPosition(0) w.setCursorPosition(0)

View File

@ -55,6 +55,7 @@ class TagTreeItem: # {{{
icon_config_dir = {} icon_config_dir = {}
file_icon_provider = None file_icon_provider = None
eval_formatter = EvalFormatter() eval_formatter = EvalFormatter()
database = None
def __init__(self, data=None, is_category=False, icon_map=None, def __init__(self, data=None, is_category=False, icon_map=None,
parent=None, tooltip=None, category_key=None, temporary=False, parent=None, tooltip=None, category_key=None, temporary=False,
@ -143,9 +144,12 @@ class TagTreeItem: # {{{
if node.type != self.TAG or node.type == self.ROOT: if node.type != self.TAG or node.type == self.ROOT:
break break
if val_icon is None and TEMPLATE_ICON_INDICATOR in self.value_icons[category]: if val_icon is None and TEMPLATE_ICON_INDICATOR in self.value_icons[category]:
v = {'category': category, 'value': self.tag.original_name,
'count': getattr(self.tag, 'count', ''),
'avg_rating': getattr(self.tag, 'avg_rating', '')}
t = self.eval_formatter.safe_format(self.value_icons[category][TEMPLATE_ICON_INDICATOR][0], t = self.eval_formatter.safe_format(self.value_icons[category][TEMPLATE_ICON_INDICATOR][0],
{'category': category, 'value': self.tag.original_name}, v, 'VALUE_ICON_TEMPLATE_ERROR', {},
'VALUE_ICON_TEMPLATE_ERROR', {}) database=self.database)
if t: if t:
val_icon = (os.path.join('template_icons', t), False) val_icon = (os.path.join('template_icons', t), False)
else: else:
@ -406,8 +410,8 @@ class TagsModel(QAbstractItemModel): # {{{
self.filter_categories_by = None self.filter_categories_by = None
self.collapse_model = 'disable' self.collapse_model = 'disable'
self.row_map = [] self.row_map = []
self.root_item = self.create_node(icon_map=self.icon_state_map)
self.db = None self.db = None
self.root_item = self.create_node(icon_map=self.icon_state_map)
self._build_in_progress = False self._build_in_progress = False
self.reread_collapse_model({}, rebuild=False) self.reread_collapse_model({}, rebuild=False)
self.show_error_after_event_loop_tick_signal.connect(self.on_show_error_after_event_loop_tick, type=Qt.ConnectionType.QueuedConnection) self.show_error_after_event_loop_tick_signal.connect(self.on_show_error_after_event_loop_tick, type=Qt.ConnectionType.QueuedConnection)
@ -1483,6 +1487,7 @@ class TagsModel(QAbstractItemModel): # {{{
node.value_icons = self.value_icons node.value_icons = self.value_icons
node.value_icon_cache = self.value_icon_cache node.value_icon_cache = self.value_icon_cache
node.icon_config_dir = self.icon_config_dir node.icon_config_dir = self.icon_config_dir
node.database = self.db
return node return node
def get_node(self, idx): def get_node(self, idx):

View File

@ -686,14 +686,21 @@ class TagsView(QTreeView): # {{{
if action == 'set_icon': if action == 'set_icon':
if category is None: if category is None:
if index is not None: if index is not None:
current_item = self._model.get_node(index).tag.original_name tag = self._model.get_node(index).tag
current_item = tag.original_name
count = tag.count
avg_rating = tag.avg_rating
else: else:
current_item = _('No value available') current_item = _('No value available')
count = ''
avg_rating = ''
template = self._model.value_icons.get(key, {}).get(TEMPLATE_ICON_INDICATOR, ('', False))[0] template = self._model.value_icons.get(key, {}).get(TEMPLATE_ICON_INDICATOR, ('', False))[0]
from calibre.gui2.dialogs.template_dialog import TemplateDialog from calibre.gui2.dialogs.template_dialog import TemplateDialog
from calibre.utils.formatter import EvalFormatter from calibre.utils.formatter import EvalFormatter
v = {'title': key, 'category': key, 'value': current_item,
'count': count, 'avg_rating': avg_rating}
d = TemplateDialog(parent=self, text=template, d = TemplateDialog(parent=self, text=template,
mi={'title': key, 'category': key, 'value': current_item}, mi=v,
doing_emblem=True, doing_emblem=True,
# fm=None, color_field=None, icon_field_key=None, # fm=None, color_field=None, icon_field_key=None,
# icon_rule_kind=None, text_is_placeholder=False, # icon_rule_kind=None, text_is_placeholder=False,

View File

@ -166,7 +166,8 @@ def get_gpref(name: str, defval = None):
return gprefs.get(name, defval) return gprefs.get(name, defval)
def get_icon_for_node(node, parent, node_to_tag_map, tag_map, eval_formatter): def get_icon_for_node(node, parent, node_to_tag_map, tag_map, eval_formatter, db):
# This needs a legacy database so legacy formatter functions work
category = node['category'] category = node['category']
if category in ('search', 'formats') or category.startswith('@'): if category in ('search', 'formats') or category.startswith('@'):
return return
@ -190,10 +191,13 @@ def get_icon_for_node(node, parent, node_to_tag_map, tag_map, eval_formatter):
if val_icon is not None and for_children: if val_icon is not None and for_children:
break break
par = pd par = pd
val_icon = None
if val_icon is None and TEMPLATE_ICON_INDICATOR in value_icons.get(category, {}): if val_icon is None and TEMPLATE_ICON_INDICATOR in value_icons.get(category, {}):
v = {'category': category, 'value': name_for_icon(node),
'count': node.get('count', ''), 'avg_rating': node.get('avg_rating', '')}
t = eval_formatter.safe_format( t = eval_formatter.safe_format(
value_icons[category][TEMPLATE_ICON_INDICATOR][0], {'category': category, 'value': name_for_icon(node)}, value_icons[category][TEMPLATE_ICON_INDICATOR][0], v,
'VALUE_ICON_TEMPLATE_ERROR', {}) 'VALUE_ICON_TEMPLATE_ERROR', {}, database=db)
if t: if t:
# Use POSIX path separator # Use POSIX path separator
val_icon = 'template_icons/' + t val_icon = 'template_icons/' + t
@ -428,7 +432,7 @@ def collapse_first_letter(collapse_nodes, items, category_node, cl_list, idx, is
def process_category_node( def process_category_node(
category_node, items, category_data, eval_formatter, field_metadata, category_node, items, category_data, eval_formatter, field_metadata,
opts, tag_map, hierarchical_tags, node_to_tag_map, collapse_nodes, opts, tag_map, hierarchical_tags, node_to_tag_map, collapse_nodes,
intermediate_nodes, hierarchical_items): intermediate_nodes, hierarchical_items, db):
category = items[category_node['id']]['category'] category = items[category_node['id']]['category']
if category not in category_data: if category not in category_data:
# This can happen for user categories that are hierarchical and missing their parent. # This can happen for user categories that are hierarchical and missing their parent.
@ -469,7 +473,7 @@ def process_category_node(
node = {'id':node_id, 'children':[]} node = {'id':node_id, 'children':[]}
parent['children'].append(node) parent['children'].append(node)
try: try:
get_icon_for_node(node_data, parent, node_to_tag_map, tag_map, eval_formatter) get_icon_for_node(node_data, parent, node_to_tag_map, tag_map, eval_formatter, db)
except Exception: except Exception:
import traceback import traceback
traceback.print_exc() traceback.print_exc()
@ -555,7 +559,7 @@ def iternode_descendants(node):
yield from iternode_descendants(child) yield from iternode_descendants(child)
def fillout_tree(root, items, node_id_map, category_nodes, category_data, field_metadata, opts, book_rating_map): def fillout_tree(root, items, node_id_map, category_nodes, category_data, field_metadata, opts, book_rating_map, db):
eval_formatter = EvalFormatter() eval_formatter = EvalFormatter()
tag_map, hierarchical_tags, node_to_tag_map = {}, set(), {} tag_map, hierarchical_tags, node_to_tag_map = {}, set(), {}
first, later, collapse_nodes, intermediate_nodes, hierarchical_items = [], [], [], {}, set() first, later, collapse_nodes, intermediate_nodes, hierarchical_items = [], [], [], {}, set()
@ -572,7 +576,7 @@ def fillout_tree(root, items, node_id_map, category_nodes, category_data, field_
process_category_node( process_category_node(
cnode, items, category_data, eval_formatter, field_metadata, cnode, items, category_data, eval_formatter, field_metadata,
opts, tag_map, hierarchical_tags, node_to_tag_map, opts, tag_map, hierarchical_tags, node_to_tag_map,
collapse_nodes, intermediate_nodes, hierarchical_items) collapse_nodes, intermediate_nodes, hierarchical_items, db)
# Do not store id_set in the tag items as it is a lot of data, with not # Do not store id_set in the tag items as it is a lot of data, with not
# much use. Instead only update the ratings and counts based on id_set # much use. Instead only update the ratings and counts based on id_set
@ -600,7 +604,7 @@ def render_categories(opts, db, category_data):
items = {} items = {}
with db.safe_read_lock: with db.safe_read_lock:
root, node_id_map, category_nodes, recount_nodes = create_toplevel_tree(category_data, items, db.field_metadata, opts, db) root, node_id_map, category_nodes, recount_nodes = create_toplevel_tree(category_data, items, db.field_metadata, opts, db)
fillout_tree(root, items, node_id_map, category_nodes, category_data, db.field_metadata, opts, db.fields['rating'].book_value_map) fillout_tree(root, items, node_id_map, category_nodes, category_data, db.field_metadata, opts, db.fields['rating'].book_value_map, db)
for node in recount_nodes: for node in recount_nodes:
item = items[node['id']] item = items[node['id']]
item['count'] = sum(1 for x in iternode_descendants(node) if not items[x['id']].get('is_category', False)) item['count'] = sum(1 for x in iternode_descendants(node) if not items[x['id']].get('is_category', False))

View File

@ -1651,6 +1651,7 @@ class TemplateFormatter(string.Formatter):
self.recursion_level = -1 self.recursion_level = -1
self._caller = None self._caller = None
self.python_context_object = None self.python_context_object = None
self.database = None
def _do_format(self, val, fmt): def _do_format(self, val, fmt):
if not fmt or not val: if not fmt or not val:
@ -1759,7 +1760,8 @@ class TemplateFormatter(string.Formatter):
def _run_python_template(self, compiled_template, arguments): def _run_python_template(self, compiled_template, arguments):
try: try:
self.python_context_object.set_values( self.python_context_object.set_values(
db=get_database(self.book, get_database(self.book, None)), db=(self.database if self.database is not None
else get_database(self.book, get_database(self.book, None))),
globals=self.global_vars, globals=self.global_vars,
arguments=arguments, arguments=arguments,
formatter=self, formatter=self,
@ -1914,7 +1916,8 @@ class TemplateFormatter(string.Formatter):
self.funcs, self.funcs,
self.locals, self.locals,
self._caller, self._caller,
self.python_context_object)) self.python_context_object,
self.database))
def restore_state(self, state): def restore_state(self, state):
self.recursion_level -= 1 self.recursion_level -= 1
@ -1929,7 +1932,8 @@ class TemplateFormatter(string.Formatter):
self.funcs, self.funcs,
self.locals, self.locals,
self._caller, self._caller,
self.python_context_object) = state self.python_context_object,
self.database) = state
# Allocate an interpreter if the formatter encounters a GPM or TPM template. # Allocate an interpreter if the formatter encounters a GPM or TPM template.
# We need to allocate additional interpreters if there is composite recursion # We need to allocate additional interpreters if there is composite recursion
@ -1980,12 +1984,13 @@ class TemplateFormatter(string.Formatter):
column_name=None, template_cache=None, column_name=None, template_cache=None,
strip_results=True, template_functions=None, strip_results=True, template_functions=None,
global_vars=None, break_reporter=None, global_vars=None, break_reporter=None,
python_context_object=None): python_context_object=None, database=None):
state = self.save_state() state = self.save_state()
if self.recursion_level == 0: if self.recursion_level == 0:
# Initialize the composite values dict if this is the base-level # Initialize the composite values dict and database if this is the
# call. Recursive calls will use the same dict. # base-level call. Recursive calls will use the same dict.
self.composite_values = {} self.composite_values = {}
self.database = database
try: try:
self._caller = FormatterFuncsCaller(self) self._caller = FormatterFuncsCaller(self)
self.strip_results = strip_results self.strip_results = strip_results

View File

@ -214,7 +214,7 @@ def get_database(mi, name):
wr = getattr(cache, 'library_database_instance', None) wr = getattr(cache, 'library_database_instance', None)
if wr is None: if wr is None:
if name is not None: if name is not None:
only_in_gui_error() only_in_gui_error(name)
return None return None
db = wr() db = wr()
if db is None: if db is None:
@ -248,7 +248,12 @@ class FormatterFunction:
def only_in_gui_error(self): def only_in_gui_error(self):
only_in_gui_error(self.name) only_in_gui_error(self.name)
def get_database(self, mi): def get_database(self, mi, formatter=None):
if formatter is not None:
if hasattr(formatter, 'database'):
db = formatter.database
if db is not None:
return db
return get_database(mi, self.name) return get_database(mi, self.name)
@ -1680,7 +1685,7 @@ attached to the current book.[/] This function works only in the GUI.
''') ''')
def evaluate(self, formatter, kwargs, mi, locals): def evaluate(self, formatter, kwargs, mi, locals):
c = self.get_database(mi).new_api.annotation_count_for_book(mi.id) c = self.get_database(mi, formatter=formatter).new_api.annotation_count_for_book(mi.id)
return '' if c == 0 else str(c) return '' if c == 0 else str(c)
@ -1697,7 +1702,7 @@ not marked. This function works only in the GUI.
''') ''')
def evaluate(self, formatter, kwargs, mi, locals): def evaluate(self, formatter, kwargs, mi, locals):
c = self.get_database(mi).data.get_marked(mi.id) c = self.get_database(mi, formatter=formatter).data.get_marked(mi.id)
return c if c else '' return c if c else ''
@ -2342,11 +2347,12 @@ r'''
contain this book.[/] This function works only in the GUI. If you want to use these contain this book.[/] This function works only in the GUI. If you want to use these
values in save-to-disk or send-to-device templates then you must make a custom values in save-to-disk or send-to-device templates then you must make a custom
"Column built from other columns", use the function in that column's template, "Column built from other columns", use the function in that column's template,
and use that column's value in your save/send templates. and use that column's value in your save/send templates. This function works
only in the GUI.
''') ''')
def evaluate(self, formatter, kwargs, mi, locals_): def evaluate(self, formatter, kwargs, mi, locals_):
db = self.get_database(mi) db = self.get_database(mi, formatter=formatter)
try: try:
a = db.data.get_virtual_libraries_for_books((mi.id,)) a = db.data.get_virtual_libraries_for_books((mi.id,))
return ', '.join(a[mi.id]) return ', '.join(a[mi.id])
@ -2370,7 +2376,7 @@ This function works only in the GUI.
''') ''')
def evaluate(self, formatter, kwargs, mi, locals): def evaluate(self, formatter, kwargs, mi, locals):
return self.get_database(mi).data.get_base_restriction_name() return self.get_database(mi, formatter=formatter).data.get_base_restriction_name()
class BuiltinUserCategories(BuiltinFormatterFunction): class BuiltinUserCategories(BuiltinFormatterFunction):
@ -2438,10 +2444,11 @@ program:
ans ans
[/CODE] [/CODE]
[/LIST] [/LIST]
This function works only in the GUI.
''') ''')
def evaluate(self, formatter, kwargs, mi, locals, field_name, field_value): def evaluate(self, formatter, kwargs, mi, locals, field_name, field_value):
db = self.get_database(mi).new_api db = self.get_database(mi, formatter=formatter).new_api
try: try:
link = None link = None
item_id = db.get_item_id(field_name, field_value, case_sensitive=True) item_id = db.get_item_id(field_name, field_value, case_sensitive=True)
@ -2602,7 +2609,7 @@ More than one of ``is_undefined``, ``is_false``, or ``is_true`` can be set to 1.
def evaluate(self, formatter, kwargs, mi, locals, field, is_undefined, is_false, is_true): def evaluate(self, formatter, kwargs, mi, locals, field, is_undefined, is_false, is_true):
# 'field' is a lookup name, not a value # 'field' is a lookup name, not a value
if field not in self.get_database(mi).field_metadata: if field not in self.get_database(mi, formatter=formatter).field_metadata:
raise ValueError(_("The column {} doesn't exist").format(field)) raise ValueError(_("The column {} doesn't exist").format(field))
res = getattr(mi, field, None) res = getattr(mi, field, None)
if res is None: if res is None:
@ -2858,6 +2865,7 @@ Using a stored template instead of putting the template into the search
eliminates problems caused by the requirement to escape quotes in search eliminates problems caused by the requirement to escape quotes in search
expressions. expressions.
[/LIST] [/LIST]
This function can be used only in the GUI.
''') ''')
def evaluate(self, formatter, kwargs, mi, locals, query, use_vl): def evaluate(self, formatter, kwargs, mi, locals, query, use_vl):
@ -2865,10 +2873,15 @@ expressions.
if (not tweaks.get('allow_template_database_functions_in_composites', False) and if (not tweaks.get('allow_template_database_functions_in_composites', False) and
formatter.global_vars.get(rendering_composite_name, None)): formatter.global_vars.get(rendering_composite_name, None)):
raise ValueError(_('The book_count() function cannot be used in a composite column')) raise ValueError(_('The book_count() function cannot be used in a composite column'))
db = self.get_database(mi) db = self.get_database(mi, formatter=formatter)
try: try:
ids = db.search_getting_ids(query, None, use_virtual_library=use_vl != '0') if use_vl == '0':
return len(ids) # use the new_api search that doesn't use virtual libraries to let
# the function work in content server icon rules.
ids = db.new_api.search(query, None)
else:
ids = db.search_getting_ids(query, None, use_virtual_library=True)
return str(len(ids))
except Exception: except Exception:
traceback.print_exc() traceback.print_exc()
@ -2886,8 +2899,8 @@ then virtual libraries are ignored. This function and its companion
``book_count()`` are particularly useful in template searches, supporting ``book_count()`` are particularly useful in template searches, supporting
searches that combine information from many books such as looking for series searches that combine information from many books such as looking for series
with only one book. It cannot be used in composite columns unless the tweak with only one book. It cannot be used in composite columns unless the tweak
``allow_template_database_functions_in_composites`` is set to True. It can be ``allow_template_database_functions_in_composites`` is set to True. This function
used only in the GUI. can be used only in the GUI.
''') ''')
def evaluate(self, formatter, kwargs, mi, locals, column, query, sep, use_vl): def evaluate(self, formatter, kwargs, mi, locals, column, query, sep, use_vl):
@ -2895,11 +2908,14 @@ used only in the GUI.
if (not tweaks.get('allow_template_database_functions_in_composites', False) and if (not tweaks.get('allow_template_database_functions_in_composites', False) and
formatter.global_vars.get(rendering_composite_name, None)): formatter.global_vars.get(rendering_composite_name, None)):
raise ValueError(_('The book_values() function cannot be used in a composite column')) raise ValueError(_('The book_values() function cannot be used in a composite column'))
db = self.get_database(mi) db = self.get_database(mi, formatter=formatter)
if column not in db.field_metadata: if column not in db.field_metadata:
raise ValueError(_("The column {} doesn't exist").format(column)) raise ValueError(_("The column {} doesn't exist").format(column))
try: try:
ids = db.search_getting_ids(query, None, use_virtual_library=use_vl != '0') if use_vl == '0':
ids = db.new_api.search(query, None)
else:
ids = db.search_getting_ids(query, None, use_virtual_library=True)
s = set() s = set()
for id_ in ids: for id_ in ids:
f = db.new_api.get_proxy_metadata(id_).get(column, None) f = db.new_api.get_proxy_metadata(id_).get(column, None)
@ -2930,7 +2946,7 @@ This function can be used only in the GUI.
if len(args) > 1: if len(args) > 1:
raise ValueError(_('Incorrect number of arguments for function {0}').format('has_extra_files')) raise ValueError(_('Incorrect number of arguments for function {0}').format('has_extra_files'))
pattern = args[0] if len(args) == 1 else None pattern = args[0] if len(args) == 1 else None
db = self.get_database(mi).new_api db = self.get_database(mi, formatter=formatter).new_api
try: try:
files = tuple(f.relpath.partition('/')[-1] for f in files = tuple(f.relpath.partition('/')[-1] for f in
db.list_extra_files(mi.id, use_cache=True, pattern=DATA_FILE_PATTERN)) db.list_extra_files(mi.id, use_cache=True, pattern=DATA_FILE_PATTERN))
@ -2961,7 +2977,7 @@ the functions :ref:`has_extra_files`, :ref:`extra_file_modtime` and
if len(args) > 1: if len(args) > 1:
raise ValueError(_('Incorrect number of arguments for function {0}').format('has_extra_files')) raise ValueError(_('Incorrect number of arguments for function {0}').format('has_extra_files'))
pattern = args[0] if len(args) == 1 else None pattern = args[0] if len(args) == 1 else None
db = self.get_database(mi).new_api db = self.get_database(mi, formatter=formatter).new_api
try: try:
files = tuple(f.relpath.partition('/')[-1] for f in files = tuple(f.relpath.partition('/')[-1] for f in
db.list_extra_files(mi.id, use_cache=True, pattern=DATA_FILE_PATTERN)) db.list_extra_files(mi.id, use_cache=True, pattern=DATA_FILE_PATTERN))
@ -2987,7 +3003,7 @@ also the functions :ref:`has_extra_files`, :ref:`extra_file_names` and
''') ''')
def evaluate(self, formatter, kwargs, mi, locals, file_name): def evaluate(self, formatter, kwargs, mi, locals, file_name):
db = self.get_database(mi).new_api db = self.get_database(mi, formatter=formatter).new_api
try: try:
q = posixpath.join(DATA_DIR_NAME, file_name) q = posixpath.join(DATA_DIR_NAME, file_name)
for f in db.list_extra_files(mi.id, use_cache=True, pattern=DATA_FILE_PATTERN): for f in db.list_extra_files(mi.id, use_cache=True, pattern=DATA_FILE_PATTERN):
@ -3016,7 +3032,7 @@ This function can be used only in the GUI.
''') ''')
def evaluate(self, formatter, kwargs, mi, locals, file_name, format_string): def evaluate(self, formatter, kwargs, mi, locals, file_name, format_string):
db = self.get_database(mi).new_api db = self.get_database(mi, formatter=formatter).new_api
try: try:
q = posixpath.join(DATA_DIR_NAME, file_name) q = posixpath.join(DATA_DIR_NAME, file_name)
for f in db.list_extra_files(mi.id, use_cache=True, pattern=DATA_FILE_PATTERN): for f in db.list_extra_files(mi.id, use_cache=True, pattern=DATA_FILE_PATTERN):
@ -3057,7 +3073,7 @@ program:
''') ''')
def evaluate(self, formatter, kwargs, mi, locals, field_name, field_value, plain_text): def evaluate(self, formatter, kwargs, mi, locals, field_name, field_value, plain_text):
db = self.get_database(mi).new_api db = self.get_database(mi, formatter=formatter).new_api
try: try:
note = None note = None
item_id = db.get_item_id(field_name, field_value, case_sensitive=True) item_id = db.get_item_id(field_name, field_value, case_sensitive=True)
@ -3125,7 +3141,7 @@ values in ``field_name``. Example:
''') ''')
def evaluate(self, formatter, kwargs, mi, locals, field_name, field_value): def evaluate(self, formatter, kwargs, mi, locals, field_name, field_value):
db = self.get_database(mi).new_api db = self.get_database(mi, formatter=formatter).new_api
if field_value: if field_value:
note = None note = None
try: try: