Implement last modified custom column

This commit is contained in:
Kovid Goyal 2011-03-03 11:01:44 -07:00
commit 105b7283b4
6 changed files with 185 additions and 43 deletions

View File

@ -616,6 +616,19 @@ class BooksModel(QAbstractTableModel): # {{{
def bool_type_decorator(r, idx=-1, bool_cols_are_tristate=True):
val = self.db.data[r][idx]
if isinstance(val, (str, unicode)):
try:
val = icu_lower(val)
if not val:
val = None
elif val in [_('yes'), _('checked'), 'true']:
val = True
elif val in [_('no'), _('unchecked'), 'false']:
val = False
else:
val = bool(int(val))
except:
val = None
if not bool_cols_are_tristate:
if val is None or not val:
return self.bool_no_icon
@ -676,6 +689,12 @@ class BooksModel(QAbstractTableModel): # {{{
if datatype in ('text', 'comments', 'composite', 'enumeration'):
self.dc[col] = functools.partial(text_type, idx=idx,
mult=self.custom_columns[col]['is_multiple'])
if datatype == 'composite':
csort = self.custom_columns[col]['display'].get('composite_sort', 'text')
if csort == 'bool':
self.dc_decorator[col] = functools.partial(
bool_type_decorator, idx=idx,
bool_cols_are_tristate=tweaks['bool_custom_columns_are_tristate'] != 'no')
elif datatype in ('int', 'float'):
self.dc[col] = functools.partial(number_type, idx=idx)
elif datatype == 'datetime':

View File

@ -68,6 +68,9 @@ class CreateCustomColumn(QDialog, Ui_QCreateCustomColumn):
text = text[:-1]
self.shortcuts.setText(text)
for sort_by in [_('Text'), _('Number'), _('Date'), _('Yes/No')]:
self.composite_sort_by.addItem(sort_by)
self.parent = parent
self.editing_col = editing
self.standard_colheads = standard_colheads
@ -108,6 +111,13 @@ class CreateCustomColumn(QDialog, Ui_QCreateCustomColumn):
self.date_format_box.setText(c['display'].get('date_format', ''))
elif ct == 'composite':
self.composite_box.setText(c['display'].get('composite_template', ''))
sb = c['display'].get('composite_sort', 'text')
vals = ['text', 'number', 'date', 'bool']
if sb in vals:
sb = vals.index(sb)
else:
sb = 0
self.composite_sort_by.setCurrentIndex(sb)
elif ct == 'enumeration':
self.enum_box.setText(','.join(c['display'].get('enum_values', [])))
self.datatype_changed()
@ -135,8 +145,9 @@ class CreateCustomColumn(QDialog, Ui_QCreateCustomColumn):
{
'isbn': '{identifiers:select(isbn)}',
'formats': '{formats}',
'last_modified':'''{last_modified:'format_date($, "%d %m, %Y")'}'''
'last_modified':'''{last_modified:'format_date($, "dd MMM yy")'}'''
}[which])
self.composite_sort_by.setCurrentIndex(2 if which == 'last_modified' else 0)
def datatype_changed(self, *args):
@ -146,7 +157,7 @@ class CreateCustomColumn(QDialog, Ui_QCreateCustomColumn):
col_type = None
for x in ('box', 'default_label', 'label'):
getattr(self, 'date_format_'+x).setVisible(col_type == 'datetime')
for x in ('box', 'default_label', 'label'):
for x in ('box', 'default_label', 'label', 'sort_by', 'sort_by_label'):
getattr(self, 'composite_'+x).setVisible(col_type == 'composite')
for x in ('box', 'default_label', 'label'):
getattr(self, 'enum_'+x).setVisible(col_type == 'enumeration')
@ -201,7 +212,10 @@ class CreateCustomColumn(QDialog, Ui_QCreateCustomColumn):
if not unicode(self.composite_box.text()).strip():
return self.simple_error('', _('You must enter a template for'
' composite columns'))
display_dict = {'composite_template':unicode(self.composite_box.text()).strip()}
display_dict = {'composite_template':unicode(self.composite_box.text()).strip(),
'composite_sort': ['text', 'number', 'date', 'bool']
[self.composite_sort_by.currentIndex()]
}
elif col_type == 'enumeration':
if not unicode(self.enum_box.text()).strip():
return self.simple_error('', _('You must enter at least one'

View File

@ -80,7 +80,7 @@
<item row="2" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Column &amp;type</string>
<string>&amp;Column type</string>
</property>
<property name="buddy">
<cstring>column_type_box</cstring>
@ -148,6 +148,16 @@
</property>
</widget>
</item>
<item row="5" column="0">
<widget class="QLabel" name="composite_label">
<property name="text">
<string>&amp;Template</string>
</property>
<property name="buddy">
<cstring>composite_box</cstring>
</property>
</widget>
</item>
<item row="5" column="2">
<layout class="QHBoxLayout" name="horizontalLayout_4">
<item>
@ -175,16 +185,46 @@
</item>
</layout>
</item>
<item row="5" column="0">
<widget class="QLabel" name="composite_label">
<item row="6" column="0">
<widget class="QLabel" name="composite_sort_by_label">
<property name="text">
<string>&amp;Template</string>
<string>&amp;Sort/search column by</string>
</property>
<property name="buddy">
<cstring>composite_box</cstring>
<cstring>composite_sort_by</cstring>
</property>
</widget>
</item>
<item row="6" column="2">
<layout class="QHBoxLayout" name="horizontalLayout_4">
<item>
<widget class="QComboBox" name="composite_sort_by">
<property name="toolTip">
<string>How this column should handled in the GUI when sorting and searching</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_24">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>10</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>0</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item row="11" column="0" colspan="4">
<spacer name="verticalSpacer_2">
<property name="orientation">

View File

@ -302,14 +302,20 @@ class ResultCache(SearchQueryParser): # {{{
for id_ in candidates:
item = self._data[id_]
if item is None: continue
if item[loc] is None or item[loc] <= UNDEFINED_DATE:
v = item[loc]
if isinstance(v, (str, unicode)):
v = parse_date(v)
if v is None or v <= UNDEFINED_DATE:
matches.add(item[0])
return matches
if query == 'true':
for id_ in candidates:
item = self._data[id_]
if item is None: continue
if item[loc] is not None and item[loc] > UNDEFINED_DATE:
v = item[loc]
if isinstance(v, (str, unicode)):
v = parse_date(v)
if v is not None and v > UNDEFINED_DATE:
matches.add(item[0])
return matches
@ -349,7 +355,10 @@ class ResultCache(SearchQueryParser): # {{{
for id_ in candidates:
item = self._data[id_]
if item is None or item[loc] is None: continue
if relop(item[loc], qd, field_count):
v = item[loc]
if isinstance(v, (str, unicode)):
v = parse_date(v)
if relop(v, qd, field_count):
matches.add(item[0])
return matches
@ -390,7 +399,7 @@ class ResultCache(SearchQueryParser): # {{{
elif dt == 'rating':
cast = (lambda x: int (x))
adjust = lambda x: x/2
elif dt == 'float':
elif dt in ('float', 'composite'):
cast = lambda x : float (x)
adjust = lambda x: x
else: # count operation
@ -413,12 +422,15 @@ class ResultCache(SearchQueryParser): # {{{
item = self._data[id_]
if item is None:
continue
v = val_func(item)
try:
v = cast(val_func(item))
except:
v = 0
if not v:
i = 0
v = 0
else:
i = adjust(v)
if relop(i, q):
v = adjust(v)
if relop(v, q):
matches.add(item[0])
return matches
@ -509,6 +521,50 @@ class ResultCache(SearchQueryParser): # {{{
query = icu_lower(query)
return matchkind, query
def get_bool_matches(self, location, query, candidates):
bools_are_tristate = tweaks['bool_custom_columns_are_tristate'] != 'no'
loc = self.field_metadata[location]['rec_index']
matches = set()
query = icu_lower(query)
for id_ in candidates:
item = self._data[id_]
if item is None:
continue
val = item[loc]
if isinstance(val, (str, unicode)):
try:
val = icu_lower(val)
if not val:
val = None
elif val in [_('yes'), _('checked'), 'true']:
val = True
elif val in [_('no'), _('unchecked'), 'false']:
val = False
else:
val = bool(int(val))
except:
val = None
if not bools_are_tristate:
if val is None or not val: # item is None or set to false
if query in [_('no'), _('unchecked'), 'false']:
matches.add(item[0])
else: # item is explicitly set to true
if query in [_('yes'), _('checked'), 'true']:
matches.add(item[0])
else:
if val is None:
if query in [_('empty'), _('blank'), 'false']:
matches.add(item[0])
elif not val: # is not None and false
if query in [_('no'), _('unchecked'), 'true']:
matches.add(item[0])
else: # item is not None and true
if query in [_('yes'), _('checked'), 'true']:
matches.add(item[0])
return matches
def get_matches(self, location, query, candidates=None,
allow_recursion=True):
matches = set([])
@ -559,13 +615,20 @@ class ResultCache(SearchQueryParser): # {{{
if location in self.field_metadata:
fm = self.field_metadata[location]
# take care of dates special case
if fm['datatype'] == 'datetime':
if fm['datatype'] == 'datetime' or \
(fm['datatype'] == 'composite' and
fm['display'].get('composite_sort', '') == 'date'):
return self.get_dates_matches(location, query.lower(), candidates)
# take care of numbers special case
if fm['datatype'] in ('rating', 'int', 'float'):
if fm['datatype'] in ('rating', 'int', 'float') or \
(fm['datatype'] == 'composite' and
fm['display'].get('composite_sort', '') == 'number'):
return self.get_numeric_matches(location, query.lower(), candidates)
if fm['datatype'] == 'bool':
return self.get_bool_matches(location, query, candidates)
# take care of the 'count' operator for is_multiples
if fm['is_multiple'] and \
len(query) > 1 and query.startswith('#') and \
@ -619,9 +682,6 @@ class ResultCache(SearchQueryParser): # {{{
for i, loc in enumerate(location):
location[i] = db_col[loc]
# get the tweak here so that the string lookup and compare aren't in the loop
bools_are_tristate = tweaks['bool_custom_columns_are_tristate'] != 'no'
for loc in location: # location is now an array of field indices
if loc == db_col['authors']:
### DB stores authors with commas changed to bars, so change query
@ -633,27 +693,6 @@ class ResultCache(SearchQueryParser): # {{{
item = self._data[id_]
if item is None: continue
if col_datatype[loc] == 'bool': # complexity caused by the two-/three-value tweak
v = item[loc]
if not bools_are_tristate:
if v is None or not v: # item is None or set to false
if q in [_('no'), _('unchecked'), 'false']:
matches.add(item[0])
else: # item is explicitly set to true
if q in [_('yes'), _('checked'), 'true']:
matches.add(item[0])
else:
if v is None:
if q in [_('empty'), _('blank'), 'false']:
matches.add(item[0])
elif not v: # is not None and false
if q in [_('no'), _('unchecked'), 'true']:
matches.add(item[0])
else: # item is not None and true
if q in [_('yes'), _('checked'), 'true']:
matches.add(item[0])
continue
if not item[loc]:
if q == 'false':
matches.add(item[0])
@ -893,6 +932,34 @@ class SortKeyGenerator(object):
for name, fm in self.entries:
dt = fm['datatype']
val = record[fm['rec_index']]
if dt == 'composite':
sb = fm['display'].get('composite_sort', 'text')
if sb == 'date':
try:
val = parse_date(val)
dt = 'datetime'
except:
pass
elif sb == 'number':
try:
val = float(val)
except:
val = 0.0
dt = 'float'
elif sb == 'bool':
try:
v = icu_lower(val)
if not val:
val = None
elif v in [_('yes'), _('checked'), 'true']:
val = True
elif v in [_('no'), _('unchecked'), 'false']:
val = False
else:
val = bool(int(val))
except:
val = None
dt = 'bool'
if dt == 'datetime':
if val is None:

View File

@ -833,7 +833,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
mi.pubdate = row[fm['pubdate']]
mi.uuid = row[fm['uuid']]
mi.title_sort = row[fm['sort']]
mi.metadata_last_modified = row[fm['last_modified']]
mi.last_modified = row[fm['last_modified']]
formats = row[fm['formats']]
if not formats:
formats = None

View File

@ -71,6 +71,8 @@ def parse_date(date_string, assume_utc=False, as_utc=True, default=None):
:param default: Missing fields are filled in from default. If None, the
current date is used.
'''
if not date_string:
return UNDEFINED_DATE
if default is None:
func = datetime.utcnow if assume_utc else datetime.now
default = func().replace(hour=0, minute=0, second=0, microsecond=0,