Пример #1
0
def category(ctx, rd, encoded_name, library_id):
    '''
    Return a dictionary describing the category specified by name. The

    Optional: ?num=100&offset=0&sort=name&sort_order=asc

    The dictionary looks like::

        {
            'category_name': Category display name,
            'base_url': Base URL for this category,
            'total_num': Total numberof items in this category,
            'offset': The offset for the items returned in this result,
            'num': The number of items returned in this result,
            'sort': How the returned items are sorted,
            'sort_order': asc or desc
            'subcategories': List of sub categories of this category.
            'items': List of items in this category,
        }

    Each subcategory is a dictionary of the same form as those returned by
    /ajax/categories

    Each  item is a dictionary of the form::

        {
            'name': Display name,
            'average_rating': Average rating for books in this item,
            'count': Number of books in this item,
            'url': URL to get list of books in this item,
            'has_children': If True this item contains sub categories, look
            for an entry corresponding to this item in subcategories int he
            main dictionary,
        }

    :param sort: How to sort the returned items. Choices are: name, rating,
                    popularity
    :param sort_order: asc or desc

    To learn how to create subcategories see
    https://manual.calibre-ebook.com/sub_groups.html
    '''

    db = get_db(ctx, rd, library_id)
    with db.safe_read_lock:
        num, offset = get_pagination(rd.query)
        sort, sort_order = rd.query.get('sort'), rd.query.get('sort_order')
        sort = ensure_val(sort, 'name', 'rating', 'popularity')
        sort_order = ensure_val(sort_order, 'asc', 'desc')
        try:
            dname = decode_name(encoded_name)
        except:
            raise HTTPNotFound('Invalid encoding of category name %r'%encoded_name)
        base_url = ctx.url_for(globals()['category'], encoded_name=encoded_name, library_id=db.server_library_id)

        if dname in ('newest', 'allbooks'):
            sort, sort_order = 'timestamp', 'desc'
            rd.query['sort'], rd.query['sort_order'] = sort, sort_order
            return books_in(ctx, rd, encoded_name, encode_name('0'), library_id)

        fm = db.field_metadata
        categories = ctx.get_categories(rd, db)
        hierarchical_categories = db.pref('categories_using_hierarchy', ())

        subcategory = dname
        toplevel = subcategory.partition('.')[0]
        if toplevel == subcategory:
            subcategory = None
        if toplevel not in categories or toplevel not in fm:
            raise HTTPNotFound('Category %r not found'%toplevel)

        # Find items and sub categories
        subcategories = []
        meta = fm[toplevel]
        item_names = {}
        children = set()

        if meta['kind'] == 'user':
            fullname = ((toplevel + '.' + subcategory) if subcategory is not
                                None else toplevel)
            try:
                # User categories cannot be applied to books, so this is the
                # complete set of items, no need to consider sub categories
                items = categories[fullname]
            except:
                raise HTTPNotFound('User category %r not found'%fullname)

            parts = fullname.split('.')
            for candidate in categories:
                cparts = candidate.split('.')
                if len(cparts) == len(parts)+1 and cparts[:-1] == parts:
                    subcategories.append({'name':cparts[-1],
                        'url':candidate,
                        'icon':category_icon(toplevel, meta)})

            category_name = toplevel[1:].split('.')
            # When browsing by user categories we ignore hierarchical normal
            # columns, so children can be empty

        elif toplevel in hierarchical_categories:
            items = []

            category_names = [x.original_name.split('.') for x in categories[toplevel] if
                    '.' in x.original_name]

            if subcategory is None:
                children = set(x[0] for x in category_names)
                category_name = [meta['name']]
                items = [x for x in categories[toplevel] if '.' not in x.original_name]
            else:
                subcategory_parts = subcategory.split('.')[1:]
                category_name = [meta['name']] + subcategory_parts

                lsp = len(subcategory_parts)
                children = set('.'.join(x) for x in category_names if len(x) ==
                        lsp+1 and x[:lsp] == subcategory_parts)
                items = [x for x in categories[toplevel] if x.original_name in
                        children]
                item_names = {x:x.original_name.rpartition('.')[-1] for x in
                        items}
                # Only mark the subcategories that have children themselves as
                # subcategories
                children = set('.'.join(x[:lsp+1]) for x in category_names if len(x) >
                        lsp+1 and x[:lsp] == subcategory_parts)
            subcategories = [{'name':x.rpartition('.')[-1],
                'url':toplevel+'.'+x,
                'icon':category_icon(toplevel, meta)} for x in children]
        else:
            items = categories[toplevel]
            category_name = meta['name']

        for x in subcategories:
            x['url'] = ctx.url_for(globals()['category'], encoded_name=encode_name(x['url']), library_id=db.server_library_id)
            x['icon'] = ctx.url_for(get_icon, which=x['icon'])
            x['is_category'] = True

        sort_keygen = {
                'name': lambda x: sort_key(x.sort if x.sort else x.original_name),
                'popularity': lambda x: x.count,
                'rating': lambda x: x.avg_rating
        }
        items.sort(key=sort_keygen[sort], reverse=sort_order == 'desc')
        total_num = len(items)
        items = items[offset:offset+num]
        items = [{
            'name':item_names.get(x, x.original_name),
            'average_rating': x.avg_rating,
            'count': x.count,
            'url': ctx.url_for(books_in, encoded_category=encode_name(x.category if x.category else toplevel),
                               encoded_item=encode_name(x.original_name if x.id is None else unicode(x.id)),
                               library_id=db.server_library_id
                               ),
            'has_children': x.original_name in children,
            } for x in items]

        return {
                'category_name': category_name,
                'base_url': base_url,
                'total_num': total_num,
                'offset':offset, 'num':len(items), 'sort':sort,
                'sort_order':sort_order,
                'subcategories':subcategories,
                'items':items,
        }
Пример #2
0
def category(ctx, rd, encoded_name, library_id):
    """
    Return a dictionary describing the category specified by name. The

    Optional: ?num=100&offset=0&sort=name&sort_order=asc

    The dictionary looks like::

        {
            'category_name': Category display name,
            'base_url': Base URL for this category,
            'total_num': Total numberof items in this category,
            'offset': The offset for the items returned in this result,
            'num': The number of items returned in this result,
            'sort': How the returned items are sorted,
            'sort_order': asc or desc
            'subcategories': List of sub categories of this category.
            'items': List of items in this category,
        }

    Each subcategory is a dictionary of the same form as those returned by
    /ajax/categories

    Each  item is a dictionary of the form::

        {
            'name': Display name,
            'average_rating': Average rating for books in this item,
            'count': Number of books in this item,
            'url': URL to get list of books in this item,
            'has_children': If True this item contains sub categories, look
            for an entry corresponding to this item in subcategories int he
            main dictionary,
        }

    :param sort: How to sort the returned items. Choices are: name, rating,
                    popularity
    :param sort_order: asc or desc

    To learn how to create subcategories see
    http://manual.calibre-ebook.com/sub_groups.html
    """

    db = get_db(ctx, library_id)
    with db.safe_read_lock:
        num, offset = get_pagination(rd.query)
        sort, sort_order = rd.query.get("sort"), rd.query.get("sort_order")
        sort = ensure_val(sort, "name", "rating", "popularity")
        sort_order = ensure_val(sort_order, "asc", "desc")
        try:
            dname = decode_name(encoded_name)
        except:
            raise HTTPNotFound("Invalid encoding of category name %r" % encoded_name)
        base_url = ctx.url_for(globals()["category"], encoded_name=encoded_name, library_id=db.server_library_id)

        if dname in ("newest", "allbooks"):
            sort, sort_order = "timestamp", "desc"
            rd.query["sort"], rd.query["sort_order"] = sort, sort_order
            return books_in(ctx, rd, encoded_name, encode_name("0"), library_id)

        fm = db.field_metadata
        categories = ctx.get_categories(rd, db)
        hierarchical_categories = db.pref("categories_using_hierarchy", ())

        subcategory = dname
        toplevel = subcategory.partition(".")[0]
        if toplevel == subcategory:
            subcategory = None
        if toplevel not in categories or toplevel not in fm:
            raise HTTPNotFound("Category %r not found" % toplevel)

        # Find items and sub categories
        subcategories = []
        meta = fm[toplevel]
        item_names = {}
        children = set()

        if meta["kind"] == "user":
            fullname = (toplevel + "." + subcategory) if subcategory is not None else toplevel
            try:
                # User categories cannot be applied to books, so this is the
                # complete set of items, no need to consider sub categories
                items = categories[fullname]
            except:
                raise HTTPNotFound("User category %r not found" % fullname)

            parts = fullname.split(".")
            for candidate in categories:
                cparts = candidate.split(".")
                if len(cparts) == len(parts) + 1 and cparts[:-1] == parts:
                    subcategories.append({"name": cparts[-1], "url": candidate, "icon": category_icon(toplevel, meta)})

            category_name = toplevel[1:].split(".")
            # When browsing by user categories we ignore hierarchical normal
            # columns, so children can be empty

        elif toplevel in hierarchical_categories:
            items = []

            category_names = [x.original_name.split(".") for x in categories[toplevel] if "." in x.original_name]

            if subcategory is None:
                children = set(x[0] for x in category_names)
                category_name = [meta["name"]]
                items = [x for x in categories[toplevel] if "." not in x.original_name]
            else:
                subcategory_parts = subcategory.split(".")[1:]
                category_name = [meta["name"]] + subcategory_parts

                lsp = len(subcategory_parts)
                children = set(
                    ".".join(x) for x in category_names if len(x) == lsp + 1 and x[:lsp] == subcategory_parts
                )
                items = [x for x in categories[toplevel] if x.original_name in children]
                item_names = {x: x.original_name.rpartition(".")[-1] for x in items}
                # Only mark the subcategories that have children themselves as
                # subcategories
                children = set(
                    ".".join(x[: lsp + 1]) for x in category_names if len(x) > lsp + 1 and x[:lsp] == subcategory_parts
                )
            subcategories = [
                {"name": x.rpartition(".")[-1], "url": toplevel + "." + x, "icon": category_icon(toplevel, meta)}
                for x in children
            ]
        else:
            items = categories[toplevel]
            category_name = meta["name"]

        for x in subcategories:
            x["url"] = ctx.url_for(
                globals()["category"], encoded_name=encode_name(x["url"]), library_id=db.server_library_id
            )
            x["icon"] = ctx.url_for(get_icon, which=x["icon"])
            x["is_category"] = True

        sort_keygen = {
            "name": lambda x: sort_key(x.sort if x.sort else x.original_name),
            "popularity": lambda x: x.count,
            "rating": lambda x: x.avg_rating,
        }
        items.sort(key=sort_keygen[sort], reverse=sort_order == "desc")
        total_num = len(items)
        items = items[offset : offset + num]
        items = [
            {
                "name": item_names.get(x, x.original_name),
                "average_rating": x.avg_rating,
                "count": x.count,
                "url": ctx.url_for(
                    books_in,
                    encoded_category=encode_name(x.category if x.category else toplevel),
                    encoded_item=encode_name(x.original_name if x.id is None else unicode(x.id)),
                    library_id=db.server_library_id,
                ),
                "has_children": x.original_name in children,
            }
            for x in items
        ]

        return {
            "category_name": category_name,
            "base_url": base_url,
            "total_num": total_num,
            "offset": offset,
            "num": len(items),
            "sort": sort,
            "sort_order": sort_order,
            "subcategories": subcategories,
            "items": items,
        }