7
from calibre.utils.ordered_dict import OrderedDict
11
If the client wants icons to be in the tag structure, this class must be
12
instantiated and filled in with real icons. If this class is instantiated
13
and passed to get_categories, All items must be given a value not None
16
category_icons = ['authors', 'series', 'formats', 'publisher', 'rating',
17
'news', 'tags', ':custom', ':user', 'search',]
18
def __init__(self, icon_dict):
19
for a in self.category_icons:
20
if a not in icon_dict:
21
raise ValueError('Missing category icon [%s]'%a)
22
self[a] = icon_dict[a]
24
class FieldMetadata(dict):
26
key: the key to the dictionary is:
27
- for standard fields, the metadata field name.
28
- for custom fields, the metadata field name prefixed by '#'
29
This is done to create two 'namespaces' so the names don't clash
31
label: the actual column label. No prefixing.
33
datatype: the type of the information in the field. Valid values are float,
34
int, rating, bool, comments, datetime, text.
35
is_multiple: valid for the text datatype. If None, the field is to be
36
treated as a single term. If not None, it contains a string, and the field
37
is assumed to contain a list of terms separated by that string
39
kind == standard: is a db field.
40
kind == category: standard tag category that isn't a field. see news.
41
kind == user: user-defined tag category.
42
kind == search: saved-searches category.
44
is_category: is a tag browser category. If true, then:
45
table: name of the db table used to construct item list
46
column: name of the column in the normalized table to join on
47
link_column: name of the column in the connection table to join on
48
If these are None, then the category constructor must know how
49
to build the item list (e.g., formats).
50
The order below is the order that the categories will
51
appear in the tags pane.
53
name: the text that is to be used when displaying the field. Column headings
56
search_terms: the terms that can be used to identify the field when
57
searching. They can be thought of as aliases for metadata keys, but are only
58
valid when passed to search().
60
is_custom: the field has been added by the user.
62
rec_index: the index of the field in the db metadata record.
66
('authors', {'table':'authors',
68
'link_column':'author',
73
'search_terms':['authors', 'author'],
76
('series', {'table':'series',
78
'link_column':'series',
83
'search_terms':['series'],
86
('formats', {'table':None,
92
'search_terms':['formats', 'format'],
95
('publisher', {'table':'publishers',
97
'link_column':'publisher',
101
'name':_('Publishers'),
102
'search_terms':['publisher'],
104
'is_category':True}),
105
('rating', {'table':'ratings',
107
'link_column':'rating',
112
'search_terms':['rating'],
114
'is_category':True}),
115
('news', {'table':'news',
123
'is_category':True}),
124
('tags', {'table':'tags',
126
'link_column': 'tag',
131
'search_terms':['tags', 'tag'],
133
'is_category':True}),
134
('author_sort',{'table':None,
142
'is_category':False}),
143
('comments', {'table':None,
149
'search_terms':['comments', 'comment'],
150
'is_custom':False, 'is_category':False}),
151
('cover', {'table':None,
157
'search_terms':['cover'],
159
'is_category':False}),
160
('flags', {'table':None,
168
'is_category':False}),
169
('id', {'table':None,
177
'is_category':False}),
178
('isbn', {'table':None,
184
'search_terms':['isbn'],
186
'is_category':False}),
187
('lccn', {'table':None,
195
'is_category':False}),
196
('ondevice', {'table':None,
202
'search_terms':['ondevice'],
204
'is_category':False}),
205
('path', {'table':None,
213
'is_category':False}),
214
('pubdate', {'table':None,
216
'datatype':'datetime',
220
'search_terms':['pubdate'],
222
'is_category':False}),
223
('series_index',{'table':None,
231
'is_category':False}),
232
('sort', {'table':None,
240
'is_category':False}),
241
('size', {'table':None,
249
'is_category':False}),
250
('timestamp', {'table':None,
252
'datatype':'datetime',
256
'search_terms':['date'],
258
'is_category':False}),
259
('title', {'table':None,
265
'search_terms':['title'],
267
'is_category':False}),
268
('uuid', {'table':None,
276
'is_category':False}),
279
# search labels that are not db columns
280
search_items = [ 'all',
286
self._tb_cats = OrderedDict()
287
self._search_term_map = {}
288
self.custom_label_to_key_map = {}
289
for k,v in self._field_metadata:
291
self._tb_cats[k]['label'] = k
292
self._tb_cats[k]['display'] = {}
293
self._tb_cats[k]['is_editable'] = True
294
self._add_search_terms_to_map(k, self._tb_cats[k]['search_terms'])
295
self.custom_field_prefix = '#'
296
self.get = self._tb_cats.get
298
def __getitem__(self, key):
299
return self._tb_cats[key]
301
def __setitem__(self, key, val):
302
raise AttributeError('Assigning to this object is forbidden')
304
def __delitem__(self, key):
305
del self._tb_cats[key]
308
for key in self._tb_cats:
311
def __contains__(self, key):
312
return self.has_key(key)
314
def has_key(self, key):
315
return key in self._tb_cats
318
return self._tb_cats.keys()
321
for key in self._tb_cats:
324
def itervalues(self):
325
return self._tb_cats.itervalues()
328
return self._tb_cats.values()
331
for key in self._tb_cats:
332
yield (key, self._tb_cats[key])
335
return list(self.iteritems())
337
def is_custom_field(self, key):
338
return key.startswith(self.custom_field_prefix)
340
def key_to_label(self, key):
341
if 'label' not in self._tb_cats[key]:
343
return self._tb_cats[key]['label']
345
def label_to_key(self, label, prefer_custom=False):
347
if label in self.custom_label_to_key_map:
348
return self.custom_label_to_key_map[label]
349
if 'label' in self._tb_cats:
351
if not prefer_custom:
352
if label in self.custom_label_to_key_map:
353
return self.custom_label_to_key_map[label]
354
raise ValueError('Unknown key [%s]'%(label))
356
def get_custom_fields(self):
357
return [l for l in self._tb_cats if self._tb_cats[l]['is_custom']]
359
def get_custom_field_metadata(self):
361
for k in self._tb_cats:
362
if self._tb_cats[k]['is_custom']:
363
l[k] = self._tb_cats[k]
366
def add_custom_field(self, label, table, column, datatype, colnum, name,
367
display, is_editable, is_multiple, is_category):
368
key = self.custom_field_prefix + label
369
if key in self._tb_cats:
370
raise ValueError('Duplicate custom field [%s]'%(label))
371
self._tb_cats[key] = {'table':table, 'column':column,
372
'datatype':datatype, 'is_multiple':is_multiple,
373
'kind':'field', 'name':name,
374
'search_terms':[key], 'label':label,
375
'colnum':colnum, 'display':display,
376
'is_custom':True, 'is_category':is_category,
377
'link_column':'value',
378
'is_editable': is_editable,}
379
self._add_search_terms_to_map(key, [key])
380
self.custom_label_to_key_map[label] = key
382
def remove_custom_fields(self):
383
for key in self.get_custom_fields():
384
del self._tb_cats[key]
386
def remove_dynamic_categories(self):
387
for key in list(self._tb_cats.keys()):
388
val = self._tb_cats[key]
389
if val['is_category'] and val['kind'] in ('user', 'search'):
390
del self._tb_cats[key]
393
def add_user_category(self, label, name):
394
if label in self._tb_cats:
395
raise ValueError('Duplicate user field [%s]'%(label))
396
self._tb_cats[label] = {'table':None, 'column':None,
397
'datatype':None, 'is_multiple':None,
398
'kind':'user', 'name':name,
399
'search_terms':[], 'is_custom':False,
402
def add_search_category(self, label, name):
403
if label in self._tb_cats:
404
raise ValueError('Duplicate user field [%s]'%(label))
405
self._tb_cats[label] = {'table':None, 'column':None,
406
'datatype':None, 'is_multiple':None,
407
'kind':'search', 'name':name,
408
'search_terms':[], 'is_custom':False,
411
def set_field_record_index(self, label, index, prefer_custom=False):
413
key = self.custom_field_prefix+label
414
if key not in self._tb_cats:
417
if label in self._tb_cats:
420
key = self.custom_field_prefix+label
421
self._tb_cats[key]['rec_index'] = index # let the exception fly ...
424
# DEFAULT_LOCATIONS = frozenset([
426
# 'author', # compatibility
428
# 'comment', # compatibility
432
# 'format', # compatibility
441
# 'tag', # compatibility
446
def get_search_terms(self):
448
for v in self._tb_cats.itervalues():
449
map((lambda x:s_keys.append(x)), v['search_terms'])
450
for v in self.search_items:
452
# if set(s_keys) != self.DEFAULT_LOCATIONS:
453
# print 'search labels and default_locations do not match:'
454
# print set(s_keys) ^ self.DEFAULT_LOCATIONS
457
def _add_search_terms_to_map(self, key, terms):
458
if terms is not None:
460
self._search_term_map[t] = key
462
def search_term_to_key(self, term):
463
if term in self._search_term_map:
464
return self._search_term_map[term]