def index(self, **kwargs): """ Output stats. """ backends = int( BaseSearcher.sql_get("SELECT count (*) from pg_stat_activity")) active_backends = int( BaseSearcher.sql_get( "SELECT count (*) - 1 from pg_stat_activity where current_query !~ '^<IDLE>'" )) ipsessions = list(cherrypy.tools.rate_limiter.cache.values()) # pylint: disable=E1101 adns = asyncdns.AsyncDNS() # blocked IPs blocked = sorted([s for s in ipsessions if s.get('blocked', 0) >= 2], key=lambda s: s.ips.sort_key()) if 'resolve' in kwargs: for d in blocked: if d.ips.ipinfo is None: d.ips.ipinfo = ipinfo.IPInfo(adns, d.ips.get_ip_to_block()) # active IPs active = sorted([s for s in ipsessions if s.get('active', False)], key=lambda s: s.ips.sort_key()) # busiest IPs busiest = sorted([s for s in active if s.get('blocked', 0) < 2], key=lambda x: -x.get('rhits'))[:10] if 'resolve' in kwargs: for d in busiest: if d.ips.ipinfo is None: d.ips.ipinfo = ipinfo.IPInfo(adns, d.ips.get_ip_to_block()) # IPs with most sessions most_sessions = sorted([ s for s in active if not s.ips.whitelisted and len(s.sessions) > 1 ], key=lambda s: -len(s.sessions))[:10] if 'resolve' in kwargs: for d in most_sessions: if d.ips.ipinfo is None: d.ips.ipinfo = ipinfo.IPInfo(adns, d.ips.get_ip_to_block()) adns.wait() adns.cancel() return self.output( 'stats', active=active, blocked=blocked, busiest=busiest, most_sessions=most_sessions, resolve='resolve' in kwargs, rl=cherrypy.tools.rate_limiter, # pylint: disable=E1101 backends=backends, active_backends=active_backends)
def index(self, **dummy_kwargs): """ Output the suggestions page. """ cherrypy.request.params['format'] = 'json' # override user os = BaseSearcher.OpenSearch() os.sort_order = 'nentry' os.start_index = 1 os.items_per_page = 5 if os.format != 'json': raise cherrypy.HTTPError(400, 'Bad Request. Unknown format.') if len(os.query) == 0: raise cherrypy.HTTPError(400, 'Bad Request. No query.') last_word = os.query.split()[-1] if len(last_word) < 4: raise cherrypy.HTTPError(400, 'Bad Request. Query too short.') # ok. request looks sane. process it os.log_request('suggestions') os.f_format_title = os.format_suggestion os.f_format_subtitle = os.format_none os.f_format_extra = os.format_none os.f_format_url = os.format_none os.f_format_thumb_url = os.format_none os.f_format_icon = os.format_none sql = BaseSearcher.SQLStatement() # prepare inner query sql.query = 'SELECT tsvec' sql.from_ = ('books', ) sql.fulltext('books.tsvec', os.query) inner_sql_query = self.sql_searcher.mogrify(os, sql) sql.query = "SELECT substr (word, 2) AS title FROM ts_stat ( %(inner)s )" sql.from_ = () sql.params['inner'] = inner_sql_query sql.where = ["word ~* %(re_word)s"] sql.params['re_word'] = '^0' + last_word try: os = self.sql_searcher.search(os, sql) except DatabaseError as what: cherrypy.log("SQL Error: " + str(what), context='REQUEST', severity=logging.ERROR) raise cherrypy.HTTPError(500, 'Internal Server Error.') os.template = os.page = 'results' os.finalize() return self.format(os)
def index(self, **dummy_kwargs): """ Output the start page. """ os = BaseSearcher.OpenSearch() os.log_request('start') os.search_terms = '' os.title = { 'mobile': _('PG Mobile'), 'opds': _('Project Gutenberg'), 'stanza': _('Project Gutenberg') }.get(os.format, _('Search Project Gutenberg')) cat = BaseSearcher.Cat() cat.header = _( 'Welcome to Project Gutenberg. Use the search box to find your book or pick a link.' ) cat.title = _('Popular') cat.subtitle = _('Our most popular books.') cat.url = os.url('search', sort_order='downloads') cat.class_ += 'navlink' cat.icon = 'popular' cat.order = 2 os.entries.append(cat) cat = BaseSearcher.Cat() cat.title = _('Latest') cat.subtitle = _('Our latest releases.') cat.url = os.url('search', sort_order='release_date') cat.class_ += 'navlink' cat.icon = 'date' cat.order = 3 os.entries.append(cat) cat = BaseSearcher.Cat() cat.title = _('Random') cat.subtitle = _('Random books.') cat.url = os.url('search', sort_order='random') cat.class_ += 'navlink' cat.icon = 'random' cat.order = 4 os.entries.append(cat) os.total_results = 0 os.template = 'results' os.page = 'start' os.url_share = os.url('/', host=os.file_host) os.twit = os.tagline os.finalize() return self.format(os)
def index(self, **kwargs): """ Check with google. """ cherrypy.lib.caching.expires(0, True) os = BaseSearcher.OpenSearch() # Remove Session cookie. name = cherrypy.serving.request.config.get('tools.sessions.name', 'session_id') del cherrypy.serving.response.cookie[name] if 'recaptcha_challenge_field' in kwargs: response = submit(kwargs['recaptcha_challenge_field'], kwargs['recaptcha_response_field'], cherrypy.config['recaptcha_private_key'], cherrypy.request.remote.ip) cherrypy.ipsession.captcha_answer(response) if not response.is_valid: raise cherrypy.HTTPRedirect( os.url('captcha.question', error='incorrect-captcha-sol')) for req in reversed(cherrypy.ipsession['requests']): if 'captcha' not in req: raise cherrypy.HTTPRedirect(req) raise cherrypy.HTTPRedirect(os.url('start'))
def setup(self, os, sql): os.f_format_url = BaseSearcher.SearchUrlFormatter('bookshelf') os.f_format_thumb_url = os.format_none os.sort_orders = ('downloads', 'quantity', 'alpha', 'release_date', 'authors') os.icon = 'bookshelf' os.class_ += 'navlink' os.title = _('All Bookshelves') sql.query = """ SELECT bookshelves.bookshelf as title, bookshelves.pk as pk, max (books.release_date) as release_date, sum (books.downloads) as downloads, count (books.pk) as quantity""" sql.from_ = ('bookshelves', 'mn_books_bookshelves as mn', 'books') sql.groupby += ('bookshelves.bookshelf', 'bookshelves.pk') sql.where.append('bookshelves.pk = mn.fk_bookshelves') sql.where.append('books.pk = mn.fk_books') if len(os.query): sql.fulltext('bookshelves.tsvec', os.query) os.title = _("Bookshelves: {bookshelf}").format(bookshelf=os.query)
def fix_dc(self, dc, os): """ Add some info to dc for easier templating. Also make sure that dc `walks like a cat´. """ super(MobileFormatter, self).fix_dc(dc, os) for file_ in dc.files + dc.generated_files: if len(file_.mediatypes) == 1: type_ = six.text_type(file_.mediatypes[0]) m = type_.partition(';')[0] if m in MOBILE_TYPES: cat = BaseSearcher.Cat() cat.type = file_.mediatypes[0] cat.header = _('Download') cat.title = file_.hr_filetype cat.extra = file_.hr_extent cat.charset = file_.encoding cat.url = file_.url cat.icon = dc.icon cat.icon2 = 'download' cat.class_ += 'filelink' cat.order = 20 os.entries.append(cat)
def setup(self, os, sql): os.f_format_subtitle = os.format_subtitle os.f_format_url = BaseSearcher.SearchUrlFormatter('author') os.f_format_thumb_url = os.format_none os.sort_orders = ('downloads', 'quantity', 'alpha', 'release_date') os.icon = 'author' os.class_ += 'navlink' os.title = _('All Authors') sql.query = """ SELECT authors.author as title, coalesce (authors.born_floor || '', '') || '-' || coalesce (authors.died_floor || '', '') as subtitle, authors.pk as pk, max (books.release_date) as release_date, sum (books.downloads) as downloads, count (books.pk) as quantity""" sql.from_ = ('authors', 'mn_books_authors as mn', 'books') sql.groupby += ('authors.author', 'subtitle', 'authors.pk') sql.where.append('authors.pk = mn.fk_authors') sql.where.append('books.pk = mn.fk_books') if len(os.query): sql.fulltext('authors.tsvec', os.query) os.title = _("Authors: {author}").format(author=os.query) else: sql.where.append( "authors.author not in ('Various', 'Anonymous', 'Unknown')")
def fixup (self, os): if (os.start_index == 1 and len (os.entries) > 0): # browse-by-author page for maintainers if 'is-catalog-maintainer' in cherrypy.request.cookie: cat = BaseSearcher.Cat () cat.type = mt.html cat.rel = 'related' cat.title = _('Browse by Author') cat.url = "/browse/authors/%s#a%d" % (os.author[:1].lower (), os.id) cat.class_ += 'navlink grayed' cat.icon = 'internal' cat.order = 9 os.entries.insert (0, cat) # wikipedia links etc. rows = BaseSearcher.SQLSearcher.execute ( """SELECT url, description AS title FROM author_urls WHERE fk_authors = %(fk_authors)s""", { 'fk_authors': os.id } ) for row in rows: cat = BaseSearcher.Cat () cat.type = mt.html cat.rel = 'related' cat.title = _('See also: {title}').format (title = row.title) cat.url = row.url cat.class_ += 'navlink grayed' cat.icon = 'external' cat.order = 8 os.entries.insert (0, cat) # author aliases if os.format == 'html': rows = BaseSearcher.SQLSearcher.execute ( """SELECT alias AS title FROM aliases WHERE fk_authors = %(fk_authors)s AND alias_heading = 1""", { 'fk_authors': os.id } ) for row in rows: cat = BaseSearcher.Cat () cat.title = _('Alias {alias}').format (alias = row.title) cat.class_ += 'grayed' cat.icon = 'alias' cat.order = 7 os.entries.insert (0, cat)
def tick(self): """ Do things here. """ try: BaseSearcher.books_in_archive = BaseSearcher.sql_get( 'select count (*) from books') except: pass
def sort_by_release_date(os): """ Append the sort by release date link. """ cat = BaseSearcher.Cat() cat.rel = 'new' cat.title = _('Sort by Release Date') cat.url = os.url_carry(sort_order='release_date') cat.class_ += 'navlink grayed' cat.icon = 'date' cat.order = 4.3 os.entries.insert(0, cat)
def did_you_mean(os, corr, corrected_query): """ Message. """ cat = BaseSearcher.Cat() cat.rel = '__didyoumean__' cat.title = _('Did you mean: {correction}').format(correction=corr) cat.url = os.url('search', query=corrected_query) cat.class_ += 'navlink' cat.icon = 'suggestion' cat.order = 12 return cat
def sort_by_quantity(os): """ Append the sort by quantity link. """ cat = BaseSearcher.Cat() cat.rel = 'numerous' cat.title = _('Sort by Quantity') cat.url = os.url_carry(sort_order='quantity') cat.class_ += 'navlink grayed' cat.icon = 'quantity' cat.order = 4.2 os.entries.insert(0, cat)
def no_records_found(os): """ Message. """ cat = BaseSearcher.Cat() cat.rel = '__notfound__' cat.title = _('No records found.') cat.url = os.url('start') cat.class_ += 'navlink grayed' cat.icon = 'bibrec' cat.order = 11 return cat
def sort_by_title(os): """ Append the sort alphabetically link. """ cat = BaseSearcher.Cat() cat.rel = 'alphabethical' cat.title = _('Sort Alphabetically') cat.url = os.url_carry(sort_order='title') cat.class_ += 'navlink grayed' cat.icon = 'alpha' cat.order = 4.1 os.entries.insert(0, cat)
def sort_by_downloads(os): """ Append the sort by downloads link. """ cat = BaseSearcher.Cat() cat.rel = 'popular' cat.title = _('Sort by Popularity') cat.url = os.url_carry(sort_order='downloads') cat.class_ += 'navlink grayed' cat.icon = 'popular' cat.order = 4.0 os.entries.insert(0, cat)
def status_line(os): """ Placeholder for status line. """ cat = BaseSearcher.Cat() cat.rel = '__statusline__' cat.class_ += 'grayed' cat.icon = 'bibrec' cat.order = 10 cat.header = os.title cat.title = _(u"Displaying results {from_}–{to}").format( from_=os.start_index, to=os.end_index) return cat
def index (self, **dummy_kwargs): """ Output sitemap index. """ sitemaps = [] now = datetime.datetime.utcnow ().replace (microsecond = 0).isoformat () + 'Z' # 99999 is safeguard against bogus ebook numbers lastbook = BaseSearcher.sql_get ('select max (pk) as lastbook from books where pk < 99999') os = BaseSearcher.OpenSearch () host = cherrypy.config['host'] for n in range (0, lastbook // SITEMAP_SIZE + 1): sitemap = Struct () sitemap.loc = os.url ('sitemap_index', page = n, host = host, format = None) sitemap.lastmod = now sitemaps.append (sitemap) data = Struct () data.sitemaps = sitemaps return self.output ('sitemap-index', data = data)
def setup(self, os, sql): os.sort_orders = ('downloads', 'title', 'release_date') os.title_icon = 'author' os.icon = 'book' os.class_ += 'booklink' os.f_format_icon = os.format_icon_titles os.author = BaseSearcher.sql_get( "select author from authors where pk = %(pk)s", pk=os.id) os.title = _('Books by {author}').format(author=os.author) sql.from_.append('mn_books_authors as mn') sql.where.append('books.pk = mn.fk_books') sql.where.append("mn.fk_authors = %(fk_authors)s") sql.params['fk_authors'] = os.id
def setup(self, os, sql): os.sort_orders = ('downloads', 'title', 'author', 'release_date') os.title_icon = 'bookshelf' os.icon = 'book' os.class_ += 'booklink' os.f_format_icon = os.format_icon_titles os.bookshelf = BaseSearcher.sql_get( "select bookshelf from bookshelves where pk = %(pk)s", pk=os.id) os.title = _('Books in {bookshelf}').format(bookshelf=os.bookshelf) sql.from_.append('mn_books_bookshelves as mn') sql.where.append('books.pk = mn.fk_books') sql.where.append("mn.fk_bookshelves = %(fk_bookshelves)s") sql.params['fk_bookshelves'] = os.id
def setup(self, os, sql): os.sort_orders = ('downloads', 'title', 'release_date') os.title_icon = 'subject' os.icon = 'book' os.class_ += 'booklink' os.f_format_icon = os.format_icon_titles os.subject = BaseSearcher.sql_get( "select subject from subjects where pk = %(pk)s", pk=os.id) os.title = _('Books about {subject}').format(subject=os.subject) sql.from_.append('mn_books_subjects as mn') sql.where.append('books.pk = mn.fk_books') sql.where.append("mn.fk_subjects = %(fk_subjects)s") sql.params['fk_subjects'] = os.id
def index(self, **kwargs): """ Output captcha. """ cherrypy.lib.caching.expires(3600, True) os = BaseSearcher.OpenSearch() os.template = 'recaptcha' os.recaptcha_public_key = cherrypy.config['recaptcha_public_key'] os.error = kwargs.get('error') os.finalize() # Remove Session cookie, so that page can be cached. name = cherrypy.serving.request.config.get('tools.sessions.name', 'session_id') del cherrypy.serving.response.cookie[name] return self.format(os)
def index (self, **kwargs): """ Output sitemap. """ urls = [] start = int (kwargs['page']) * SITEMAP_SIZE rows = BaseSearcher.SQLSearcher.execute ( 'select pk from books where pk >= %(start)s and pk < %(end)s order by pk', { 'start': str (start), 'end': str (start + SITEMAP_SIZE) }) os = BaseSearcher.OpenSearch () host = cherrypy.config['host'] for row in rows: url = Struct () url.loc = os.url ('bibrec', id = row[0], host = host, format = None) urls.append (url) data = Struct () data.urls = urls return self.output ('sitemap', data = data)
def output_suggestions(self, os, max_suggestions_per_word=3): """ Make suggestions. """ # similarity == matching_trigrams / (len1 + len2 - matching_trigrams) sql_query = """ SELECT word, nentry, similarity (word, %(word)s) AS sml FROM terms WHERE word %% %(word)s ORDER BY sml DESC, nentry DESC LIMIT %(suggestions)s;""" q = os.query.lower() sugg = [] for word in q.split(): if len(word) > 3: try: rows = BaseSearcher.SQLSearcher().execute( sql_query, { 'word': word, 'suggestions': max_suggestions_per_word + 1 }) for i, row in enumerate(rows): if i >= max_suggestions_per_word: break corr = row.word if corr != word: sugg.append((word, corr)) except DatabaseError: pass for word, corr in reversed(sugg): os.entries.insert( 0, self.did_you_mean(os, corr, q.replace(word, corr)))
def get_dc (self): """ Get a DublinCore struct for the ebook. """ dc = BaseSearcher.DC (cherrypy.engine.pool) dc.load_from_database (self.id) # dc.translate () return dc
def index(self, **kwargs): os = BaseSearcher.OpenSearch() raise cherrypy.HTTPRedirect(os.url('start'))
def fixup(self, os): """ strip marc subfields, add social media hints and facet links """ for e in os.entries: if '$' in e.title: e.title = DublinCore.strip_marc_subfields(e.title) if (os.sort_order == 'release_date' and os.total_results > 0 and os.start_index == 1): cat = BaseSearcher.Cat() cat.title = _('Follow new books on Twitter') cat.subtitle = _("Follow our new books on Twitter.") cat.url = 'https://twitter.com/gutenberg_new' cat.class_ += 'navlink grayed' cat.icon = 'twitter' cat.order = 5 os.entries.insert(0, cat) cat = BaseSearcher.Cat() cat.title = _('Follow new books on Facebook') cat.subtitle = _( "Follow the link and like the page to have us post new books to your wall." ) cat.url = 'https://www.facebook.com/gutenberg.new' cat.class_ += 'navlink grayed' cat.icon = 'facebook' cat.order = 5 os.entries.insert(0, cat) if (len(os.query) and os.start_index == 1): sql2 = BaseSearcher.SQLStatement() sql2.query = "select count (*) from bookshelves" sql2.fulltext('bookshelves.tsvec', os.query) rows = BaseSearcher.SQLSearcher.execute(*sql2.build()) if rows[0][0] > 0: cat = BaseSearcher.Cat() cat.rel = 'related' cat.title = _('Bookshelves') cat.subtitle = __('One bookshelf matches your query.', '{count} bookshelves match your search.', rows[0][0]).format(count=rows[0][0]) cat.url = os.url('bookshelf_search', query=os.query) cat.class_ += 'navlink grayed' cat.icon = 'bookshelf' cat.order = 3 os.entries.insert(0, cat) sql2 = BaseSearcher.SQLStatement() sql2.query = "select count (*) from subjects" sql2.fulltext('subjects.tsvec', os.query) rows = BaseSearcher.SQLSearcher.execute(*sql2.build()) if rows[0][0] > 0: cat = BaseSearcher.Cat() cat.rel = 'related' cat.title = _('Subjects') cat.subtitle = __( 'One subject heading matches your search.', '{count} subject headings match your search.', rows[0][0]).format(count=rows[0][0]) cat.url = os.url('subject_search', query=os.query) cat.class_ += 'navlink grayed' cat.icon = 'subject' cat.order = 3 os.entries.insert(0, cat) sql2 = BaseSearcher.SQLStatement() sql2.query = "select count (*) from authors" sql2.fulltext('authors.tsvec', os.query) rows = BaseSearcher.SQLSearcher.execute(*sql2.build()) if rows[0][0] > 0: cat = BaseSearcher.Cat() cat.rel = 'related' cat.title = _('Authors') cat.subtitle = __('One author name matches your search.', '{count} author names match your search.', rows[0][0]).format(count=rows[0][0]) cat.url = os.url('author_search', query=os.query) cat.class_ += 'navlink grayed' cat.icon = 'author' cat.order = 3 os.entries.insert(0, cat)
def index(self, **dummy_kwargs): """ A bibrec page. """ os = BaseSearcher.OpenSearch() os.log_request('bibrec') dc = BaseSearcher.DC(cherrypy.engine.pool) # the bulk of the work is done here dc.load_from_database(os.id) if not dc.files: # NOTE: Error message cherrypy.tools.rate_limiter.e404() raise cherrypy.HTTPError(404, _('No ebook by that number.')) # add these fields so we won't have to test for their existence later dc.extra_info = None dc.url = None dc.translate() dc.header = gg.cut_at_newline(dc.title) os.title = dc.make_pretty_title() dc.extra_info = '' dc.class_ = BaseSearcher.ClassAttr() dc.order = 10 dc.icon = 'book' if 'Sound' in dc.categories: dc.icon = 'audiobook' os.title_icon = dc.icon os.twit = os.title os.qrcode_url = '//%s/cache/epub/%d/pg%d.qrcode.png' % (os.file_host, os.id, os.id) os.entries.append(dc) s = cherrypy.session last_visited = s.get('last_visited', []) last_visited.append(os.id) s['last_visited'] = last_visited # can we find some meaningful breadcrumbs ? for a in dc.authors: if a.marcrel in ('aut', 'cre'): book_cnt = BaseSearcher.sql_get( "select count (*) from mn_books_authors where fk_authors = %(aid)s", aid=a.id) if book_cnt > 1: os.breadcrumbs.append( (__('One by {author}', '{count} by {author}', book_cnt).format(count=book_cnt, author=dc.make_pretty_name( a.name)), _('Find more ebooks by the same author.'), os.url('author', id=a.id))) if os.format in ('html', 'mobile'): cat = BaseSearcher.Cat() cat.header = _('Similar Books') cat.title = _('Readers also downloaded…') cat.rel = 'related' cat.url = os.url('also', id=os.id) cat.class_ += 'navlink grayed noprint' cat.icon = 'suggestion' cat.order = 30 os.entries.append(cat) for bookshelf in dc.bookshelves: cat = BaseSearcher.Cat() cat.title = _('In {bookshelf}').format( bookshelf=bookshelf.bookshelf) cat.rel = 'related' cat.url = os.url('bookshelf', id=bookshelf.id) cat.class_ += 'navlink grayed' cat.icon = 'bookshelf' cat.order = 33 os.entries.append(cat) if os.format in ('mobile', ): for author in dc.authors: cat = BaseSearcher.Cat() cat.title = _('By {author}').format( author=author.name_and_dates) cat.rel = 'related' cat.url = os.url('author', id=author.id) cat.class_ += 'navlink grayed' cat.icon = 'author' cat.order = 31 os.entries.append(cat) for subject in dc.subjects: cat = BaseSearcher.Cat() cat.title = _('On {subject}').format(subject=subject.subject) cat.rel = 'related' cat.url = os.url('subject', id=subject.id) cat.class_ += 'navlink grayed' cat.icon = 'subject' cat.order = 32 os.entries.append(cat) os.total_results = 1 os.template = 'results' if os.format == 'mobile' else 'bibrec' os.page = 'bibrec' os.og_type = 'book' os.finalize() return self.format(os)
def index(self, **kwargs): """ Output a search result page. """ os = BaseSearcher.OpenSearch() os.log_request('search') if 'default_prefix' in kwargs: raise cherrypy.HTTPError( 400, 'Bad Request. Unknown parameter: default_prefix') if os.start_index > BaseSearcher.MAX_RESULTS: raise cherrypy.HTTPError( 400, 'Bad Request. Parameter start_index too high') sql = BaseSearcher.SQLStatement() sql.query = 'SELECT *' sql.from_ = ['v_appserver_books_4 as books'] # let derived classes prepare the query try: self.setup(os, sql) except ValueError as what: raise cherrypy.HTTPError(400, 'Bad Request. ' + str(what)) os.fix_sortorder() # execute the query try: BaseSearcher.SQLSearcher().search(os, sql) except DatabaseError as what: cherrypy.log("SQL Error: " + str(what), context='REQUEST', severity=logging.ERROR) raise cherrypy.HTTPError(400, 'Bad Request. Check your query.') # sync os.title and first entry header if os.entries: entry = os.entries[0] if os.title and not entry.header: entry.header = os.title elif entry.header and not os.title: os.title = entry.header os.template = os.page = 'results' # give derived class a chance to tweak result set self.fixup(os) # warn user about no records found if os.total_results == 0: self.nothing_found(os) # suggest alternate queries if os.total_results < 5: self.output_suggestions(os) # add sort by links if os.start_index == 1 and os.total_results > 1: if 'downloads' in os.alternate_sort_orders: self.sort_by_downloads(os) if 'release_date' in os.alternate_sort_orders: self.sort_by_release_date(os) if 'title' in os.alternate_sort_orders: self.sort_by_title(os) if 'alpha' in os.alternate_sort_orders: self.sort_alphabetically(os) if 'quantity' in os.alternate_sort_orders: self.sort_by_quantity(os) os.finalize() self.finalize(os) if os.total_results > 0: # call this after finalize () os.entries.insert(0, self.status_line(os)) return self.format(os)