From de36d8acc9decd9937c29dd0ce4c91bda430aa67 Mon Sep 17 00:00:00 2001 From: Charles Haley <> Date: Fri, 11 Jun 2010 18:06:57 +0100 Subject: [PATCH] Prototype implementation of average ratings --- src/calibre/gui2/tag_view.py | 17 +++++- src/calibre/library/custom_columns.py | 19 +++++-- src/calibre/library/database2.py | 17 +++--- src/calibre/library/schema_upgrades.py | 73 ++++++++++++++++++++++++++ 4 files changed, 115 insertions(+), 11 deletions(-) diff --git a/src/calibre/gui2/tag_view.py b/src/calibre/gui2/tag_view.py index bc698a3502..8b1a376bb5 100644 --- a/src/calibre/gui2/tag_view.py +++ b/src/calibre/gui2/tag_view.py @@ -298,7 +298,10 @@ class TagTreeItem(object): # {{{ if self.tag.count == 0: return QVariant('%s'%(self.tag.name)) else: - return QVariant('[%d] %s'%(self.tag.count, self.tag.name)) + if self.tag.avg is None: + return QVariant('[%d] %s'%(self.tag.count, self.tag.name)) + else: + return QVariant('[%d][%d] %s'%(self.tag.count, self.tag.avg, self.tag.name)) if role == Qt.EditRole: return QVariant(self.tag.name) if role == Qt.DecorationRole: @@ -332,6 +335,7 @@ class TagsModel(QAbstractItemModel): # {{{ ':custom' : QIcon(I('column.svg')), ':user' : QIcon(I('drawer.svg')), 'search' : QIcon(I('search.svg'))}) + self.categories_with_ratings = ['authors', 'series', 'publisher', 'tags'] self.icon_state_map = [None, QIcon(I('plus.svg')), QIcon(I('minus.svg'))] self.db = db @@ -354,7 +358,14 @@ class TagsModel(QAbstractItemModel): # {{{ data=self.categories[i], category_icon=self.category_icon_map[r], tooltip=tt, category_key=r) + # This duplicates code in refresh(). Having it here as well + # can save seconds during startup, because we avoid a second + # call to get_node_tree. for tag in data[r]: + if r not in self.categories_with_ratings and \ + not self.db.field_metadata[r]['is_custom'] and \ + not self.db.field_metadata[r]['kind'] == 'user': + tag.avg = None TagTreeItem(parent=c, data=tag, icon_map=self.icon_state_map) def set_search_restriction(self, s): @@ -417,6 +428,10 @@ class TagsModel(QAbstractItemModel): # {{{ if len(data[r]) > 0: self.beginInsertRows(category_index, 0, len(data[r])-1) for tag in data[r]: + if r not in self.categories_with_ratings and \ + not self.db.field_metadata[r]['is_custom'] and \ + not self.db.field_metadata[r]['kind'] == 'user': + tag.avg = None tag.state = state_map.get(tag.name, 0) t = TagTreeItem(parent=category, data=tag, icon_map=self.icon_state_map) self.endInsertRows() diff --git a/src/calibre/library/custom_columns.py b/src/calibre/library/custom_columns.py index 23b78f38ae..91d04a4639 100644 --- a/src/calibre/library/custom_columns.py +++ b/src/calibre/library/custom_columns.py @@ -461,14 +461,27 @@ class CustomColumns(object): CREATE VIEW tag_browser_{table} AS SELECT id, value, - (SELECT COUNT(id) FROM {lt} WHERE value={table}.id) count + (SELECT COUNT(id) FROM {lt} WHERE value={table}.id) count, + (SELECT AVG(r.rating) + FROM {lt}, + books_ratings_link as bl, + ratings as r + WHERE {lt}.value={table}.id and bl.book={lt}.book and + r.id = bl.rating and r.rating <> 0) avg_rating FROM {table}; CREATE VIEW tag_browser_filtered_{table} AS SELECT id, value, (SELECT COUNT({lt}.id) FROM {lt} WHERE value={table}.id AND - books_list_filter(book)) count + books_list_filter(book)) count, + (SELECT AVG(r.rating) + FROM {lt}, + books_ratings_link as bl, + ratings as r + WHERE {lt}.value={table}.id AND bl.book={lt}.book AND + r.id = bl.rating AND r.rating <> 0 AND + books_list_filter(bl.book)) avg_rating FROM {table}; '''.format(lt=lt, table=table), @@ -505,7 +518,7 @@ class CustomColumns(object): END; '''.format(table=table), ] - + print lines script = ' \n'.join(lines) self.conn.executescript(script) self.conn.commit() diff --git a/src/calibre/library/database2.py b/src/calibre/library/database2.py index 7b98dc4537..41ad235d01 100644 --- a/src/calibre/library/database2.py +++ b/src/calibre/library/database2.py @@ -56,11 +56,12 @@ copyfile = os.link if hasattr(os, 'link') else shutil.copyfile class Tag(object): - def __init__(self, name, id=None, count=0, state=0, tooltip=None, icon=None): + def __init__(self, name, id=None, count=0, state=0, avg=0, tooltip=None, icon=None): self.name = name self.id = id self.count = count self.state = state + self.avg = avg/2 if avg is not None else 0 self.tooltip = tooltip self.icon = icon @@ -125,15 +126,16 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): self.connect() self.is_case_sensitive = not iswindows and not isosx and \ not os.path.exists(self.dbpath.replace('metadata.db', 'MeTAdAtA.dB')) - SchemaUpgrade.__init__(self) self.initialize_dynamic() + SchemaUpgrade.__init__(self) def initialize_dynamic(self): self.conn.executescript(u''' CREATE TEMP VIEW IF NOT EXISTS tag_browser_news AS SELECT DISTINCT id, name, - (SELECT COUNT(books_tags_link.id) FROM books_tags_link WHERE tag=x.id) count + (SELECT COUNT(books_tags_link.id) FROM books_tags_link WHERE tag=x.id) count, + (0) as avg_rating FROM tags as x WHERE name!="{0}" AND id IN (SELECT DISTINCT tag FROM books_tags_link WHERE book IN (SELECT DISTINCT book FROM books_tags_link WHERE tag IN @@ -144,7 +146,8 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): CREATE TEMP VIEW IF NOT EXISTS tag_browser_filtered_news AS SELECT DISTINCT id, name, - (SELECT COUNT(books_tags_link.id) FROM books_tags_link WHERE tag=x.id and books_list_filter(book)) count + (SELECT COUNT(books_tags_link.id) FROM books_tags_link WHERE tag=x.id and books_list_filter(book)) count, + (0) as avg_rating FROM tags as x WHERE name!="{0}" AND id IN (SELECT DISTINCT tag FROM books_tags_link WHERE book IN (SELECT DISTINCT book FROM books_tags_link WHERE tag IN @@ -698,9 +701,9 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): continue cn = cat['column'] if ids is None: - query = 'SELECT id, {0}, count FROM tag_browser_{1}'.format(cn, tn) + query = 'SELECT id, {0}, count, avg_rating FROM tag_browser_{1}'.format(cn, tn) else: - query = 'SELECT id, {0}, count FROM tag_browser_filtered_{1}'.format(cn, tn) + query = 'SELECT id, {0}, count, avg_rating FROM tag_browser_filtered_{1}'.format(cn, tn) if sort_on_count: query += ' ORDER BY count DESC' else: @@ -733,7 +736,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): formatter = (lambda x:unicode(x)) categories[category] = [Tag(formatter(r[1]), count=r[2], id=r[0], - icon=icon, tooltip = tooltip) + avg=r[3], icon=icon, tooltip=tooltip) for r in data if item_not_zero_func(r)] if category == 'series' and not sort_on_count: if tweaks['title_series_sorting'] == 'library_order': diff --git a/src/calibre/library/schema_upgrades.py b/src/calibre/library/schema_upgrades.py index 070ad1f3a6..0660a8b136 100644 --- a/src/calibre/library/schema_upgrades.py +++ b/src/calibre/library/schema_upgrades.py @@ -292,3 +292,76 @@ class SchemaUpgrade(object): for field in self.field_metadata.itervalues(): if field['is_category'] and not field['is_custom'] and 'link_column' in field: create_tag_browser_view(field['table'], field['link_column'], field['column']) + + def upgrade_version_11(self): + 'Add average rating to tag browser views' + def create_std_tag_browser_view(table_name, column_name, view_column_name): + script = (''' + DROP VIEW IF EXISTS tag_browser_{tn}; + CREATE VIEW tag_browser_{tn} AS SELECT + id, + {vcn}, + (SELECT COUNT(id) FROM books_{tn}_link WHERE {cn}={tn}.id) count, + (SELECT AVG(ratings.rating) + FROM books_{tn}_link as tl, books_ratings_link as bl, ratings + WHERE tl.{cn}={tn}.id and bl.book=tl.book and + ratings.id = bl.rating and ratings.rating <> 0) avg_rating + FROM {tn}; + DROP VIEW IF EXISTS tag_browser_filtered_{tn}; + CREATE VIEW tag_browser_filtered_{tn} AS SELECT + id, + {vcn}, + (SELECT COUNT(books_{tn}_link.id) FROM books_{tn}_link WHERE + {cn}={tn}.id AND books_list_filter(book)) count, + (SELECT AVG(ratings.rating) + FROM books_{tn}_link as tl, books_ratings_link as bl, ratings + WHERE tl.{cn}={tn}.id and bl.book=tl.book and + ratings.id = bl.rating and ratings.rating <> 0 AND + books_list_filter(bl.book)) avg_rating + FROM {tn}; + + '''.format(tn=table_name, cn=column_name, vcn=view_column_name)) + self.conn.executescript(script) + + def create_cust_tag_browser_view(table_name, link_table_name): + script = ''' + DROP VIEW IF EXISTS tag_browser_{table}; + CREATE VIEW tag_browser_{table} AS SELECT + id, + value, + (SELECT COUNT(id) FROM {lt} WHERE value={table}.id) count, + (SELECT AVG(r.rating) + FROM {lt}, + books_ratings_link as bl, + ratings as r + WHERE {lt}.value={table}.id and bl.book={lt}.book and + r.id = bl.rating and r.rating <> 0) avg_rating + FROM {table}; + + DROP VIEW IF EXISTS tag_browser_filtered_{table}; + CREATE VIEW tag_browser_filtered_{table} AS SELECT + id, + value, + (SELECT COUNT({lt}.id) FROM {lt} WHERE value={table}.id AND + books_list_filter(book)) count, + (SELECT AVG(r.rating) + FROM {lt}, + books_ratings_link as bl, + ratings as r + WHERE {lt}.value={table}.id AND bl.book={lt}.book AND + r.id = bl.rating AND r.rating <> 0 AND + books_list_filter(bl.book)) avg_rating + FROM {table}; + '''.format(lt=link_table_name, table=table_name) + self.conn.executescript(script) + + for field in self.field_metadata.itervalues(): + if field['is_category'] and not field['is_custom'] and 'link_column' in field: + create_std_tag_browser_view(field['table'], field['link_column'], + field['column']) + + for field in self.field_metadata.itervalues(): + if field['is_category'] and field['is_custom']: + link_table_name = 'books_custom_column_%d_link'%field['colnum'] + print 'try to upgrade cust col', field['table'], link_table_name + create_cust_tag_browser_view(field['table'], link_table_name)