Add language dependent sorting for series like columns

This commit is contained in:
Kovid Goyal 2013-01-19 22:37:27 +05:30
parent 3e4d847eee
commit 419f3b6394
4 changed files with 68 additions and 40 deletions

View File

@ -269,11 +269,11 @@ class Cache(object):
return () return ()
@read_api @read_api
def all_book_ids(self): def all_book_ids(self, type=frozenset):
''' '''
Frozen set of all known book ids. Frozen set of all known book ids.
''' '''
return frozenset(self.fields['uuid']) return type(self.fields['uuid'])
@read_api @read_api
def all_field_ids(self, name): def all_field_ids(self, name):
@ -316,6 +316,10 @@ class Cache(object):
self.format_metadata_cache[book_id][fmt] = ans self.format_metadata_cache[book_id][fmt] = ans
return ans return ans
@read_api
def pref(self, name):
return self.backend.prefs[name]
@api @api
def get_metadata(self, book_id, def get_metadata(self, book_id,
get_cover=False, get_user_categories=True, cover_as_data=False): get_cover=False, get_user_categories=True, cover_as_data=False):
@ -378,17 +382,21 @@ class Cache(object):
all_book_ids = frozenset(self._all_book_ids() if ids_to_sort is None all_book_ids = frozenset(self._all_book_ids() if ids_to_sort is None
else ids_to_sort) else ids_to_sort)
get_metadata = partial(self._get_metadata, get_user_categories=False) get_metadata = partial(self._get_metadata, get_user_categories=False)
def get_lang(book_id):
ans = self._field_for('languages', book_id)
return ans[0] if ans else None
fm = {'title':'sort', 'authors':'author_sort'} fm = {'title':'sort', 'authors':'author_sort'}
def sort_key(field): def sort_key(field):
'Handle series type fields' 'Handle series type fields'
ans = self.fields[fm.get(field, field)].sort_keys_for_books(get_metadata,
all_book_ids)
idx = field + '_index' idx = field + '_index'
if idx in self.fields: is_series = idx in self.fields
idx_ans = self.fields[idx].sort_keys_for_books(get_metadata, ans = self.fields[fm.get(field, field)].sort_keys_for_books(
all_book_ids) get_metadata, get_lang, all_book_ids,)
if is_series:
idx_ans = self.fields[idx].sort_keys_for_books(
get_metadata, get_lang, all_book_ids)
ans = {k:(v, idx_ans[k]) for k, v in ans.iteritems()} ans = {k:(v, idx_ans[k]) for k, v in ans.iteritems()}
return ans return ans

View File

@ -11,6 +11,8 @@ __docformat__ = 'restructuredtext en'
from threading import Lock from threading import Lock
from calibre.db.tables import ONE_ONE, MANY_ONE, MANY_MANY from calibre.db.tables import ONE_ONE, MANY_ONE, MANY_MANY
from calibre.ebooks.metadata import title_sort
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
from calibre.utils.localization import calibre_langcode_to_name from calibre.utils.localization import calibre_langcode_to_name
@ -72,7 +74,7 @@ class Field(object):
''' '''
return iter(()) return iter(())
def sort_keys_for_books(self, get_metadata, all_book_ids): def sort_keys_for_books(self, get_metadata, get_lang, all_book_ids):
''' '''
Return a mapping of book_id -> sort_key. The sort key is suitable for 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 use in sorting the list of all books by this field, via the python cmp
@ -96,7 +98,7 @@ class OneToOneField(Field):
def __iter__(self): def __iter__(self):
return self.table.book_col_map.iterkeys() return self.table.book_col_map.iterkeys()
def sort_keys_for_books(self, get_metadata, all_book_ids): def sort_keys_for_books(self, get_metadata, get_lang, all_book_ids):
return {id_ : self._sort_key(self.table.book_col_map.get(id_, return {id_ : self._sort_key(self.table.book_col_map.get(id_,
self._default_sort_key)) for id_ in all_book_ids} self._default_sort_key)) for id_ in all_book_ids}
@ -133,7 +135,7 @@ class CompositeField(OneToOneField):
ans = mi.get('#'+self.metadata['label']) ans = mi.get('#'+self.metadata['label'])
return ans return ans
def sort_keys_for_books(self, get_metadata, all_book_ids): def sort_keys_for_books(self, get_metadata, get_lang, all_book_ids):
return {id_ : sort_key(self.get_value_with_cache(id_, get_metadata)) for id_ in return {id_ : sort_key(self.get_value_with_cache(id_, get_metadata)) for id_ in
all_book_ids} all_book_ids}
@ -170,7 +172,7 @@ class OnDeviceField(OneToOneField):
def __iter__(self): def __iter__(self):
return iter(()) return iter(())
def sort_keys_for_books(self, get_metadata, all_book_ids): def sort_keys_for_books(self, get_metadata, get_lang, all_book_ids):
return {id_ : self.for_book(id_) for id_ in return {id_ : self.for_book(id_) for id_ in
all_book_ids} all_book_ids}
@ -196,7 +198,7 @@ class ManyToOneField(Field):
def __iter__(self): def __iter__(self):
return self.table.id_map.iterkeys() return self.table.id_map.iterkeys()
def sort_keys_for_books(self, get_metadata, all_book_ids): def sort_keys_for_books(self, get_metadata, get_lang, all_book_ids):
ans = {id_ : self.table.book_col_map.get(id_, None) ans = {id_ : self.table.book_col_map.get(id_, None)
for id_ in all_book_ids} for id_ in all_book_ids}
sk_map = {cid : (self._default_sort_key if cid is None else sk_map = {cid : (self._default_sort_key if cid is None else
@ -227,7 +229,7 @@ class ManyToManyField(Field):
def __iter__(self): def __iter__(self):
return self.table.id_map.iterkeys() return self.table.id_map.iterkeys()
def sort_keys_for_books(self, get_metadata, all_book_ids): def sort_keys_for_books(self, get_metadata, get_lang, all_book_ids):
ans = {id_ : self.table.book_col_map.get(id_, ()) ans = {id_ : self.table.book_col_map.get(id_, ())
for id_ in all_book_ids} for id_ in all_book_ids}
all_cids = set() all_cids = set()
@ -248,7 +250,7 @@ class IdentifiersField(ManyToManyField):
ids = default_value ids = default_value
return ids return ids
def sort_keys_for_books(self, get_metadata, all_book_ids): def sort_keys_for_books(self, get_metadata, get_lang, all_book_ids):
'Sort by identifier keys' 'Sort by identifier keys'
ans = {id_ : self.table.book_col_map.get(id_, ()) ans = {id_ : self.table.book_col_map.get(id_, ())
for id_ in all_book_ids} for id_ in all_book_ids}
@ -274,6 +276,21 @@ class FormatsField(ManyToManyField):
def format_fname(self, book_id, fmt): def format_fname(self, book_id, fmt):
return self.table.fname_map[book_id][fmt.upper()] return self.table.fname_map[book_id][fmt.upper()]
class SeriesField(ManyToOneField):
def sort_key_for_series(self, book_id, get_lang, series_sort_order):
sid = self.table.book_col_map.get(book_id, None)
if sid is None:
return self._default_sort_key
return self._sort_key(title_sort(self.table.id_map[sid],
order=series_sort_order,
lang=get_lang(book_id)))
def sort_keys_for_books(self, get_metadata, get_lang, all_book_ids):
sso = tweaks['title_series_sorting']
return {book_id:self.sort_key_for_series(book_id, get_lang, sso) for book_id
in all_book_ids}
def create_field(name, table): def create_field(name, table):
cls = { cls = {
ONE_ONE : OneToOneField, ONE_ONE : OneToOneField,
@ -290,5 +307,7 @@ def create_field(name, table):
cls = IdentifiersField cls = IdentifiersField
elif table.metadata['datatype'] == 'composite': elif table.metadata['datatype'] == 'composite':
cls = CompositeField cls = CompositeField
elif table.metadata['datatype'] == 'series':
cls = SeriesField
return cls(name, table) return cls(name, table)

Binary file not shown.

View File

@ -63,7 +63,7 @@ class ReadingTest(BaseTest):
'sort': 'One', 'sort': 'One',
'authors': ('Author One',), 'authors': ('Author One',),
'author_sort': 'One, Author', 'author_sort': 'One, Author',
'series' : 'Series One', 'series' : 'A Series One',
'series_index': 1.0, 'series_index': 1.0,
'tags':('Tag Two', 'Tag One'), 'tags':('Tag Two', 'Tag One'),
'formats': (), 'formats': (),
@ -92,7 +92,7 @@ class ReadingTest(BaseTest):
'sort': 'Title Two', 'sort': 'Title Two',
'authors': ('Author Two', 'Author One'), 'authors': ('Author Two', 'Author One'),
'author_sort': 'Two, Author & One, Author', 'author_sort': 'Two, Author & One, Author',
'series' : 'Series One', 'series' : 'A Series One',
'series_index': 2.0, 'series_index': 2.0,
'rating': 6.0, 'rating': 6.0,
'tags': ('Tag One',), 'tags': ('Tag One',),
@ -130,30 +130,31 @@ class ReadingTest(BaseTest):
'Test sorting' 'Test sorting'
cache = self.init_cache(self.library_path) cache = self.init_cache(self.library_path)
for field, order in { for field, order in {
'title' : [2, 1, 3], 'title' : [2, 1, 3],
'authors': [2, 1, 3], 'authors': [2, 1, 3],
'series' : [3, 2, 1], 'series' : [3, 1, 2],
'tags' : [3, 1, 2], 'tags' : [3, 1, 2],
'rating' : [3, 2, 1], 'rating' : [3, 2, 1],
# 'identifiers': [3, 2, 1], There is no stable sort since 1 and # 'identifiers': [3, 2, 1], There is no stable sort since 1 and
# 2 have the same identifier keys # 2 have the same identifier keys
# TODO: Add an empty book to the db and ensure that empty # 'last_modified': [3, 2, 1], There is no stable sort as two
# fields sort the same as they do in db2 # records have the exact same value
'timestamp': [2, 1, 3], 'timestamp': [2, 1, 3],
'pubdate' : [1, 2, 3], 'pubdate' : [1, 2, 3],
'publisher': [3, 2, 1], 'publisher': [3, 2, 1],
'last_modified': [2, 1, 3], 'languages': [3, 2, 1],
'languages': [3, 2, 1], 'comments': [3, 2, 1],
'comments': [3, 2, 1], '#enum' : [3, 2, 1],
'#enum' : [3, 2, 1], '#authors' : [3, 2, 1],
'#authors' : [3, 2, 1], '#date': [3, 1, 2],
'#date': [3, 1, 2], '#rating':[3, 2, 1],
'#rating':[3, 2, 1], '#series':[3, 2, 1],
'#series':[3, 2, 1], '#tags':[3, 2, 1],
'#tags':[3, 2, 1], '#yesno':[3, 1, 2],
'#yesno':[3, 1, 2], '#comments':[3, 2, 1],
'#comments':[3, 2, 1], # TODO: Add an empty book to the db and ensure that empty
}.iteritems(): # fields sort the same as they do in db2
}.iteritems():
x = list(reversed(order)) x = list(reversed(order))
self.assertEqual(order, cache.multisort([(field, True)], self.assertEqual(order, cache.multisort([(field, True)],
ids_to_sort=x), ids_to_sort=x),