mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Add language dependent sorting for series like columns
This commit is contained in:
parent
3e4d847eee
commit
419f3b6394
@ -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
|
||||||
|
|
||||||
|
@ -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.
@ -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),
|
||||||
|
Loading…
x
Reference in New Issue
Block a user