mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Change server avg_rating calculation to match new GUI algorithm for hierarchical items
This commit is contained in:
parent
2ed36fb746
commit
4f601b23df
@ -137,7 +137,8 @@ class Context(object):
|
|||||||
old = cache.pop(key, None)
|
old = cache.pop(key, None)
|
||||||
if old is None or old[0] <= db.last_modified():
|
if old is None or old[0] <= db.last_modified():
|
||||||
categories = db.get_categories(book_ids=restrict_to_ids, sort=opts.sort_by, first_letter_sort=opts.collapse_model == 'first letter')
|
categories = db.get_categories(book_ids=restrict_to_ids, sort=opts.sort_by, first_letter_sort=opts.collapse_model == 'first letter')
|
||||||
cache[key] = old = (utcnow(), render(categories))
|
with db.safe_read_lock:
|
||||||
|
cache[key] = old = (utcnow(), render(categories))
|
||||||
if len(cache) > self.CATEGORY_CACHE_SIZE:
|
if len(cache) > self.CATEGORY_CACHE_SIZE:
|
||||||
cache.popitem(last=False)
|
cache.popitem(last=False)
|
||||||
else:
|
else:
|
||||||
|
@ -303,7 +303,7 @@ def collapse_first_letter(collapse_nodes, items, category_node, cl_list, idx, is
|
|||||||
def process_category_node(
|
def process_category_node(
|
||||||
category_node, items, category_data, eval_formatter, field_metadata,
|
category_node, items, category_data, eval_formatter, field_metadata,
|
||||||
opts, tag_map, hierarchical_tags, node_to_tag_map, collapse_nodes,
|
opts, tag_map, hierarchical_tags, node_to_tag_map, collapse_nodes,
|
||||||
intermediate_nodes, hierarchical_nodes):
|
intermediate_nodes, hierarchical_items):
|
||||||
category = items[category_node['id']]['category']
|
category = items[category_node['id']]['category']
|
||||||
category_items = category_data[category]
|
category_items = category_data[category]
|
||||||
cat_len = len(category_items)
|
cat_len = len(category_items)
|
||||||
@ -339,8 +339,6 @@ def process_category_node(
|
|||||||
else:
|
else:
|
||||||
node_id, node_data = node_data
|
node_id, node_data = node_data
|
||||||
node = {'id':node_id, 'children':[]}
|
node = {'id':node_id, 'children':[]}
|
||||||
if node_id not in hierarchical_nodes:
|
|
||||||
hierarchical_nodes[node_id] = node
|
|
||||||
parent['children'].append(node)
|
parent['children'].append(node)
|
||||||
return node, node_data
|
return node, node_data
|
||||||
|
|
||||||
@ -383,9 +381,11 @@ def process_category_node(
|
|||||||
if cm_key in child_map:
|
if cm_key in child_map:
|
||||||
node_parent = child_map[cm_key]
|
node_parent = child_map[cm_key]
|
||||||
node_id = node_parent['id']
|
node_id = node_parent['id']
|
||||||
items[node_id]['is_hierarchical'] = 3 if tag.category == 'search' else 5
|
item = items[node_id]
|
||||||
if node_id not in hierarchical_nodes:
|
item['is_hierarchical'] = 3 if tag.category == 'search' else 5
|
||||||
hierarchical_nodes[node_id] = node_parent
|
if tag.id_set is not None:
|
||||||
|
item['id_set'] |= tag.id_set
|
||||||
|
hierarchical_items.add(node_id)
|
||||||
hierarchical_tags.add(id(node_to_tag_map[node_parent['id']]))
|
hierarchical_tags.add(id(node_to_tag_map[node_parent['id']]))
|
||||||
else:
|
else:
|
||||||
if i < len(components) - 1: # Non-leaf node
|
if i < len(components) - 1: # Non-leaf node
|
||||||
@ -421,10 +421,10 @@ def iternode_descendants(node):
|
|||||||
for x in iternode_descendants(child):
|
for x in iternode_descendants(child):
|
||||||
yield x
|
yield x
|
||||||
|
|
||||||
def fillout_tree(root, items, node_id_map, category_nodes, category_data, field_metadata, opts):
|
def fillout_tree(root, items, node_id_map, category_nodes, category_data, field_metadata, opts, book_rating_map):
|
||||||
eval_formatter = EvalFormatter()
|
eval_formatter = EvalFormatter()
|
||||||
tag_map, hierarchical_tags, node_to_tag_map = {}, set(), {}
|
tag_map, hierarchical_tags, node_to_tag_map = {}, set(), {}
|
||||||
first, later, collapse_nodes, intermediate_nodes, hierarchical_nodes = [], [], [], {}, {}
|
first, later, collapse_nodes, intermediate_nodes, hierarchical_items = [], [], [], {}, set()
|
||||||
# User categories have to be processed after normal categories as they can
|
# User categories have to be processed after normal categories as they can
|
||||||
# reference hierarchical nodes that were created only during processing of
|
# reference hierarchical nodes that were created only during processing of
|
||||||
# normal categories
|
# normal categories
|
||||||
@ -438,10 +438,20 @@ def fillout_tree(root, items, node_id_map, category_nodes, category_data, field_
|
|||||||
process_category_node(
|
process_category_node(
|
||||||
cnode, items, category_data, eval_formatter, field_metadata,
|
cnode, items, category_data, eval_formatter, field_metadata,
|
||||||
opts, tag_map, hierarchical_tags, node_to_tag_map,
|
opts, tag_map, hierarchical_tags, node_to_tag_map,
|
||||||
collapse_nodes, intermediate_nodes, hierarchical_nodes)
|
collapse_nodes, intermediate_nodes, hierarchical_items)
|
||||||
|
|
||||||
# Do not store id_set in the tag items as it is a lot of data, with not
|
# Do not store id_set in the tag items as it is a lot of data, with not
|
||||||
# much use. Instead only update the counts based on id_set
|
# much use. Instead only update the ratings and counts based on id_set
|
||||||
|
for item_id in hierarchical_items:
|
||||||
|
item = items[item_id]
|
||||||
|
total = count = 0
|
||||||
|
for book_id in item['id_set']:
|
||||||
|
rating = book_rating_map.get(book_id, 0)
|
||||||
|
if rating:
|
||||||
|
total += rating/2.0
|
||||||
|
count += 1
|
||||||
|
item['avg_rating'] = float(total)/count if count else 0
|
||||||
|
|
||||||
for item_id, item in tag_map.itervalues():
|
for item_id, item in tag_map.itervalues():
|
||||||
id_len = len(item.pop('id_set', ()))
|
id_len = len(item.pop('id_set', ()))
|
||||||
if id_len:
|
if id_len:
|
||||||
@ -451,41 +461,10 @@ def fillout_tree(root, items, node_id_map, category_nodes, category_data, field_
|
|||||||
item = items[node['id']]
|
item = items[node['id']]
|
||||||
item['count'] = sum(1 for _ in iternode_descendants(node))
|
item['count'] = sum(1 for _ in iternode_descendants(node))
|
||||||
|
|
||||||
# Calculate correct avg_rating for hierarchical category items
|
def render_categories(field_metadata, opts, book_rating_map, category_data):
|
||||||
calculated = {}
|
|
||||||
|
|
||||||
def set_average_rating(item_id, children):
|
|
||||||
cr = calculated.get(item_id, None)
|
|
||||||
if cr is not None:
|
|
||||||
return cr
|
|
||||||
item = items[item_id]
|
|
||||||
if not item.get('is_hierarchical', False) or not children:
|
|
||||||
return item.get('avg_rating', 0)
|
|
||||||
total = num = 0
|
|
||||||
for child in children:
|
|
||||||
r = set_average_rating(child['id'], child['children'])
|
|
||||||
if r:
|
|
||||||
total += 1
|
|
||||||
num += r
|
|
||||||
sr = item.get('avg_rating', 0)
|
|
||||||
if sr:
|
|
||||||
total += 1
|
|
||||||
num += sr
|
|
||||||
try:
|
|
||||||
calculated[item_id] = item['avg_rating'] = ans = num/float(total)
|
|
||||||
except ZeroDivisionError:
|
|
||||||
calculated[item_id] = item['avg_rating'] = ans = 0
|
|
||||||
return ans
|
|
||||||
|
|
||||||
for item_id, node in hierarchical_nodes.iteritems():
|
|
||||||
item = items[item_id]
|
|
||||||
if item.get('is_hierarchical', False):
|
|
||||||
set_average_rating(item_id, node['children'])
|
|
||||||
|
|
||||||
def render_categories(field_metadata, opts, category_data):
|
|
||||||
items = {}
|
items = {}
|
||||||
root, node_id_map, category_nodes, recount_nodes = create_toplevel_tree(category_data, items, field_metadata, opts)
|
root, node_id_map, category_nodes, recount_nodes = create_toplevel_tree(category_data, items, field_metadata, opts)
|
||||||
fillout_tree(root, items, node_id_map, category_nodes, category_data, field_metadata, opts)
|
fillout_tree(root, items, node_id_map, category_nodes, category_data, field_metadata, opts, book_rating_map)
|
||||||
for node in recount_nodes:
|
for node in recount_nodes:
|
||||||
item = items[node['id']]
|
item = items[node['id']]
|
||||||
item['count'] = sum(1 for x in iternode_descendants(node) if not items[x['id']].get('is_category', False))
|
item['count'] = sum(1 for x in iternode_descendants(node) if not items[x['id']].get('is_category', False))
|
||||||
@ -497,7 +476,7 @@ def render_categories(field_metadata, opts, category_data):
|
|||||||
|
|
||||||
def categories_as_json(ctx, rd, db):
|
def categories_as_json(ctx, rd, db):
|
||||||
opts = categories_settings(rd.query, db)
|
opts = categories_settings(rd.query, db)
|
||||||
return ctx.get_tag_browser(rd, db, opts, partial(render_categories, db.field_metadata, opts))
|
return ctx.get_tag_browser(rd, db, opts, partial(render_categories, db.field_metadata, opts, db.fields['rating'].book_value_map))
|
||||||
|
|
||||||
# Test tag browser {{{
|
# Test tag browser {{{
|
||||||
|
|
||||||
@ -542,7 +521,7 @@ def test_tag_browser(library_path=None):
|
|||||||
opts = categories_settings({}, db)
|
opts = categories_settings({}, db)
|
||||||
# opts = opts._replace(hidden_categories={'publisher'})
|
# opts = opts._replace(hidden_categories={'publisher'})
|
||||||
category_data = db.get_categories(sort=opts.sort_by, first_letter_sort=opts.collapse_model == 'first letter')
|
category_data = db.get_categories(sort=opts.sort_by, first_letter_sort=opts.collapse_model == 'first letter')
|
||||||
data = render_categories(db.field_metadata, opts, category_data)
|
data = render_categories(db.field_metadata, opts, db.fields['rating'].book_value_map, category_data)
|
||||||
srv_data = dump_categories_tree(data)
|
srv_data = dump_categories_tree(data)
|
||||||
from calibre.gui2 import Application, gprefs
|
from calibre.gui2 import Application, gprefs
|
||||||
from calibre.gui2.tag_browser.model import TagsModel
|
from calibre.gui2.tag_browser.model import TagsModel
|
||||||
|
Loading…
x
Reference in New Issue
Block a user