newdb: Fix sorting of is_multiple fields

Fix sorting of book list by multi-valued fields like tags not correct in
the new backend. Fixes #1215820 [different ordering by tags in the 1.0 version](https://bugs.launchpad.net/calibre/+bug/1215820)
This commit is contained in:
Kovid Goyal 2013-08-23 17:06:52 +05:30
parent 189a280bb7
commit aa58f3a559
3 changed files with 28 additions and 5 deletions

View File

@ -13,7 +13,7 @@ from collections import defaultdict, Counter
from calibre.db.tables import ONE_ONE, MANY_ONE, MANY_MANY, null from calibre.db.tables import ONE_ONE, MANY_ONE, MANY_MANY, null
from calibre.db.write import Writer from calibre.db.write import Writer
from calibre.ebooks.metadata import title_sort from calibre.ebooks.metadata import title_sort, author_to_author_sort
from calibre.utils.config_base import tweaks from calibre.utils.config_base import tweaks
from calibre.utils.icu import sort_key from calibre.utils.icu import sort_key
from calibre.utils.date import UNDEFINED_DATE from calibre.utils.date import UNDEFINED_DATE
@ -48,6 +48,10 @@ class Field(object):
self._sort_key = lambda x:sort_key(calibre_langcode_to_name(x)) self._sort_key = lambda x:sort_key(calibre_langcode_to_name(x))
self.is_multiple = (bool(self.metadata['is_multiple']) or self.name == self.is_multiple = (bool(self.metadata['is_multiple']) or self.name ==
'formats') 'formats')
self.sort_sort_key = True
if self.is_multiple and '&' in self.metadata['is_multiple']['list_to_ui']:
self._sort_key = lambda x: sort_key(author_to_author_sort(x))
self.sort_sort_key = False
self.default_value = {} if name == 'identifiers' else () if self.is_multiple else None self.default_value = {} if name == 'identifiers' else () if self.is_multiple else None
self.category_formatter = type(u'') self.category_formatter = type(u'')
if dt == 'rating': if dt == 'rating':
@ -378,9 +382,9 @@ class ManyToManyField(Field):
all_cids = set() all_cids = set()
for cids in ans.itervalues(): for cids in ans.itervalues():
all_cids = all_cids.union(set(cids)) all_cids = all_cids.union(set(cids))
sk_map = {cid: self._sort_key(self.table.id_map[cid]) sk_map = {cid: self._sort_key(self.table.id_map[cid]) for cid in all_cids}
for cid in all_cids} sort_func = (lambda x:tuple(sorted(x))) if self.sort_sort_key else tuple
return {id_: (tuple(sk_map[cid] for cid in cids) if cids else return {id_: (sort_func(sk_map[cid] for cid in cids) if cids else
(self._default_sort_key,)) (self._default_sort_key,))
for id_, cids in ans.iteritems()} for id_, cids in ans.iteritems()}

View File

@ -161,6 +161,25 @@ class ReadingTest(BaseTest):
# Test subsorting # Test subsorting
self.assertEqual([3, 2, 1], cache.multisort([('identifiers', True), self.assertEqual([3, 2, 1], cache.multisort([('identifiers', True),
('title', True)]), 'Subsort failed') ('title', True)]), 'Subsort failed')
# Test sorting of is_multiple fields.
# Author like fields should be sorted by generating sort names from the
# actual values in entry order
for field in ('authors', '#authors'):
self.assertEqual(
cache.set_field(field, {1:('aa bb', 'bb cc', 'cc dd'), 2:('bb aa', 'xx yy'), 3: ('aa bb', 'bb aa')}), {1, 2, 3})
self.assertEqual([2, 3, 1], cache.multisort([(field, True)], ids_to_sort=(1, 2, 3)))
self.assertEqual([1, 3, 2], cache.multisort([(field, False)], ids_to_sort=(1, 2, 3)))
# All other is_multiple fields should be sorted by sorting the values
# for each book and using that as the sort key
for field in ('tags', '#tags'):
self.assertEqual(
cache.set_field(field, {1:('b', 'a'), 2:('c', 'y'), 3: ('b', 'z')}), {1, 2, 3})
self.assertEqual([1, 3, 2], cache.multisort([(field, True)], ids_to_sort=(1, 2, 3)))
self.assertEqual([2, 3, 1], cache.multisort([(field, False)], ids_to_sort=(1, 2, 3)))
# }}} # }}}
def test_get_metadata(self): # {{{ def test_get_metadata(self): # {{{

View File

@ -685,7 +685,7 @@ class BooksModel(QAbstractTableModel): # {{{
elif dt in {'text', 'comments', 'composite', 'enumeration'}: elif dt in {'text', 'comments', 'composite', 'enumeration'}:
if m['is_multiple'] and not field_obj.is_composite: if m['is_multiple'] and not field_obj.is_composite:
jv = m['is_multiple']['list_to_ui'] jv = m['is_multiple']['list_to_ui']
do_sort = field == 'tags' do_sort = '&' not in jv
if do_sort: if do_sort:
def func(idx): def func(idx):
return QVariant(jv.join(sorted(fffunc(field_obj, idfunc(idx), default_value=()), key=sort_key))) return QVariant(jv.join(sorted(fffunc(field_obj, idfunc(idx), default_value=()), key=sort_key)))