commit before changing tag_browser_categories to field_metadata, just in case...

This commit is contained in:
Charles Haley 2010-05-27 09:57:04 +01:00
parent 294e1c3c32
commit cb09c7ef46
5 changed files with 137 additions and 146 deletions

View File

@ -49,7 +49,7 @@ class TagCategories(QDialog, Ui_TagCategories):
cc_map = self.db.custom_column_label_map
for cc in cc_map:
if cc_map[cc]['datatype'] == 'text':
self.category_labels.append(db.tag_browser_categories.get_search_label(cc))
self.category_labels.append(db.tag_browser_categories.label_to_key(cc))
category_icons.append(cc_icon)
category_values.append(lambda col=cc: self.db.all_custom(label=col))
category_names.append(cc_map[cc]['name'])

View File

@ -157,7 +157,7 @@ class ResultCache(SearchQueryParser):
self.first_sort = True
self.search_restriction = ''
self.tag_browser_categories = tag_browser_categories
self.all_search_locations = tag_browser_categories.get_search_labels()
self.all_search_locations = tag_browser_categories.get_search_keys()
SearchQueryParser.__init__(self, self.all_search_locations)
self.build_date_relop_dict()
self.build_numeric_relop_dict()
@ -249,10 +249,10 @@ class ResultCache(SearchQueryParser):
query = query[p:]
if relop is None:
(p, relop) = self.date_search_relops['=']
if location in self.custom_column_label_map:
loc = self.FIELD_MAP[self.custom_column_label_map[location]['num']]
else:
loc = self.FIELD_MAP[{'date':'timestamp', 'pubdate':'pubdate'}[location]]
if location == 'date':
location = 'timestamp'
loc = self.tag_browser_categories[location]['rec_index']
if query == _('today'):
qd = now()
@ -310,22 +310,18 @@ class ResultCache(SearchQueryParser):
query = query[p:]
if relop is None:
(p, relop) = self.numeric_search_relops['=']
if location in self.custom_column_label_map:
loc = self.FIELD_MAP[self.custom_column_label_map[location]['num']]
dt = self.custom_column_label_map[location]['datatype']
if dt == 'int':
cast = (lambda x: int (x))
adjust = lambda x: x
elif dt == 'rating':
cast = (lambda x: int (x))
adjust = lambda x: x/2
elif dt == 'float':
cast = lambda x : float (x)
adjust = lambda x: x
else:
loc = self.FIELD_MAP['rating']
loc = self.tag_browser_categories[location]['rec_index']
dt = self.tag_browser_categories[location]['datatype']
if dt == 'int':
cast = (lambda x: int (x))
adjust = lambda x: x
elif dt == 'rating':
cast = (lambda x: int (x))
adjust = lambda x: x/2
elif dt == 'float':
cast = lambda x : float (x)
adjust = lambda x: x
try:
q = cast(query)
@ -347,21 +343,22 @@ class ResultCache(SearchQueryParser):
matches = set([])
if query and query.strip():
location = location.lower().strip()
if location in ('tag', 'author', 'format', 'comment'):
location += 's'
### take care of dates special case
if (location in ('pubdate', 'date')) or \
((location in self.custom_column_label_map) and \
self.custom_column_label_map[location]['datatype'] == 'datetime'):
(location in self.tag_browser_categories and \
self.tag_browser_categories[location]['datatype'] == 'datetime'):
return self.get_dates_matches(location, query.lower())
### take care of numerics special case
if location == 'rating' or \
(location in self.custom_column_label_map and
self.custom_column_label_map[location]['datatype'] in
('rating', 'int', 'float')):
### take care of numbers special case
if location in self.tag_browser_categories and \
self.tag_browser_categories[location]['datatype'] in \
('rating', 'int', 'float'):
return self.get_numeric_matches(location, query.lower())
### everything else
### everything else, or 'all' matches
matchkind = CONTAINS_MATCH
if (len(query) > 1):
if query.startswith('\\'):
@ -372,57 +369,48 @@ class ResultCache(SearchQueryParser):
elif query.startswith('~'):
matchkind = REGEXP_MATCH
query = query[1:]
if matchkind != REGEXP_MATCH: ### leave case in regexps because it can be significant e.g. \S \W \D
if matchkind != REGEXP_MATCH:
# leave case in regexps because it can be significant e.g. \S \W \D
query = query.lower()
if not isinstance(query, unicode):
query = query.decode('utf-8')
if location in ('tag', 'author', 'format', 'comment'):
location += 's'
MAP = {}
# Fields not used when matching against text contents. These are
# the non-text fields
EXCLUDE_FIELDS = []
# get the db columns for the standard searchables
for x in self.tag_browser_categories:
if len(self.tag_browser_categories[x]['search_labels']) and \
not self.tag_browser_categories.is_custom_field(x):
MAP[x] = self.tag_browser_categories[x]['rec_index']
if self.tag_browser_categories[x]['datatype'] != 'text':
EXCLUDE_FIELDS.append(MAP[x])
# add custom columns to MAP. Put the column's type into IS_CUSTOM
IS_CUSTOM = []
db_col = {}
# fields to not check when matching against text.
exclude_fields = []
col_datatype = []
for x in range(len(self.FIELD_MAP)):
IS_CUSTOM.append('')
col_datatype.append('')
for x in self.tag_browser_categories:
if len(self.tag_browser_categories[x]['search_keys']):
db_col[x] = self.tag_browser_categories[x]['rec_index']
if self.tag_browser_categories[x]['datatype'] not in ['text', 'comments']:
exclude_fields.append(db_col[x])
if self.tag_browser_categories.is_custom_field(x):
col_datatype[db_col[x]] = self.tag_browser_categories[x]['datatype']
# normal and custom ratings columns use the same code
IS_CUSTOM[self.FIELD_MAP['rating']] = 'rating'
for x in self.tag_browser_categories.get_custom_fields():
if self.tag_browser_categories[x]['datatype'] != "datetime":
MAP[x] = self.FIELD_MAP[self.tag_browser_categories[x]['colnum']]
IS_CUSTOM[MAP[x]] = self.tag_browser_categories[x]['datatype']
col_datatype[self.FIELD_MAP['rating']] = 'rating'
SPLITABLE_FIELDS = [MAP['authors'], MAP['tags'], MAP['formats']]
splitable_fields = [db_col['authors'], db_col['tags'], db_col['formats']]
for x in self.tag_browser_categories.get_custom_fields():
if self.tag_browser_categories[x]['is_multiple']:
SPLITABLE_FIELDS.append(MAP[x])
splitable_fields.append(db_col[x])
try:
rating_query = int(query) * 2
except:
rating_query = None
location = [location] if location != 'all' else list(MAP.keys())
location = [location] if location != 'all' else list(db_col.keys())
for i, loc in enumerate(location):
location[i] = MAP[loc]
location[i] = db_col[loc]
# get the tweak here so that the string lookup and compare aren't in the loop
bools_are_tristate = tweaks['bool_custom_columns_are_tristate'] == 'yes'
for loc in location:
if loc == MAP['authors']:
if loc == db_col['authors']:
### DB stores authors with commas changed to bars, so change query
q = query.replace(',', '|');
else:
@ -431,7 +419,7 @@ class ResultCache(SearchQueryParser):
for item in self._data:
if item is None: continue
if IS_CUSTOM[loc] == 'bool': # complexity caused by the two-/three-value tweak
if col_datatype[loc] == 'bool': # complexity caused by the two-/three-value tweak
v = item[loc]
if not bools_are_tristate:
if v is None or not v: # item is None or set to false
@ -466,18 +454,18 @@ class ResultCache(SearchQueryParser):
matches.add(item[0])
continue
if IS_CUSTOM[loc] == 'rating': # get here if 'all' query
if col_datatype[loc] == 'rating': # get here if 'all' query
if rating_query and rating_query == int(item[loc]):
matches.add(item[0])
continue
try: # a conversion below might fail
# relationals not supported in 'all' queries
if IS_CUSTOM[loc] == 'float':
if col_datatype[loc] == 'float':
if float(query) == item[loc]:
matches.add(item[0])
continue
if IS_CUSTOM[loc] == 'int':
if col_datatype[loc] == 'int':
if int(query) == item[loc]:
matches.add(item[0])
continue
@ -486,9 +474,9 @@ class ResultCache(SearchQueryParser):
# no further match is possible
continue
if loc not in EXCLUDE_FIELDS:
if loc in SPLITABLE_FIELDS:
if IS_CUSTOM[loc]:
if loc not in exclude_fields:
if loc in splitable_fields:
if col_datatype[loc]:
vals = item[loc].split('|')
else:
vals = item[loc].split(',')

View File

@ -144,14 +144,14 @@ class CustomColumns(object):
for k in sorted(self.custom_column_label_map.keys()):
v = self.custom_column_label_map[k]
if v['normalized']:
searchable = True
is_category = True
else:
searchable = False
is_category = False
tn = 'custom_column_{0}'.format(v['num'])
self.tag_browser_categories.add_custom_field(label=v['label'],
table=tn, column='value', datatype=v['datatype'],
is_multiple=v['is_multiple'], colnum=v['num'],
name=v['name'], searchable=searchable)
name=v['name'], is_category=is_category)
def get_custom(self, idx, label=None, num=None, index_is_id=False):
if label is not None:

View File

@ -656,11 +656,18 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
if icon_map is not None and type(icon_map) != TagsIcons:
raise TypeError('icon_map passed to get_categories must be of type TagIcons')
#### First, build the standard and custom-column categories ####
tb_cats = self.tag_browser_categories
# remove all user categories from tag_browser_categories. They can
# easily come and go. We will add all the existing ones in below.
for k in tb_cats.keys():
if tb_cats[k]['kind'] in ['user', 'search']:
del tb_cats[k]
#### First, build the standard and custom-column categories ####
for category in tb_cats.keys():
cat = tb_cats[category]
if cat['kind'] == 'not_cat':
if not cat['is_category']:
continue
tn = cat['table']
categories[category] = [] #reserve the position in the ordered list
@ -680,7 +687,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
# icon_map is not None if get_categories is to store an icon and
# possibly a tooltip in the tag structure.
icon, tooltip = None, ''
label = tb_cats.get_field_label(category)
label = tb_cats.key_to_label(category)
if icon_map:
if not tb_cats.is_custom_field(category):
if category in icon_map:
@ -737,12 +744,6 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
#### Now do the user-defined categories. ####
user_categories = prefs['user_categories']
# remove all user categories from tag_browser_categories. They can
# easily come and go. We will add all the existing ones in below.
for k in tb_cats.keys():
if tb_cats[k]['kind'] in ['user', 'search']:
del tb_cats[k]
# We want to use same node in the user category as in the source
# category. To do that, we need to find the original Tag node. There is
# a time/space tradeoff here. By converting the tags into a map, we can

View File

@ -4,7 +4,6 @@ Created on 25 May 2010
@author: charles
'''
from UserDict import DictMixin
from calibre.utils.ordered_dict import OrderedDict
class TagsIcons(dict):
@ -22,7 +21,7 @@ class TagsIcons(dict):
raise ValueError('Missing category icon [%s]'%a)
self[a] = icon_dict[a]
class FieldMetadata(dict, DictMixin):
class FieldMetadata(dict):
# kind == standard: is tag category. May be a search label. Is db col
# or is specially handled (e.g., news)
@ -41,86 +40,86 @@ class FieldMetadata(dict, DictMixin):
('authors', {'table':'authors', 'column':'name',
'datatype':'text', 'is_multiple':False,
'kind':'standard', 'name':_('Authors'),
'search_labels':['authors', 'author'],
'is_custom':False}),
'search_keys':['authors', 'author'],
'is_custom':False, 'is_category':True}),
('series', {'table':'series', 'column':'name',
'datatype':'text', 'is_multiple':False,
'kind':'standard', 'name':_('Series'),
'search_labels':['series'],
'is_custom':False}),
'search_keys':['series'],
'is_custom':False, 'is_category':True}),
('formats', {'table':None, 'column':None,
'datatype':'text', 'is_multiple':False, # must think what type this is!
'kind':'standard', 'name':_('Formats'),
'search_labels':['formats', 'format'],
'is_custom':False}),
'search_keys':['formats', 'format'],
'is_custom':False, 'is_category':True}),
('publisher', {'table':'publishers', 'column':'name',
'datatype':'text', 'is_multiple':False,
'kind':'standard', 'name':_('Publishers'),
'search_labels':['publisher'],
'is_custom':False}),
'search_keys':['publisher'],
'is_custom':False, 'is_category':True}),
('rating', {'table':'ratings', 'column':'rating',
'datatype':'rating', 'is_multiple':False,
'kind':'standard', 'name':_('Ratings'),
'search_labels':['rating'],
'is_custom':False}),
'search_keys':['rating'],
'is_custom':False, 'is_category':True}),
('news', {'table':'news', 'column':'name',
'datatype':None, 'is_multiple':False,
'kind':'standard', 'name':_('News'),
'search_labels':[],
'is_custom':False}),
'search_keys':[],
'is_custom':False, 'is_category':True}),
('tags', {'table':'tags', 'column':'name',
'datatype':'text', 'is_multiple':True,
'kind':'standard', 'name':_('Tags'),
'search_labels':['tags', 'tag'],
'is_custom':False}),
'search_keys':['tags', 'tag'],
'is_custom':False, 'is_category':True}),
('author_sort',{'table':None, 'column':None, 'datatype':'text',
'is_multiple':False, 'kind':'not_cat', 'name':None,
'search_labels':[], 'is_custom':False}),
'is_multiple':False, 'kind':'standard', 'name':None,
'search_keys':[], 'is_custom':False, 'is_category':False}),
('comments', {'table':None, 'column':None, 'datatype':'text',
'is_multiple':False, 'kind':'not_cat', 'name':None,
'search_labels':['comments', 'comment'], 'is_custom':False}),
'is_multiple':False, 'kind':'standard', 'name':None,
'search_keys':['comments', 'comment'], 'is_custom':False, 'is_category':False}),
('cover', {'table':None, 'column':None, 'datatype':None,
'is_multiple':False, 'kind':'not_cat', 'name':None,
'search_labels':['cover'], 'is_custom':False}),
'is_multiple':False, 'kind':'standard', 'name':None,
'search_keys':['cover'], 'is_custom':False, 'is_category':False}),
('flags', {'table':None, 'column':None, 'datatype':'text',
'is_multiple':False, 'kind':'not_cat', 'name':None,
'search_labels':[], 'is_custom':False}),
'is_multiple':False, 'kind':'standard', 'name':None,
'search_keys':[], 'is_custom':False, 'is_category':False}),
('id', {'table':None, 'column':None, 'datatype':'int',
'is_multiple':False, 'kind':'not_cat', 'name':None,
'search_labels':[], 'is_custom':False}),
'is_multiple':False, 'kind':'standard', 'name':None,
'search_keys':[], 'is_custom':False, 'is_category':False}),
('isbn', {'table':None, 'column':None, 'datatype':'text',
'is_multiple':False, 'kind':'not_cat', 'name':None,
'search_labels':['isbn'], 'is_custom':False}),
'is_multiple':False, 'kind':'standard', 'name':None,
'search_keys':['isbn'], 'is_custom':False, 'is_category':False}),
('lccn', {'table':None, 'column':None, 'datatype':'text',
'is_multiple':False, 'kind':'not_cat', 'name':None,
'search_labels':[], 'is_custom':False}),
'is_multiple':False, 'kind':'standard', 'name':None,
'search_keys':[], 'is_custom':False, 'is_category':False}),
('ondevice', {'table':None, 'column':None, 'datatype':'bool',
'is_multiple':False, 'kind':'not_cat', 'name':None,
'search_labels':[], 'is_custom':False}),
'is_multiple':False, 'kind':'standard', 'name':None,
'search_keys':[], 'is_custom':False, 'is_category':False}),
('path', {'table':None, 'column':None, 'datatype':'text',
'is_multiple':False, 'kind':'not_cat', 'name':None,
'search_labels':[], 'is_custom':False}),
'is_multiple':False, 'kind':'standard', 'name':None,
'search_keys':[], 'is_custom':False, 'is_category':False}),
('pubdate', {'table':None, 'column':None, 'datatype':'datetime',
'is_multiple':False, 'kind':'not_cat', 'name':None,
'search_labels':['pubdate'], 'is_custom':False}),
'is_multiple':False, 'kind':'standard', 'name':None,
'search_keys':['pubdate'], 'is_custom':False, 'is_category':False}),
('series_index',{'table':None, 'column':None, 'datatype':'float',
'is_multiple':False, 'kind':'not_cat', 'name':None,
'search_labels':[], 'is_custom':False}),
'is_multiple':False, 'kind':'standard', 'name':None,
'search_keys':[], 'is_custom':False, 'is_category':False}),
('sort', {'table':None, 'column':None, 'datatype':'text',
'is_multiple':False, 'kind':'not_cat', 'name':None,
'search_labels':[], 'is_custom':False}),
'is_multiple':False, 'kind':'standard', 'name':None,
'search_keys':[], 'is_custom':False, 'is_category':False}),
('size', {'table':None, 'column':None, 'datatype':'float',
'is_multiple':False, 'kind':'not_cat', 'name':None,
'search_labels':[], 'is_custom':False}),
'is_multiple':False, 'kind':'standard', 'name':None,
'search_keys':[], 'is_custom':False, 'is_category':False}),
('timestamp', {'table':None, 'column':None, 'datatype':'datetime',
'is_multiple':False, 'kind':'not_cat', 'name':None,
'search_labels':['date'], 'is_custom':False}),
'is_multiple':False, 'kind':'standard', 'name':None,
'search_keys':['date'], 'is_custom':False, 'is_category':False}),
('title', {'table':None, 'column':None, 'datatype':'text',
'is_multiple':False, 'kind':'not_cat', 'name':None,
'search_labels':['title'], 'is_custom':False}),
'is_multiple':False, 'kind':'standard', 'name':None,
'search_keys':['title'], 'is_custom':False, 'is_category':False}),
('uuid', {'table':None, 'column':None, 'datatype':'text',
'is_multiple':False, 'kind':'not_cat', 'name':None,
'search_labels':[], 'is_custom':False}),
'is_multiple':False, 'kind':'standard', 'name':None,
'search_keys':[], 'is_custom':False, 'is_category':False}),
]
# search labels that are not db columns
@ -150,6 +149,12 @@ class FieldMetadata(dict, DictMixin):
for key in self._tb_cats:
yield key
def has_key(self, key):
return key in self._tb_cats
def __contains__(self, key):
return self.has_key(key)
def keys(self):
return self._tb_cats.keys()
@ -164,12 +169,12 @@ class FieldMetadata(dict, DictMixin):
def is_custom_field(self, key):
return key.startswith(self.custom_field_prefix)
def get_field_label(self, key):
def key_to_label(self, key):
if 'label' not in self._tb_cats[key]:
return key
return self._tb_cats[key]['label']
def get_search_label(self, label):
def label_to_key(self, label, prefer_custom=False):
if 'label' in self._tb_cats:
return label
if self.is_custom_field(label):
@ -179,22 +184,17 @@ class FieldMetadata(dict, DictMixin):
def get_custom_fields(self):
return [l for l in self._tb_cats if self._tb_cats[l]['is_custom']]
def add_custom_field(self, label, table, column, datatype,
is_multiple, colnum, name, searchable):
def add_custom_field(self, label, table, column, datatype, colnum,
name, is_multiple, is_category):
fn = self.custom_field_prefix + label
if fn in self._tb_cats:
raise ValueError('Duplicate custom field [%s]'%(label))
if searchable:
sl = [fn]
kind = 'standard'
else:
sl = []
kind = 'not_cat'
self._tb_cats[fn] = {'table':table, 'column':column,
'datatype':datatype, 'is_multiple':is_multiple,
'kind':kind, 'name':name,
'search_labels':sl, 'label':label,
'colnum':colnum, 'is_custom':True}
'kind':'standard', 'name':name,
'search_keys':[fn], 'label':label,
'colnum':colnum, 'is_custom':True,
'is_category':is_category}
def set_field_record_index(self, label, index, prefer_custom=False):
if prefer_custom:
@ -214,7 +214,8 @@ class FieldMetadata(dict, DictMixin):
self._tb_cats[label] = {'table':None, 'column':None,
'datatype':None, 'is_multiple':False,
'kind':'user', 'name':name,
'search_labels':[], 'is_custom':False}
'search_keys':[], 'is_custom':False,
'is_category':True}
def add_search_category(self, label, name):
if label in self._tb_cats:
@ -222,7 +223,8 @@ class FieldMetadata(dict, DictMixin):
self._tb_cats[label] = {'table':None, 'column':None,
'datatype':None, 'is_multiple':False,
'kind':'search', 'name':name,
'search_labels':[], 'is_custom':False}
'search_keys':[], 'is_custom':False,
'is_category':True}
# DEFAULT_LOCATIONS = frozenset([
# 'all',
@ -247,13 +249,13 @@ class FieldMetadata(dict, DictMixin):
# ])
def get_search_labels(self):
s_labels = []
def get_search_keys(self):
s_keys = []
for v in self._tb_cats.itervalues():
map((lambda x:s_labels.append(x)), v['search_labels'])
map((lambda x:s_keys.append(x)), v['search_keys'])
for v in self.search_items:
s_labels.append(v)
# if set(s_labels) != self.DEFAULT_LOCATIONS:
s_keys.append(v)
# if set(s_keys) != self.DEFAULT_LOCATIONS:
# print 'search labels and default_locations do not match:'
# print set(s_labels) ^ self.DEFAULT_LOCATIONS
return s_labels
# print set(s_keys) ^ self.DEFAULT_LOCATIONS
return s_keys