mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
New db backend: Sorting implemented with tests
This commit is contained in:
parent
fe27e08a83
commit
26a9197cfb
@ -200,7 +200,12 @@ class Cache(object):
|
||||
``book_id``. If no such book exists or it has no defined value for the
|
||||
field ``name`` or no such field exists, then ``default_value`` is returned.
|
||||
|
||||
The returned value for is_multiple fields are always tuples.
|
||||
default_values is not used for title, title_sort, authors, author_sort
|
||||
and series_index. This is because these always have values in the db.
|
||||
default_value is used for all custom columns.
|
||||
|
||||
The returned value for is_multiple fields are always tuples, unless
|
||||
default_value is returned.
|
||||
'''
|
||||
if self.composites and name in self.composites:
|
||||
return self.composite_for(name, book_id,
|
||||
@ -254,7 +259,7 @@ class Cache(object):
|
||||
'''
|
||||
Frozen set of all known book ids.
|
||||
'''
|
||||
return frozenset(self.fields['uuid'].iter_book_ids())
|
||||
return frozenset(self.fields['uuid'])
|
||||
|
||||
@read_api
|
||||
def all_field_ids(self, name):
|
||||
@ -348,17 +353,37 @@ class Cache(object):
|
||||
|
||||
@read_api
|
||||
def multisort(self, fields, ids_to_sort=None):
|
||||
'''
|
||||
Return a list of sorted book ids. If ids_to_sort is None, all book ids
|
||||
are returned.
|
||||
|
||||
fields must be a list of 2-tuples of the form (field_name,
|
||||
ascending=True or False). The most significant field is the first
|
||||
2-tuple.
|
||||
'''
|
||||
all_book_ids = frozenset(self._all_book_ids() if ids_to_sort is None
|
||||
else ids_to_sort)
|
||||
get_metadata = partial(self._get_metadata, get_user_categories=False)
|
||||
|
||||
sort_keys = tuple(self.fields[field[0]].sort_keys_for_books(get_metadata,
|
||||
all_book_ids) for field in fields)
|
||||
fm = {'title':'sort', 'authors':'author_sort'}
|
||||
|
||||
def sort_key(field):
|
||||
'Handle series type fields'
|
||||
ans = self.fields[fm.get(field, field)].sort_keys_for_books(get_metadata,
|
||||
all_book_ids)
|
||||
idx = field + '_index'
|
||||
if idx in self.fields:
|
||||
idx_ans = self.fields[idx].sort_keys_for_books(get_metadata,
|
||||
all_book_ids)
|
||||
ans = {k:(v, idx_ans[k]) for k, v in ans.iteritems()}
|
||||
return ans
|
||||
|
||||
sort_keys = tuple(sort_key(field[0]) for field in fields)
|
||||
|
||||
if len(sort_keys) == 1:
|
||||
sk = sort_keys[0]
|
||||
return sorted(all_book_ids, key=lambda i:sk[i], reverse=not
|
||||
fields[1])
|
||||
fields[0][1])
|
||||
else:
|
||||
return sorted(all_book_ids, key=partial(SortKey, fields, sort_keys))
|
||||
|
||||
|
@ -2,7 +2,7 @@
|
||||
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
|
||||
from __future__ import (unicode_literals, division, absolute_import,
|
||||
print_function)
|
||||
from future_builtins import map
|
||||
#from future_builtins import map
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__copyright__ = '2011, Kovid Goyal <kovid@kovidgoyal.net>'
|
||||
@ -12,6 +12,8 @@ from threading import Lock
|
||||
|
||||
from calibre.db.tables import ONE_ONE, MANY_ONE, MANY_MANY
|
||||
from calibre.utils.icu import sort_key
|
||||
from calibre.utils.date import UNDEFINED_DATE
|
||||
from calibre.utils.localization import calibre_langcode_to_name
|
||||
|
||||
class Field(object):
|
||||
|
||||
@ -21,7 +23,16 @@ class Field(object):
|
||||
'series', 'enumeration')
|
||||
self.table_type = self.table.table_type
|
||||
dt = self.metadata['datatype']
|
||||
self._sort_key = (sort_key if dt == 'text' else lambda x: x)
|
||||
self._sort_key = (sort_key if dt in ('text', 'series', 'enumeration') else lambda x: x)
|
||||
self._default_sort_key = ''
|
||||
if self.metadata['datatype'] in ('int', 'float', 'rating'):
|
||||
self._default_sort_key = 0
|
||||
elif self.metadata['datatype'] == 'bool':
|
||||
self._default_sort_key = None
|
||||
elif self.metadata['datatype'] == 'datetime':
|
||||
self._default_sort_key = UNDEFINED_DATE
|
||||
if self.name == 'languages':
|
||||
self._sort_key = lambda x:sort_key(calibre_langcode_to_name(x))
|
||||
|
||||
@property
|
||||
def metadata(self):
|
||||
@ -63,7 +74,8 @@ class Field(object):
|
||||
'''
|
||||
Return a mapping of book_id -> sort_key. The sort key is suitable for
|
||||
use in sorting the list of all books by this field, via the python cmp
|
||||
method.
|
||||
method. all_book_ids is the list/set of book ids for which sort_keys
|
||||
should be generated.
|
||||
'''
|
||||
raise NotImplementedError()
|
||||
|
||||
@ -83,8 +95,8 @@ class OneToOneField(Field):
|
||||
return self.table.book_col_map.iterkeys()
|
||||
|
||||
def sort_keys_for_books(self, get_metadata, all_book_ids):
|
||||
return {id_ : self._sort_key(self.book_col_map.get(id_, '')) for id_ in
|
||||
all_book_ids}
|
||||
return {id_ : self._sort_key(self.table.book_col_map.get(id_,
|
||||
self._default_sort_key)) for id_ in all_book_ids}
|
||||
|
||||
class CompositeField(OneToOneField):
|
||||
|
||||
@ -182,10 +194,12 @@ class ManyToOneField(Field):
|
||||
return self.table.id_map.iterkeys()
|
||||
|
||||
def sort_keys_for_books(self, get_metadata, all_book_ids):
|
||||
keys = {id_ : self._sort_key(self.table.id_map.get(id_, '')) for id_ in
|
||||
all_book_ids}
|
||||
return {id_ : keys.get(
|
||||
self.book_col_map.get(id_, None), '') for id_ in all_book_ids}
|
||||
ans = {id_ : self.table.book_col_map.get(id_, None)
|
||||
for id_ in all_book_ids}
|
||||
sk_map = {cid : (self._default_sort_key if cid is None else
|
||||
self._sort_key(self.table.id_map[cid]))
|
||||
for cid in ans.itervalues()}
|
||||
return {id_ : sk_map[cid] for id_, cid in ans.iteritems()}
|
||||
|
||||
class ManyToManyField(Field):
|
||||
|
||||
@ -211,16 +225,17 @@ class ManyToManyField(Field):
|
||||
return self.table.id_map.iterkeys()
|
||||
|
||||
def sort_keys_for_books(self, get_metadata, all_book_ids):
|
||||
keys = {id_ : self._sort_key(self.table.id_map.get(id_, '')) for id_ in
|
||||
all_book_ids}
|
||||
ans = {id_ : self.table.book_col_map.get(id_, ())
|
||||
for id_ in all_book_ids}
|
||||
all_cids = set()
|
||||
for cids in ans.itervalues():
|
||||
all_cids = all_cids.union(set(cids))
|
||||
sk_map = {cid : self._sort_key(self.table.id_map[cid])
|
||||
for cid in all_cids}
|
||||
return {id_ : (tuple(sk_map[cid] for cid in cids) if cids else
|
||||
(self._default_sort_key,))
|
||||
for id_, cids in ans.iteritems()}
|
||||
|
||||
def sort_key_for_book(book_id):
|
||||
item_ids = self.table.book_col_map.get(book_id, ())
|
||||
if self.alphabetical_sort:
|
||||
item_ids = sorted(item_ids, key=keys.get)
|
||||
return tuple(map(keys.get, item_ids))
|
||||
|
||||
return {id_ : sort_key_for_book(id_) for id_ in all_book_ids}
|
||||
|
||||
class IdentifiersField(ManyToManyField):
|
||||
|
||||
@ -230,6 +245,15 @@ class IdentifiersField(ManyToManyField):
|
||||
ids = default_value
|
||||
return ids
|
||||
|
||||
def sort_keys_for_books(self, get_metadata, all_book_ids):
|
||||
'Sort by identifier keys'
|
||||
ans = {id_ : self.table.book_col_map.get(id_, ())
|
||||
for id_ in all_book_ids}
|
||||
return {id_ : (tuple(sorted(cids.iterkeys())) if cids else
|
||||
(self._default_sort_key,))
|
||||
for id_, cids in ans.iteritems()}
|
||||
|
||||
|
||||
class AuthorsField(ManyToManyField):
|
||||
|
||||
def author_data(self, author_id):
|
||||
|
Binary file not shown.
@ -39,8 +39,40 @@ class ReadingTest(unittest.TestCase):
|
||||
shutil.rmtree(self.library_path)
|
||||
|
||||
def test_read(self): # {{{
|
||||
'Test the reading of data from the database'
|
||||
cache = init_cache(self.library_path)
|
||||
tests = {
|
||||
3 : {
|
||||
'title': 'Unknown',
|
||||
'sort': 'Unknown',
|
||||
'authors': ('Unknown',),
|
||||
'author_sort': 'Unknown',
|
||||
'series' : None,
|
||||
'series_index': 1.0,
|
||||
'rating': None,
|
||||
'tags': None,
|
||||
'identifiers': None,
|
||||
'timestamp': datetime.datetime(2011, 9, 7, 13, 54, 41,
|
||||
tzinfo=local_tz),
|
||||
'pubdate': datetime.datetime(2011, 9, 7, 13, 54, 41,
|
||||
tzinfo=local_tz),
|
||||
'last_modified': datetime.datetime(2011, 9, 7, 13, 54, 41,
|
||||
tzinfo=local_tz),
|
||||
'publisher': None,
|
||||
'languages': None,
|
||||
'comments': None,
|
||||
'#enum': None,
|
||||
'#authors':None,
|
||||
'#date':None,
|
||||
'#rating':None,
|
||||
'#series':None,
|
||||
'#series_index': None,
|
||||
'#tags':None,
|
||||
'#yesno':None,
|
||||
'#comments': None,
|
||||
|
||||
},
|
||||
|
||||
2 : {
|
||||
'title': 'Title One',
|
||||
'sort': 'One',
|
||||
@ -74,10 +106,10 @@ class ReadingTest(unittest.TestCase):
|
||||
'sort': 'Title Two',
|
||||
'authors': ('Author Two', 'Author One'),
|
||||
'author_sort': 'Two, Author & One, Author',
|
||||
'series' : 'Series Two',
|
||||
'series' : 'Series One',
|
||||
'series_index': 2.0,
|
||||
'rating': 6.0,
|
||||
'tags': ('Tag Two',),
|
||||
'tags': ('Tag One',),
|
||||
'identifiers': {'test':'two'},
|
||||
'timestamp': datetime.datetime(2011, 9, 6, 0, 0,
|
||||
tzinfo=local_tz),
|
||||
@ -105,6 +137,48 @@ class ReadingTest(unittest.TestCase):
|
||||
cache.field_for(field, book_id))
|
||||
# }}}
|
||||
|
||||
def test_sorting(self): # {{{
|
||||
'Test sorting'
|
||||
cache = init_cache(self.library_path)
|
||||
for field, order in {
|
||||
'title' : [2, 1, 3],
|
||||
'authors': [2, 1, 3],
|
||||
'series' : [3, 2, 1],
|
||||
'tags' : [3, 1, 2],
|
||||
'rating' : [3, 2, 1],
|
||||
# 'identifiers': [3, 2, 1], There is no stable sort since 1 and
|
||||
# 2 have the same identifier keys
|
||||
# TODO: Add an empty book to the db and ensure that empty
|
||||
# fields sort the same as they do in db2
|
||||
'timestamp': [2, 1, 3],
|
||||
'pubdate' : [1, 2, 3],
|
||||
'publisher': [3, 2, 1],
|
||||
'last_modified': [2, 1, 3],
|
||||
'languages': [3, 2, 1],
|
||||
'comments': [3, 2, 1],
|
||||
'#enum' : [3, 2, 1],
|
||||
'#authors' : [3, 2, 1],
|
||||
'#date': [3, 1, 2],
|
||||
'#rating':[3, 2, 1],
|
||||
'#series':[3, 2, 1],
|
||||
'#tags':[3, 2, 1],
|
||||
'#yesno':[3, 1, 2],
|
||||
'#comments':[3, 2, 1],
|
||||
}.iteritems():
|
||||
x = list(reversed(order))
|
||||
self.assertEqual(order, cache.multisort([(field, True)],
|
||||
ids_to_sort=x),
|
||||
'Ascending sort of %s failed'%field)
|
||||
self.assertEqual(x, cache.multisort([(field, False)],
|
||||
ids_to_sort=order),
|
||||
'Descending sort of %s failed'%field)
|
||||
|
||||
# Test subsorting
|
||||
self.assertEqual([3, 2, 1], cache.multisort([('identifiers', True),
|
||||
('title', True)]), 'Subsort failed')
|
||||
# }}}
|
||||
|
||||
|
||||
def tests():
|
||||
return unittest.TestLoader().loadTestsFromTestCase(ReadingTest)
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user