mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
1) add the composite field custom datatype
2) clean up content server code so it uses the new formatting facilities
This commit is contained in:
parent
69d021a542
commit
7881286274
@ -137,7 +137,6 @@ class CollectionsBookList(BookList):
|
|||||||
# For existing books, modify the collections only if the user
|
# For existing books, modify the collections only if the user
|
||||||
# specified 'on_connect'
|
# specified 'on_connect'
|
||||||
attrs = collection_attributes
|
attrs = collection_attributes
|
||||||
meta_vals = book.get_all_non_none_attributes()
|
|
||||||
for attr in attrs:
|
for attr in attrs:
|
||||||
attr = attr.strip()
|
attr = attr.strip()
|
||||||
ign, val, orig_val, fm = book.format_field_extended(attr)
|
ign, val, orig_val, fm = book.format_field_extended(attr)
|
||||||
@ -166,7 +165,7 @@ class CollectionsBookList(BookList):
|
|||||||
continue
|
continue
|
||||||
if attr == 'series' or \
|
if attr == 'series' or \
|
||||||
('series' in collection_attributes and
|
('series' in collection_attributes and
|
||||||
meta_vals.get('series', None) == category):
|
book.get('series', None) == category):
|
||||||
is_series = True
|
is_series = True
|
||||||
cat_name = self.compute_category_name(attr, category, fm)
|
cat_name = self.compute_category_name(attr, category, fm)
|
||||||
if cat_name not in collections:
|
if cat_name not in collections:
|
||||||
@ -177,10 +176,10 @@ class CollectionsBookList(BookList):
|
|||||||
collections_lpaths[cat_name].add(lpath)
|
collections_lpaths[cat_name].add(lpath)
|
||||||
if is_series:
|
if is_series:
|
||||||
collections[cat_name].append(
|
collections[cat_name].append(
|
||||||
(book, meta_vals.get(attr+'_index', sys.maxint)))
|
(book, book.get(attr+'_index', sys.maxint)))
|
||||||
else:
|
else:
|
||||||
collections[cat_name].append(
|
collections[cat_name].append(
|
||||||
(book, meta_vals.get('title_sort', 'zzzz')))
|
(book, book.get('title_sort', 'zzzz')))
|
||||||
# Sort collections
|
# Sort collections
|
||||||
result = {}
|
result = {}
|
||||||
for category, books in collections.items():
|
for category, books in collections.items():
|
||||||
|
@ -81,9 +81,8 @@ DEVICE_METADATA_FIELDS = frozenset([
|
|||||||
|
|
||||||
CALIBRE_METADATA_FIELDS = frozenset([
|
CALIBRE_METADATA_FIELDS = frozenset([
|
||||||
'application_id', # An application id, currently set to the db_id.
|
'application_id', # An application id, currently set to the db_id.
|
||||||
# the calibre primary key of the item.
|
|
||||||
'db_id', # the calibre primary key of the item.
|
'db_id', # the calibre primary key of the item.
|
||||||
# TODO: NEWMETA: May want to remove once Sony's no longer use it
|
'formats', # list of formats (extensions) for this book
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -124,5 +123,5 @@ SERIALIZABLE_FIELDS = SOCIAL_METADATA_FIELDS.union(
|
|||||||
PUBLICATION_METADATA_FIELDS).union(
|
PUBLICATION_METADATA_FIELDS).union(
|
||||||
CALIBRE_METADATA_FIELDS).union(
|
CALIBRE_METADATA_FIELDS).union(
|
||||||
DEVICE_METADATA_FIELDS) - \
|
DEVICE_METADATA_FIELDS) - \
|
||||||
frozenset(['device_collections'])
|
frozenset(['device_collections', 'formats'])
|
||||||
# device_collections is rebuilt when needed
|
# these are rebuilt when needed
|
||||||
|
@ -5,8 +5,7 @@ __license__ = 'GPL v3'
|
|||||||
__copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>'
|
__copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>'
|
||||||
__docformat__ = 'restructuredtext en'
|
__docformat__ = 'restructuredtext en'
|
||||||
|
|
||||||
import copy
|
import copy, re, string, traceback
|
||||||
import traceback
|
|
||||||
|
|
||||||
from calibre import prints
|
from calibre import prints
|
||||||
from calibre.ebooks.metadata.book import SC_COPYABLE_FIELDS
|
from calibre.ebooks.metadata.book import SC_COPYABLE_FIELDS
|
||||||
@ -33,6 +32,23 @@ NULL_VALUES = {
|
|||||||
|
|
||||||
field_metadata = FieldMetadata()
|
field_metadata = FieldMetadata()
|
||||||
|
|
||||||
|
class SafeFormat(string.Formatter):
|
||||||
|
'''
|
||||||
|
Provides a format function that substitutes '' for any missing value
|
||||||
|
'''
|
||||||
|
def get_value(self, key, args, mi):
|
||||||
|
ign, v = mi.format_field(key, series_with_index=False)
|
||||||
|
if v is None:
|
||||||
|
return ''
|
||||||
|
return v
|
||||||
|
|
||||||
|
composite_formatter = SafeFormat()
|
||||||
|
compress_spaces = re.compile(r'\s+')
|
||||||
|
|
||||||
|
def format_composite(x, mi):
|
||||||
|
ans = composite_formatter.vformat(x, [], mi).strip()
|
||||||
|
return compress_spaces.sub(' ', ans)
|
||||||
|
|
||||||
class Metadata(object):
|
class Metadata(object):
|
||||||
|
|
||||||
'''
|
'''
|
||||||
@ -343,18 +359,19 @@ class Metadata(object):
|
|||||||
def format_rating(self):
|
def format_rating(self):
|
||||||
return unicode(self.rating)
|
return unicode(self.rating)
|
||||||
|
|
||||||
def format_field(self, key):
|
def format_field(self, key, series_with_index=True):
|
||||||
name, val, ign, ign = self.format_field_extended(key)
|
name, val, ign, ign = self.format_field_extended(key, series_with_index)
|
||||||
return (name, val)
|
return (name, val)
|
||||||
|
|
||||||
def format_field_extended(self, key):
|
def format_field_extended(self, key, series_with_index=True):
|
||||||
from calibre.ebooks.metadata import authors_to_string
|
from calibre.ebooks.metadata import authors_to_string
|
||||||
'''
|
'''
|
||||||
returns the tuple (field_name, formatted_value)
|
returns the tuple (field_name, formatted_value)
|
||||||
'''
|
'''
|
||||||
if key in self.user_metadata_keys:
|
if key in self.user_metadata_keys:
|
||||||
res = self.get(key, None)
|
res = self.get(key, None)
|
||||||
if res is None or res == '':
|
cmeta = self.get_user_metadata(key, make_copy=False)
|
||||||
|
if cmeta['datatype'] != 'composite' and (res is None or res == ''):
|
||||||
return (None, None, None, None)
|
return (None, None, None, None)
|
||||||
orig_res = res
|
orig_res = res
|
||||||
cmeta = self.get_user_metadata(key, make_copy=False)
|
cmeta = self.get_user_metadata(key, make_copy=False)
|
||||||
@ -362,13 +379,15 @@ class Metadata(object):
|
|||||||
datatype = cmeta['datatype']
|
datatype = cmeta['datatype']
|
||||||
if datatype == 'text' and cmeta['is_multiple']:
|
if datatype == 'text' and cmeta['is_multiple']:
|
||||||
res = u', '.join(res)
|
res = u', '.join(res)
|
||||||
elif datatype == 'series':
|
elif datatype == 'series' and series_with_index:
|
||||||
res = res + \
|
res = res + \
|
||||||
' [%s]'%self.format_series_index(val=self.get_extra(key))
|
' [%s]'%self.format_series_index(val=self.get_extra(key))
|
||||||
elif datatype == 'datetime':
|
elif datatype == 'datetime':
|
||||||
res = format_date(res, cmeta['display'].get('date_format','dd MMM yyyy'))
|
res = format_date(res, cmeta['display'].get('date_format','dd MMM yyyy'))
|
||||||
elif datatype == 'bool':
|
elif datatype == 'bool':
|
||||||
res = _('Yes') if res else _('No')
|
res = _('Yes') if res else _('No')
|
||||||
|
elif datatype == 'composite':
|
||||||
|
res = format_composite(cmeta['display']['composite_template'], self)
|
||||||
return (name, res, orig_res, cmeta)
|
return (name, res, orig_res, cmeta)
|
||||||
|
|
||||||
if key in field_metadata and field_metadata[key]['kind'] == 'field':
|
if key in field_metadata and field_metadata[key]['kind'] == 'field':
|
||||||
@ -383,7 +402,7 @@ class Metadata(object):
|
|||||||
res = authors_to_string(res)
|
res = authors_to_string(res)
|
||||||
elif datatype == 'text' and fmeta['is_multiple']:
|
elif datatype == 'text' and fmeta['is_multiple']:
|
||||||
res = u', '.join(res)
|
res = u', '.join(res)
|
||||||
elif datatype == 'series':
|
elif datatype == 'series' and series_with_index:
|
||||||
res = res + ' [%s]'%self.format_series_index()
|
res = res + ' [%s]'%self.format_series_index()
|
||||||
elif datatype == 'datetime':
|
elif datatype == 'datetime':
|
||||||
res = format_date(res, fmeta['display'].get('date_format','dd MMM yyyy'))
|
res = format_date(res, fmeta['display'].get('date_format','dd MMM yyyy'))
|
||||||
|
@ -86,6 +86,7 @@ class BooksModel(QAbstractTableModel): # {{{
|
|||||||
self.last_search = '' # The last search performed on this model
|
self.last_search = '' # The last search performed on this model
|
||||||
self.column_map = []
|
self.column_map = []
|
||||||
self.headers = {}
|
self.headers = {}
|
||||||
|
self.metadata_cache = {}
|
||||||
self.alignment_map = {}
|
self.alignment_map = {}
|
||||||
self.buffer_size = buffer
|
self.buffer_size = buffer
|
||||||
self.cover_cache = None
|
self.cover_cache = None
|
||||||
@ -114,6 +115,16 @@ class BooksModel(QAbstractTableModel): # {{{
|
|||||||
def clear_caches(self):
|
def clear_caches(self):
|
||||||
if self.cover_cache:
|
if self.cover_cache:
|
||||||
self.cover_cache.clear_cache()
|
self.cover_cache.clear_cache()
|
||||||
|
self.metadata_cache = {}
|
||||||
|
|
||||||
|
def get_cached_metadata(self, idx):
|
||||||
|
if idx not in self.metadata_cache:
|
||||||
|
self.metadata_cache[idx] = self.db.get_metadata(idx)
|
||||||
|
return self.metadata_cache[idx]
|
||||||
|
|
||||||
|
def remove_cached_metadata(self, idx):
|
||||||
|
if idx in self.metadata_cache:
|
||||||
|
del self.metadata_cache[idx]
|
||||||
|
|
||||||
def read_config(self):
|
def read_config(self):
|
||||||
self.use_roman_numbers = config['use_roman_numerals_for_series_number']
|
self.use_roman_numbers = config['use_roman_numerals_for_series_number']
|
||||||
@ -146,6 +157,7 @@ class BooksModel(QAbstractTableModel): # {{{
|
|||||||
elif col in self.custom_columns:
|
elif col in self.custom_columns:
|
||||||
self.headers[col] = self.custom_columns[col]['name']
|
self.headers[col] = self.custom_columns[col]['name']
|
||||||
|
|
||||||
|
self.metadata_cache = {}
|
||||||
self.build_data_convertors()
|
self.build_data_convertors()
|
||||||
self.reset()
|
self.reset()
|
||||||
self.database_changed.emit(db)
|
self.database_changed.emit(db)
|
||||||
@ -159,11 +171,13 @@ class BooksModel(QAbstractTableModel): # {{{
|
|||||||
db.add_listener(refresh_cover)
|
db.add_listener(refresh_cover)
|
||||||
|
|
||||||
def refresh_ids(self, ids, current_row=-1):
|
def refresh_ids(self, ids, current_row=-1):
|
||||||
|
self.metadata_cache = {}
|
||||||
rows = self.db.refresh_ids(ids)
|
rows = self.db.refresh_ids(ids)
|
||||||
if rows:
|
if rows:
|
||||||
self.refresh_rows(rows, current_row=current_row)
|
self.refresh_rows(rows, current_row=current_row)
|
||||||
|
|
||||||
def refresh_rows(self, rows, current_row=-1):
|
def refresh_rows(self, rows, current_row=-1):
|
||||||
|
self.metadata_cache = {}
|
||||||
for row in rows:
|
for row in rows:
|
||||||
if row == current_row:
|
if row == current_row:
|
||||||
self.new_bookdisplay_data.emit(
|
self.new_bookdisplay_data.emit(
|
||||||
@ -193,6 +207,7 @@ class BooksModel(QAbstractTableModel): # {{{
|
|||||||
return ret
|
return ret
|
||||||
|
|
||||||
def count_changed(self, *args):
|
def count_changed(self, *args):
|
||||||
|
self.metadata_cache = {}
|
||||||
self.count_changed_signal.emit(self.db.count())
|
self.count_changed_signal.emit(self.db.count())
|
||||||
|
|
||||||
def row_indices(self, index):
|
def row_indices(self, index):
|
||||||
@ -262,6 +277,7 @@ class BooksModel(QAbstractTableModel): # {{{
|
|||||||
self.sorting_done.emit(self.db.index)
|
self.sorting_done.emit(self.db.index)
|
||||||
|
|
||||||
def refresh(self, reset=True):
|
def refresh(self, reset=True):
|
||||||
|
self.metadata_cache = {}
|
||||||
self.db.refresh(field=None)
|
self.db.refresh(field=None)
|
||||||
self.resort(reset=reset)
|
self.resort(reset=reset)
|
||||||
|
|
||||||
@ -318,7 +334,7 @@ class BooksModel(QAbstractTableModel): # {{{
|
|||||||
data[_('Series')] = \
|
data[_('Series')] = \
|
||||||
_('Book <font face="serif">%s</font> of %s.')%\
|
_('Book <font face="serif">%s</font> of %s.')%\
|
||||||
(sidx, prepare_string_for_xml(series))
|
(sidx, prepare_string_for_xml(series))
|
||||||
mi = self.db.get_metadata(idx)
|
mi = self.get_cached_metadata(idx)
|
||||||
for key in mi.user_metadata_keys:
|
for key in mi.user_metadata_keys:
|
||||||
name, val = mi.format_field(key)
|
name, val = mi.format_field(key)
|
||||||
if val is not None:
|
if val is not None:
|
||||||
@ -327,6 +343,7 @@ class BooksModel(QAbstractTableModel): # {{{
|
|||||||
|
|
||||||
def set_cache(self, idx):
|
def set_cache(self, idx):
|
||||||
l, r = 0, self.count()-1
|
l, r = 0, self.count()-1
|
||||||
|
self.remove_cached_metadata(idx)
|
||||||
if self.cover_cache is not None:
|
if self.cover_cache is not None:
|
||||||
l = max(l, idx-self.buffer_size)
|
l = max(l, idx-self.buffer_size)
|
||||||
r = min(r, idx+self.buffer_size)
|
r = min(r, idx+self.buffer_size)
|
||||||
@ -586,6 +603,10 @@ class BooksModel(QAbstractTableModel): # {{{
|
|||||||
def number_type(r, idx=-1):
|
def number_type(r, idx=-1):
|
||||||
return QVariant(self.db.data[r][idx])
|
return QVariant(self.db.data[r][idx])
|
||||||
|
|
||||||
|
def composite_type(r, key=None):
|
||||||
|
mi = self.get_cached_metadata(r)
|
||||||
|
return QVariant(mi.format_field(key)[1])
|
||||||
|
|
||||||
self.dc = {
|
self.dc = {
|
||||||
'title' : functools.partial(text_type,
|
'title' : functools.partial(text_type,
|
||||||
idx=self.db.field_metadata['title']['rec_index'], mult=False),
|
idx=self.db.field_metadata['title']['rec_index'], mult=False),
|
||||||
@ -620,7 +641,8 @@ class BooksModel(QAbstractTableModel): # {{{
|
|||||||
idx = self.custom_columns[col]['rec_index']
|
idx = self.custom_columns[col]['rec_index']
|
||||||
datatype = self.custom_columns[col]['datatype']
|
datatype = self.custom_columns[col]['datatype']
|
||||||
if datatype in ('text', 'comments'):
|
if datatype in ('text', 'comments'):
|
||||||
self.dc[col] = functools.partial(text_type, idx=idx, mult=self.custom_columns[col]['is_multiple'])
|
self.dc[col] = functools.partial(text_type, idx=idx,
|
||||||
|
mult=self.custom_columns[col]['is_multiple'])
|
||||||
elif datatype in ('int', 'float'):
|
elif datatype in ('int', 'float'):
|
||||||
self.dc[col] = functools.partial(number_type, idx=idx)
|
self.dc[col] = functools.partial(number_type, idx=idx)
|
||||||
elif datatype == 'datetime':
|
elif datatype == 'datetime':
|
||||||
@ -628,13 +650,15 @@ class BooksModel(QAbstractTableModel): # {{{
|
|||||||
elif datatype == 'bool':
|
elif datatype == 'bool':
|
||||||
self.dc[col] = functools.partial(bool_type, idx=idx)
|
self.dc[col] = functools.partial(bool_type, idx=idx)
|
||||||
self.dc_decorator[col] = functools.partial(
|
self.dc_decorator[col] = functools.partial(
|
||||||
bool_type_decorator, idx=idx,
|
bool_type_decorator, idx=idx,
|
||||||
bool_cols_are_tristate=tweaks['bool_custom_columns_are_tristate'] == 'yes')
|
bool_cols_are_tristate=tweaks['bool_custom_columns_are_tristate'] == 'yes')
|
||||||
elif datatype == 'rating':
|
elif datatype == 'rating':
|
||||||
self.dc[col] = functools.partial(rating_type, idx=idx)
|
self.dc[col] = functools.partial(rating_type, idx=idx)
|
||||||
elif datatype == 'series':
|
elif datatype == 'series':
|
||||||
self.dc[col] = functools.partial(series_type, idx=idx,
|
self.dc[col] = functools.partial(series_type, idx=idx,
|
||||||
siix=self.db.field_metadata.cc_series_index_column_for(col))
|
siix=self.db.field_metadata.cc_series_index_column_for(col))
|
||||||
|
elif datatype == 'composite':
|
||||||
|
self.dc[col] = functools.partial(composite_type, key=col)
|
||||||
else:
|
else:
|
||||||
print 'What type is this?', col, datatype
|
print 'What type is this?', col, datatype
|
||||||
# build a index column to data converter map, to remove the string lookup in the data loop
|
# build a index column to data converter map, to remove the string lookup in the data loop
|
||||||
@ -729,6 +753,7 @@ class BooksModel(QAbstractTableModel): # {{{
|
|||||||
if role == Qt.EditRole:
|
if role == Qt.EditRole:
|
||||||
row, col = index.row(), index.column()
|
row, col = index.row(), index.column()
|
||||||
column = self.column_map[col]
|
column = self.column_map[col]
|
||||||
|
self.remove_cached_metadata(row)
|
||||||
if self.is_custom_column(column):
|
if self.is_custom_column(column):
|
||||||
if not self.set_custom_column_data(row, column, value):
|
if not self.set_custom_column_data(row, column, value):
|
||||||
return False
|
return False
|
||||||
|
@ -155,7 +155,8 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
|
|||||||
name=self.custcols[c]['name'],
|
name=self.custcols[c]['name'],
|
||||||
datatype=self.custcols[c]['datatype'],
|
datatype=self.custcols[c]['datatype'],
|
||||||
is_multiple=self.custcols[c]['is_multiple'],
|
is_multiple=self.custcols[c]['is_multiple'],
|
||||||
display = self.custcols[c]['display'])
|
display = self.custcols[c]['display'],
|
||||||
|
editable = self.custcols[c]['editable'])
|
||||||
must_restart = True
|
must_restart = True
|
||||||
elif '*deleteme' in self.custcols[c]:
|
elif '*deleteme' in self.custcols[c]:
|
||||||
db.delete_custom_column(label=self.custcols[c]['label'])
|
db.delete_custom_column(label=self.custcols[c]['label'])
|
||||||
|
@ -38,6 +38,8 @@ class CreateCustomColumn(QDialog, Ui_QCreateCustomColumn):
|
|||||||
'is_multiple':False},
|
'is_multiple':False},
|
||||||
8:{'datatype':'bool',
|
8:{'datatype':'bool',
|
||||||
'text':_('Yes/No'), 'is_multiple':False},
|
'text':_('Yes/No'), 'is_multiple':False},
|
||||||
|
8:{'datatype':'composite',
|
||||||
|
'text':_('Field built from other fields'), 'is_multiple':False},
|
||||||
}
|
}
|
||||||
|
|
||||||
def __init__(self, parent, editing, standard_colheads, standard_colnames):
|
def __init__(self, parent, editing, standard_colheads, standard_colnames):
|
||||||
@ -86,6 +88,8 @@ class CreateCustomColumn(QDialog, Ui_QCreateCustomColumn):
|
|||||||
if ct == 'datetime':
|
if ct == 'datetime':
|
||||||
if c['display'].get('date_format', None):
|
if c['display'].get('date_format', None):
|
||||||
self.date_format_box.setText(c['display'].get('date_format', ''))
|
self.date_format_box.setText(c['display'].get('date_format', ''))
|
||||||
|
elif ct == 'composite':
|
||||||
|
self.composite_box.setText(c['display'].get('composite_template', ''))
|
||||||
self.datatype_changed()
|
self.datatype_changed()
|
||||||
self.exec_()
|
self.exec_()
|
||||||
|
|
||||||
@ -94,9 +98,10 @@ class CreateCustomColumn(QDialog, Ui_QCreateCustomColumn):
|
|||||||
col_type = self.column_types[self.column_type_box.currentIndex()]['datatype']
|
col_type = self.column_types[self.column_type_box.currentIndex()]['datatype']
|
||||||
except:
|
except:
|
||||||
col_type = None
|
col_type = None
|
||||||
df_visible = col_type == 'datetime'
|
|
||||||
for x in ('box', 'default_label', 'label'):
|
for x in ('box', 'default_label', 'label'):
|
||||||
getattr(self, 'date_format_'+x).setVisible(df_visible)
|
getattr(self, 'date_format_'+x).setVisible(col_type == 'datetime')
|
||||||
|
for x in ('box', 'default_label', 'label'):
|
||||||
|
getattr(self, 'composite_'+x).setVisible(col_type == 'composite')
|
||||||
|
|
||||||
|
|
||||||
def accept(self):
|
def accept(self):
|
||||||
@ -122,6 +127,7 @@ class CreateCustomColumn(QDialog, Ui_QCreateCustomColumn):
|
|||||||
bad_col = True
|
bad_col = True
|
||||||
if bad_col:
|
if bad_col:
|
||||||
return self.simple_error('', _('The lookup name %s is already used')%col)
|
return self.simple_error('', _('The lookup name %s is already used')%col)
|
||||||
|
|
||||||
bad_head = False
|
bad_head = False
|
||||||
for t in self.parent.custcols:
|
for t in self.parent.custcols:
|
||||||
if self.parent.custcols[t]['name'] == col_heading:
|
if self.parent.custcols[t]['name'] == col_heading:
|
||||||
@ -133,12 +139,20 @@ class CreateCustomColumn(QDialog, Ui_QCreateCustomColumn):
|
|||||||
if bad_head:
|
if bad_head:
|
||||||
return self.simple_error('', _('The heading %s is already used')%col_heading)
|
return self.simple_error('', _('The heading %s is already used')%col_heading)
|
||||||
|
|
||||||
date_format = {}
|
display_dict = {}
|
||||||
if col_type == 'datetime':
|
if col_type == 'datetime':
|
||||||
if self.date_format_box.text():
|
if self.date_format_box.text():
|
||||||
date_format = {'date_format':unicode(self.date_format_box.text())}
|
display_dict = {'date_format':unicode(self.date_format_box.text())}
|
||||||
else:
|
else:
|
||||||
date_format = {'date_format': None}
|
display_dict = {'date_format': None}
|
||||||
|
|
||||||
|
if col_type == 'composite':
|
||||||
|
if not self.composite_box.text():
|
||||||
|
return self.simple_error('', _('You must enter a template for composite fields')%col_heading)
|
||||||
|
display_dict = {'composite_template':unicode(self.composite_box.text())}
|
||||||
|
is_editable = False
|
||||||
|
else:
|
||||||
|
is_editable = True
|
||||||
|
|
||||||
db = self.parent.gui.library_view.model().db
|
db = self.parent.gui.library_view.model().db
|
||||||
key = db.field_metadata.custom_field_prefix+col
|
key = db.field_metadata.custom_field_prefix+col
|
||||||
@ -148,8 +162,8 @@ class CreateCustomColumn(QDialog, Ui_QCreateCustomColumn):
|
|||||||
'label':col,
|
'label':col,
|
||||||
'name':col_heading,
|
'name':col_heading,
|
||||||
'datatype':col_type,
|
'datatype':col_type,
|
||||||
'editable':True,
|
'editable':is_editable,
|
||||||
'display':date_format,
|
'display':display_dict,
|
||||||
'normalized':None,
|
'normalized':None,
|
||||||
'colnum':None,
|
'colnum':None,
|
||||||
'is_multiple':is_multiple,
|
'is_multiple':is_multiple,
|
||||||
@ -164,7 +178,7 @@ class CreateCustomColumn(QDialog, Ui_QCreateCustomColumn):
|
|||||||
item.setText(col_heading)
|
item.setText(col_heading)
|
||||||
self.parent.custcols[self.orig_column_name]['label'] = col
|
self.parent.custcols[self.orig_column_name]['label'] = col
|
||||||
self.parent.custcols[self.orig_column_name]['name'] = col_heading
|
self.parent.custcols[self.orig_column_name]['name'] = col_heading
|
||||||
self.parent.custcols[self.orig_column_name]['display'].update(date_format)
|
self.parent.custcols[self.orig_column_name]['display'].update(display_dict)
|
||||||
self.parent.custcols[self.orig_column_name]['*edited'] = True
|
self.parent.custcols[self.orig_column_name]['*edited'] = True
|
||||||
self.parent.custcols[self.orig_column_name]['*must_restart'] = True
|
self.parent.custcols[self.orig_column_name]['*must_restart'] = True
|
||||||
QDialog.accept(self)
|
QDialog.accept(self)
|
||||||
|
@ -147,9 +147,59 @@
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
<item row="5" column="1">
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout_4">
|
||||||
|
<item>
|
||||||
|
<widget class="QLineEdit" name="composite_box">
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<property name="toolTip">
|
||||||
|
<string><p>Field template. Uses the same syntax as save templates.</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="composite_default_label">
|
||||||
|
<property name="toolTip">
|
||||||
|
<string>Similar to save templates. For example, {title} {isbn}</string>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Default: (nothing)</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
<item row="5" column="0">
|
||||||
|
<widget class="QLabel" name="composite_label">
|
||||||
|
<property name="text">
|
||||||
|
<string>&Template</string>
|
||||||
|
</property>
|
||||||
|
<property name="buddy">
|
||||||
|
<cstring>composite_box</cstring>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="10" column="0" colspan="3">
|
||||||
|
<spacer name="verticalSpacer_2">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Vertical</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>20</width>
|
||||||
|
<height>40</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</item>
|
</item>
|
||||||
<item row="3" column="0">
|
<item row="11" column="0">
|
||||||
<widget class="QDialogButtonBox" name="button_box">
|
<widget class="QDialogButtonBox" name="button_box">
|
||||||
<property name="orientation">
|
<property name="orientation">
|
||||||
<enum>Qt::Horizontal</enum>
|
<enum>Qt::Horizontal</enum>
|
||||||
@ -184,6 +234,7 @@
|
|||||||
<tabstop>column_heading_box</tabstop>
|
<tabstop>column_heading_box</tabstop>
|
||||||
<tabstop>column_type_box</tabstop>
|
<tabstop>column_type_box</tabstop>
|
||||||
<tabstop>date_format_box</tabstop>
|
<tabstop>date_format_box</tabstop>
|
||||||
|
<tabstop>composite_box</tabstop>
|
||||||
<tabstop>button_box</tabstop>
|
<tabstop>button_box</tabstop>
|
||||||
</tabstops>
|
</tabstops>
|
||||||
<resources/>
|
<resources/>
|
||||||
|
@ -18,7 +18,7 @@ from calibre.utils.date import parse_date
|
|||||||
class CustomColumns(object):
|
class CustomColumns(object):
|
||||||
|
|
||||||
CUSTOM_DATA_TYPES = frozenset(['rating', 'text', 'comments', 'datetime',
|
CUSTOM_DATA_TYPES = frozenset(['rating', 'text', 'comments', 'datetime',
|
||||||
'int', 'float', 'bool', 'series'])
|
'int', 'float', 'bool', 'series', 'composite'])
|
||||||
|
|
||||||
def custom_table_names(self, num):
|
def custom_table_names(self, num):
|
||||||
return 'custom_column_%d'%num, 'books_custom_column_%d_link'%num
|
return 'custom_column_%d'%num, 'books_custom_column_%d_link'%num
|
||||||
@ -540,7 +540,7 @@ class CustomColumns(object):
|
|||||||
if datatype not in self.CUSTOM_DATA_TYPES:
|
if datatype not in self.CUSTOM_DATA_TYPES:
|
||||||
raise ValueError('%r is not a supported data type'%datatype)
|
raise ValueError('%r is not a supported data type'%datatype)
|
||||||
normalized = datatype not in ('datetime', 'comments', 'int', 'bool',
|
normalized = datatype not in ('datetime', 'comments', 'int', 'bool',
|
||||||
'float')
|
'float', 'composite')
|
||||||
is_multiple = is_multiple and datatype in ('text',)
|
is_multiple = is_multiple and datatype in ('text',)
|
||||||
num = self.conn.execute(
|
num = self.conn.execute(
|
||||||
('INSERT INTO '
|
('INSERT INTO '
|
||||||
@ -551,7 +551,7 @@ class CustomColumns(object):
|
|||||||
|
|
||||||
if datatype in ('rating', 'int'):
|
if datatype in ('rating', 'int'):
|
||||||
dt = 'INT'
|
dt = 'INT'
|
||||||
elif datatype in ('text', 'comments', 'series'):
|
elif datatype in ('text', 'comments', 'series', 'composite'):
|
||||||
dt = 'TEXT'
|
dt = 'TEXT'
|
||||||
elif datatype in ('float',):
|
elif datatype in ('float',):
|
||||||
dt = 'REAL'
|
dt = 'REAL'
|
||||||
|
@ -538,6 +538,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
|
|||||||
mi.pubdate = self.pubdate(idx, index_is_id=index_is_id)
|
mi.pubdate = self.pubdate(idx, index_is_id=index_is_id)
|
||||||
mi.uuid = self.uuid(idx, index_is_id=index_is_id)
|
mi.uuid = self.uuid(idx, index_is_id=index_is_id)
|
||||||
mi.title_sort = self.title_sort(idx, index_is_id=index_is_id)
|
mi.title_sort = self.title_sort(idx, index_is_id=index_is_id)
|
||||||
|
mi.formats = self.formats(idx, index_is_id=index_is_id).split(',')
|
||||||
tags = self.tags(idx, index_is_id=index_is_id)
|
tags = self.tags(idx, index_is_id=index_is_id)
|
||||||
if tags:
|
if tags:
|
||||||
mi.tags = [i.strip() for i in tags.split(',')]
|
mi.tags = [i.strip() for i in tags.split(',')]
|
||||||
|
@ -68,7 +68,7 @@ class FieldMetadata(dict):
|
|||||||
'''
|
'''
|
||||||
|
|
||||||
VALID_DATA_TYPES = frozenset([None, 'rating', 'text', 'comments', 'datetime',
|
VALID_DATA_TYPES = frozenset([None, 'rating', 'text', 'comments', 'datetime',
|
||||||
'int', 'float', 'bool', 'series'])
|
'int', 'float', 'bool', 'series', 'composite'])
|
||||||
|
|
||||||
# Builtin metadata {{{
|
# Builtin metadata {{{
|
||||||
|
|
||||||
|
@ -228,29 +228,19 @@ class MobileServer(object):
|
|||||||
for key in CKEYS:
|
for key in CKEYS:
|
||||||
def concat(name, val):
|
def concat(name, val):
|
||||||
return '%s:#:%s'%(name, unicode(val))
|
return '%s:#:%s'%(name, unicode(val))
|
||||||
val = record[CFM[key]['rec_index']]
|
mi = self.db.get_metadata(record[CFM['id']['rec_index']], index_is_id=True)
|
||||||
if val:
|
name, val = mi.format_field(key)
|
||||||
datatype = CFM[key]['datatype']
|
if val is None:
|
||||||
if datatype in ['comments']:
|
continue
|
||||||
continue
|
datatype = CFM[key]['datatype']
|
||||||
name = CFM[key]['name']
|
if datatype in ['comments']:
|
||||||
if datatype == 'text' and CFM[key]['is_multiple']:
|
continue
|
||||||
book[key] = concat(name,
|
if datatype == 'text' and CFM[key]['is_multiple']:
|
||||||
format_tag_string(val, '|',
|
book[key] = concat(name,
|
||||||
no_tag_count=True))
|
format_tag_string(val, ',',
|
||||||
elif datatype == 'series':
|
no_tag_count=True))
|
||||||
book[key] = concat(name, '%s [%s]'%(val,
|
else:
|
||||||
fmt_sidx(record[CFM.cc_series_index_column_for(key)])))
|
book[key] = concat(name, val)
|
||||||
elif datatype == 'datetime':
|
|
||||||
book[key] = concat(name,
|
|
||||||
format_date(val, CFM[key]['display'].get('date_format','dd MMM yyyy')))
|
|
||||||
elif datatype == 'bool':
|
|
||||||
if val:
|
|
||||||
book[key] = concat(name, __builtin__._('Yes'))
|
|
||||||
else:
|
|
||||||
book[key] = concat(name, __builtin__._('No'))
|
|
||||||
else:
|
|
||||||
book[key] = concat(name, val)
|
|
||||||
|
|
||||||
updated = self.db.last_modified()
|
updated = self.db.last_modified()
|
||||||
|
|
||||||
|
@ -132,7 +132,8 @@ def CATALOG_GROUP_ENTRY(item, category, base_href, version, updated):
|
|||||||
link
|
link
|
||||||
)
|
)
|
||||||
|
|
||||||
def ACQUISITION_ENTRY(item, version, FM, updated, CFM, CKEYS):
|
def ACQUISITION_ENTRY(item, version, db, updated, CFM, CKEYS):
|
||||||
|
FM = db.FIELD_MAP
|
||||||
title = item[FM['title']]
|
title = item[FM['title']]
|
||||||
if not title:
|
if not title:
|
||||||
title = _('Unknown')
|
title = _('Unknown')
|
||||||
@ -157,22 +158,16 @@ def ACQUISITION_ENTRY(item, version, FM, updated, CFM, CKEYS):
|
|||||||
(series,
|
(series,
|
||||||
fmt_sidx(float(item[FM['series_index']]))))
|
fmt_sidx(float(item[FM['series_index']]))))
|
||||||
for key in CKEYS:
|
for key in CKEYS:
|
||||||
val = item[CFM[key]['rec_index']]
|
mi = db.get_metadata(item[CFM['id']['rec_index']], index_is_id=True)
|
||||||
|
name, val = mi.format_field(key)
|
||||||
if val is not None:
|
if val is not None:
|
||||||
name = CFM[key]['name']
|
|
||||||
datatype = CFM[key]['datatype']
|
datatype = CFM[key]['datatype']
|
||||||
if datatype == 'text' and CFM[key]['is_multiple']:
|
if datatype == 'text' and CFM[key]['is_multiple']:
|
||||||
extra.append('%s: %s<br />'%(name, format_tag_string(val, '|',
|
extra.append('%s: %s<br />'%(name, format_tag_string(val, ',',
|
||||||
ignore_max=True,
|
ignore_max=True,
|
||||||
no_tag_count=True)))
|
no_tag_count=True)))
|
||||||
elif datatype == 'series':
|
|
||||||
extra.append('%s: %s [%s]<br />'%(name, val,
|
|
||||||
fmt_sidx(item[CFM.cc_series_index_column_for(key)])))
|
|
||||||
elif datatype == 'datetime':
|
|
||||||
extra.append('%s: %s<br />'%(name,
|
|
||||||
format_date(val, CFM[key]['display'].get('date_format','dd MMM yyyy'))))
|
|
||||||
else:
|
else:
|
||||||
extra.append('%s: %s <br />' % (CFM[key]['name'], val))
|
extra.append('%s: %s<br />'%(name, val))
|
||||||
comments = item[FM['comments']]
|
comments = item[FM['comments']]
|
||||||
if comments:
|
if comments:
|
||||||
comments = comments_to_html(comments)
|
comments = comments_to_html(comments)
|
||||||
@ -280,13 +275,14 @@ class NavFeed(Feed):
|
|||||||
class AcquisitionFeed(NavFeed):
|
class AcquisitionFeed(NavFeed):
|
||||||
|
|
||||||
def __init__(self, updated, id_, items, offsets, page_url, up_url, version,
|
def __init__(self, updated, id_, items, offsets, page_url, up_url, version,
|
||||||
FM, CFM):
|
db):
|
||||||
NavFeed.__init__(self, id_, updated, version, offsets, page_url, up_url)
|
NavFeed.__init__(self, id_, updated, version, offsets, page_url, up_url)
|
||||||
|
CFM = db.field_metadata
|
||||||
CKEYS = [key for key in sorted(CFM.get_custom_fields(),
|
CKEYS = [key for key in sorted(CFM.get_custom_fields(),
|
||||||
cmp=lambda x,y: cmp(CFM[x]['name'].lower(),
|
cmp=lambda x,y: cmp(CFM[x]['name'].lower(),
|
||||||
CFM[y]['name'].lower()))]
|
CFM[y]['name'].lower()))]
|
||||||
for item in items:
|
for item in items:
|
||||||
self.root.append(ACQUISITION_ENTRY(item, version, FM, updated,
|
self.root.append(ACQUISITION_ENTRY(item, version, db, updated,
|
||||||
CFM, CKEYS))
|
CFM, CKEYS))
|
||||||
|
|
||||||
class CategoryFeed(NavFeed):
|
class CategoryFeed(NavFeed):
|
||||||
@ -384,7 +380,7 @@ class OPDSServer(object):
|
|||||||
cherrypy.response.headers['Last-Modified'] = self.last_modified(updated)
|
cherrypy.response.headers['Last-Modified'] = self.last_modified(updated)
|
||||||
cherrypy.response.headers['Content-Type'] = 'application/atom+xml;profile=opds-catalog'
|
cherrypy.response.headers['Content-Type'] = 'application/atom+xml;profile=opds-catalog'
|
||||||
return str(AcquisitionFeed(updated, id_, items, offsets,
|
return str(AcquisitionFeed(updated, id_, items, offsets,
|
||||||
page_url, up_url, version, self.db.FIELD_MAP, self.db.field_metadata))
|
page_url, up_url, version, self.db))
|
||||||
|
|
||||||
def opds_search(self, query=None, version=0, offset=0):
|
def opds_search(self, query=None, version=0, offset=0):
|
||||||
try:
|
try:
|
||||||
|
@ -102,31 +102,21 @@ class XMLServer(object):
|
|||||||
for key in CKEYS:
|
for key in CKEYS:
|
||||||
def concat(name, val):
|
def concat(name, val):
|
||||||
return '%s:#:%s'%(name, unicode(val))
|
return '%s:#:%s'%(name, unicode(val))
|
||||||
val = record[CFM[key]['rec_index']]
|
mi = self.db.get_metadata(record[CFM['id']['rec_index']], index_is_id=True)
|
||||||
if val:
|
name, val = mi.format_field(key)
|
||||||
datatype = CFM[key]['datatype']
|
if not val:
|
||||||
if datatype in ['comments']:
|
continue
|
||||||
continue
|
datatype = CFM[key]['datatype']
|
||||||
k = str('CF_'+key[1:])
|
if datatype in ['comments']:
|
||||||
name = CFM[key]['name']
|
continue
|
||||||
custcols.append(k)
|
k = str('CF_'+key[1:])
|
||||||
if datatype == 'text' and CFM[key]['is_multiple']:
|
name = CFM[key]['name']
|
||||||
kwargs[k] = concat('#T#'+name,
|
custcols.append(k)
|
||||||
format_tag_string(val,'|',
|
if datatype == 'text' and CFM[key]['is_multiple']:
|
||||||
ignore_max=True))
|
kwargs[k] = concat('#T#'+name, format_tag_string(val,',',
|
||||||
elif datatype == 'series':
|
ignore_max=True))
|
||||||
kwargs[k] = concat(name, '%s [%s]'%(val,
|
else:
|
||||||
fmt_sidx(record[CFM.cc_series_index_column_for(key)])))
|
kwargs[k] = concat(name, val)
|
||||||
elif datatype == 'datetime':
|
|
||||||
kwargs[k] = concat(name,
|
|
||||||
format_date(val, CFM[key]['display'].get('date_format','dd MMM yyyy')))
|
|
||||||
elif datatype == 'bool':
|
|
||||||
if val:
|
|
||||||
kwargs[k] = concat(name, __builtin__._('Yes'))
|
|
||||||
else:
|
|
||||||
kwargs[k] = concat(name, __builtin__._('No'))
|
|
||||||
else:
|
|
||||||
kwargs[k] = concat(name, val)
|
|
||||||
kwargs['custcols'] = ','.join(custcols)
|
kwargs['custcols'] = ','.join(custcols)
|
||||||
books.append(E.book(c, **kwargs))
|
books.append(E.book(c, **kwargs))
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user