"Fossies" - the Fresh Open Source Software Archive

Member "buku-4.3/bukuserver/views.py" (31 Jan 2020, 24034 Bytes) of package /linux/privat/buku-4.3.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Python source code syntax highlighting (style: standard) with prefixed line numbers. Alternatively you can here view or download the uninterpreted source code file. For more information about "views.py" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 4.2.2_vs_4.3.

    1 """views module."""
    2 from argparse import Namespace
    3 from collections import Counter
    4 from types import SimpleNamespace
    5 from typing import Any, List, Optional, Tuple
    6 from urllib.parse import urlparse
    7 import itertools
    8 import logging
    9 
   10 from flask import current_app, flash, redirect, request, url_for
   11 from flask_admin.babel import gettext
   12 from flask_admin.base import AdminIndexView, BaseView, expose
   13 from flask_admin.model import BaseModelView
   14 from flask_wtf import FlaskForm
   15 from jinja2 import Markup
   16 import arrow
   17 import wtforms
   18 
   19 try:
   20     from . import forms, filters as bs_filters
   21     from .filters import BookmarkField, FilterType
   22 except ImportError:
   23     from bukuserver import forms, filters as bs_filters
   24     from bukuserver.filters import BookmarkField, FilterType
   25 
   26 
   27 STATISTIC_DATA = None
   28 DEFAULT_URL_RENDER_MODE = 'full'
   29 DEFAULT_PER_PAGE = 10
   30 LOG = logging.getLogger("bukuserver.views")
   31 
   32 
   33 class CustomAdminIndexView(AdminIndexView):
   34 
   35     @expose('/')
   36     def index(self):
   37         return self.render('bukuserver/home.html', form=forms.HomeForm())
   38 
   39     @expose('/', methods=['POST',])
   40     def search(self):
   41         "redirect to bookmark search"
   42         form = forms.HomeForm()
   43         bbm_filter = bs_filters.BookmarkBukuFilter(
   44             all_keywords=False, deep=form.deep.data, regex=form.regex.data)
   45         op_text = bbm_filter.operation()
   46         values_combi = sorted(itertools.product([True, False], repeat=3))
   47         for idx, (all_keywords, deep, regex) in enumerate(values_combi):
   48             if deep == form.deep.data and regex == form.regex.data and not all_keywords:
   49                 choosen_idx = idx
   50         url_op_text = op_text.replace(', ', '_').replace('  ', ' ').replace(' ', '_')
   51         key = ''.join(['flt', str(choosen_idx), '_buku_', url_op_text])
   52         kwargs = {key: form.keyword.data}
   53         url = url_for('bookmark.index_view', **kwargs)
   54         return redirect(url)
   55 
   56 
   57 class CustomBukuDbModel:  # pylint: disable=too-few-public-methods
   58 
   59     def __init__(self, bukudb_inst, name):
   60         self.bukudb = bukudb_inst
   61         self.name = name
   62 
   63     @property
   64     def __name__(self):
   65         return self.name
   66 
   67 
   68 class BookmarkModelView(BaseModelView):
   69 
   70     def _apply_filters(self, models, filters):
   71         for idx, flt_name, value in filters:
   72             flt = self._filters[idx]
   73             clean_value = flt.clean(value)
   74             models = list(flt.apply(models, clean_value))
   75         return models
   76 
   77     def _create_ajax_loader(self, name, options):
   78         pass
   79 
   80     def _list_entry(
   81             self, context: Any, model: Namespace, name: str) -> Markup:
   82         parsed_url = urlparse(model.url)
   83         netloc, scheme = parsed_url.netloc, parsed_url.scheme
   84         is_scheme_valid = scheme in ('http', 'https')
   85         tag_text = []
   86         tag_tmpl = '<a class="btn btn-default" href="{1}">{0}</a>'
   87         for tag in model.tags.split(','):
   88             if tag:
   89                 tag_text.append(tag_tmpl.format(tag, url_for(
   90                     'bookmark.index_view', flt2_tags_contain=tag)))
   91         if not netloc:
   92             return Markup("""\
   93             {0.title}<br/>{2}<br/>{1}{0.description}
   94             """.format(
   95                 model, ''.join(tag_text), Markup.escape(model.url)
   96             ))
   97         res = ''
   98         if not current_app.config.get('BUKUSERVER_DISABLE_FAVICON', False):
   99             netloc_tmpl = '<img src="{}{}"/> '
  100             res = netloc_tmpl.format(
  101                 'http://www.google.com/s2/favicons?domain=', netloc)
  102         title = model.title if model.title else '&lt;EMPTY TITLE&gt;'
  103         open_in_new_tab = current_app.config.get('BUKUSERVER_OPEN_IN_NEW_TAB', False)
  104         if is_scheme_valid and open_in_new_tab:
  105             res += '<a href="{0.url}" target="_blank">{1}</a>'.format(model, title)
  106         elif is_scheme_valid and not open_in_new_tab:
  107             res += '<a href="{0.url}">{1}</a>'.format(model, title)
  108         else:
  109             res += title
  110         if self.url_render_mode == 'netloc':
  111             res += ' (<a href="{1}">{0}</a>)'.format(
  112                 netloc,
  113                 url_for('bookmark.index_view', flt2_url_netloc_match=netloc)
  114             )
  115         res += '<br/>'
  116         if not is_scheme_valid:
  117             res += model.url
  118         elif self.url_render_mode is None or self.url_render_mode == 'full':
  119             res += '<a href="{0.url}">{0.url}</a>'.format(model)
  120             res += '<br/>'
  121         if self.url_render_mode != 'netloc':
  122             res += tag_tmpl.format(
  123                 'netloc:{}'.format(netloc),
  124                 url_for('bookmark.index_view', flt2_url_netloc_match=netloc)
  125             )
  126         res += ''.join(tag_text)
  127         description = model.description
  128         if description:
  129             res += '<br/>'
  130             res += description.replace('\n', '<br/>')
  131         return Markup(res)
  132 
  133     can_set_page_size = True
  134     can_view_details = True
  135     column_filters = ['buku', 'id', 'url', 'title', 'tags']
  136     column_formatters = {'Entry': _list_entry,}
  137     column_list = ['Entry']
  138     create_modal = True
  139     create_modal_template = 'bukuserver/bookmark_create_modal.html'
  140     create_template = 'bukuserver/bookmark_create.html'
  141     details_modal = True
  142     edit_modal = True
  143     edit_modal_template = 'bukuserver/bookmark_edit_modal.html'
  144     edit_template = 'bukuserver/bookmark_edit.html'
  145     named_filter_urls = True
  146 
  147     def __init__(self, *args, **kwargs):
  148         self.bukudb = args[0]
  149         custom_model = CustomBukuDbModel(args[0], 'bookmark')
  150         args = [custom_model, ] + list(args[1:])
  151         self.page_size = kwargs.pop('page_size', DEFAULT_PER_PAGE)
  152         self.url_render_mode = kwargs.pop('url_render_mode', DEFAULT_URL_RENDER_MODE)
  153         super().__init__(*args, **kwargs)
  154 
  155     def create_model(self, form):
  156         try:
  157             model = SimpleNamespace(id=None, url=None, title=None, tags=None, description=None)
  158             form.populate_obj(model)
  159             vars(model).pop('id')
  160             self._on_model_change(form, model, True)
  161             tags_in = model.tags
  162             if not tags_in.startswith(','):
  163                 tags_in = ',{}'.format(tags_in)
  164             if not tags_in.endswith(','):
  165                 tags_in = '{},'.format(tags_in)
  166             self.model.bukudb.add_rec(
  167                 url=model.url, title_in=model.title, tags_in=tags_in, desc=model.description)
  168         except Exception as ex:
  169             if not self.handle_view_exception(ex):
  170                 flash(gettext('Failed to create record. %(error)s', error=str(ex)), 'error')
  171                 LOG.exception('Failed to create record.')
  172             return False
  173         else:
  174             self.after_model_change(form, model, True)
  175         return model
  176 
  177     def delete_model(self, model):
  178         try:
  179             self.on_model_delete(model)
  180             res = self.bukudb.delete_rec(model.id)
  181         except Exception as ex:
  182             if not self.handle_view_exception(ex):
  183                 flash(gettext('Failed to delete record. %(error)s', error=str(ex)), 'error')
  184                 LOG.exception('Failed to delete record.')
  185             return False
  186         else:
  187             self.after_model_delete(model)
  188         return res
  189 
  190     def get_list(self, page, sort_field, sort_desc, search, filters, page_size=None):
  191         bukudb = self.bukudb
  192         contain_buku_search = any(x[1] == 'buku' for x in filters)
  193         if contain_buku_search:
  194             mode_id = [x[0] for x in filters]
  195             if len(list(set(mode_id))) > 1:
  196                 flash(gettext('Invalid search mode combination'), 'error')
  197                 return 0, []
  198             keywords = [x[2] for x in filters]
  199             for idx, flt_name, value in filters:
  200                 if flt_name == 'buku':
  201                     flt = self._filters[idx]
  202             bookmarks = bukudb.searchdb(
  203                 keywords, all_keywords=flt.all_keywords, deep=flt.deep, regex=flt.regex)
  204         else:
  205             bookmarks = bukudb.get_rec_all()
  206         bookmarks = self._apply_filters(bookmarks, filters)
  207         if sort_field:
  208             key_idx = [x.value for x in BookmarkField if x.name.lower() == sort_field][0]
  209             bookmarks = sorted(bookmarks, key=lambda x: x[key_idx], reverse=sort_desc)
  210         count = len(bookmarks)
  211         if page_size and bookmarks:
  212             bookmarks = list(chunks(bookmarks, page_size))[page]
  213         data = []
  214         for bookmark in bookmarks:
  215             bm_sns = SimpleNamespace(id=None, url=None, title=None, tags=None, description=None)
  216             for field in list(BookmarkField):
  217                 if field == BookmarkField.TAGS:
  218                     value = bookmark[field.value]
  219                     if value.startswith(','):
  220                         value = value[1:]
  221                     if value.endswith(','):
  222                         value = value[:-1]
  223                     setattr(bm_sns, field.name.lower(), value)
  224                 else:
  225                     setattr(bm_sns, field.name.lower(), bookmark[field.value])
  226             data.append(bm_sns)
  227         return count, data
  228 
  229     def get_one(self, id):
  230         bookmark = self.model.bukudb.get_rec_by_id(id)
  231         bm_sns = SimpleNamespace(id=None, url=None, title=None, tags=None, description=None)
  232         for field in list(BookmarkField):
  233             if field == BookmarkField.TAGS and bookmark[field.value].startswith(','):
  234                 value = bookmark[field.value]
  235                 if value.startswith(','):
  236                     value = value[1:]
  237                 if value.endswith(','):
  238                     value = value[:-1]
  239                 setattr(bm_sns, field.name.lower(), value)
  240             else:
  241                 setattr(bm_sns, field.name.lower(), bookmark[field.value])
  242         return bm_sns
  243 
  244     def get_pk_value(self, model):
  245         return model.id
  246 
  247     def scaffold_list_columns(self):
  248         return [x.name.lower() for x in BookmarkField]
  249 
  250     def scaffold_list_form(self, widget=None, validators=None):
  251         pass
  252 
  253     def scaffold_sortable_columns(self):
  254         return {x:x for x in self.scaffold_list_columns()}
  255 
  256     def scaffold_filters(self, name):
  257         res = []
  258         if name == 'buku':
  259             values_combi = sorted(itertools.product([True, False], repeat=3))
  260             for all_keywords, deep, regex in values_combi:
  261                 res.append(
  262                     bs_filters.BookmarkBukuFilter(all_keywords=all_keywords, deep=deep, regex=regex)
  263                 )
  264         elif name == BookmarkField.ID.name.lower():
  265             res.extend([
  266                 bs_filters.BookmarkBaseFilter(name, filter_type=FilterType.EQUAL),
  267                 bs_filters.BookmarkBaseFilter(name, filter_type=FilterType.NOT_EQUAL),
  268                 bs_filters.BookmarkBaseFilter(name, filter_type=FilterType.IN_LIST),
  269                 bs_filters.BookmarkBaseFilter(name, filter_type=FilterType.NOT_IN_LIST),
  270                 bs_filters.BookmarkBaseFilter(name, filter_type=FilterType.GREATER),
  271                 bs_filters.BookmarkBaseFilter(name, filter_type=FilterType.SMALLER),
  272                 bs_filters.BookmarkBaseFilter(name, filter_type=FilterType.TOP_X),
  273                 bs_filters.BookmarkBaseFilter(name, filter_type=FilterType.BOTTOM_X),
  274             ])
  275         elif name == BookmarkField.URL.name.lower():
  276             def netloc_match_func(query, value, index):
  277                 return filter(lambda x: urlparse(x[index]).netloc == value, query)
  278 
  279             res.extend([
  280                 bs_filters.BookmarkBaseFilter(name, 'netloc match', netloc_match_func),
  281                 bs_filters.BookmarkBaseFilter(name, filter_type=FilterType.EQUAL),
  282                 bs_filters.BookmarkBaseFilter(name, filter_type=FilterType.NOT_EQUAL),
  283                 bs_filters.BookmarkBaseFilter(name, filter_type=FilterType.IN_LIST),
  284                 bs_filters.BookmarkBaseFilter(name, filter_type=FilterType.NOT_IN_LIST),
  285             ])
  286         elif name == BookmarkField.TITLE.name.lower():
  287             res.extend([
  288                 bs_filters.BookmarkBaseFilter(name, filter_type=FilterType.EQUAL),
  289                 bs_filters.BookmarkBaseFilter(name, filter_type=FilterType.NOT_EQUAL),
  290                 bs_filters.BookmarkBaseFilter(name, filter_type=FilterType.IN_LIST),
  291                 bs_filters.BookmarkBaseFilter(name, filter_type=FilterType.NOT_IN_LIST),
  292             ])
  293         elif name == BookmarkField.TAGS.name.lower():
  294             def tags_contain_func(query, value, index):
  295                 for item in query:
  296                     for tag in item[index].split(','):
  297                         if tag and tag == value:
  298                             yield item
  299 
  300             def tags_not_contain_func(query, value, index):
  301                 for item in query:
  302                     for tag in item[index].split(','):
  303                         if tag and tag == value:
  304                             yield item
  305 
  306             res.extend([
  307                 bs_filters.BookmarkBaseFilter(name, 'contain', tags_contain_func),
  308                 bs_filters.BookmarkBaseFilter(name, 'not contain', tags_not_contain_func),
  309                 bs_filters.BookmarkTagNumberEqualFilter(name, 'number equal'),
  310                 bs_filters.BookmarkTagNumberNotEqualFilter(name, 'number not equal'),
  311                 bs_filters.BookmarkTagNumberGreaterFilter(name, 'number greater than'),
  312                 bs_filters.BookmarkTagNumberSmallerFilter(name, 'number smaller than'),
  313             ])
  314         elif name in self.scaffold_list_columns():
  315             pass
  316         else:
  317             return super().scaffold_filters(name)
  318         return res
  319 
  320     def scaffold_form(self):
  321         cls = forms.BookmarkForm
  322         return cls
  323 
  324     def update_model(self, form, model):
  325         res = False
  326         try:
  327             original_tags = model.tags
  328             form.populate_obj(model)
  329             self._on_model_change(form, model, False)
  330             self.bukudb.delete_tag_at_index(model.id, original_tags)
  331             tags_in = model.tags
  332             if not tags_in.startswith(','):
  333                 tags_in = ',{}'.format(tags_in)
  334             if not tags_in.endswith(','):
  335                 tags_in = '{},'.format(tags_in)
  336             res = self.bukudb.update_rec(
  337                 model.id, url=model.url, title_in=model.title, tags_in=tags_in,
  338                 desc=model.description)
  339         except Exception as ex:
  340             if not self.handle_view_exception(ex):
  341                 flash(gettext('Failed to update record. %(error)s', error=str(ex)), 'error')
  342                 LOG.exception('Failed to update record.')
  343             return False
  344         else:
  345             self.after_model_change(form, model, False)
  346         return res
  347 
  348 
  349 class TagModelView(BaseModelView):
  350 
  351     def _create_ajax_loader(self, name, options):
  352         pass
  353 
  354     def _apply_filters(self, models, filters):
  355         for idx, flt_name, value in filters:
  356             flt = self._filters[idx]
  357             clean_value = flt.clean(value)
  358             models = list(flt.apply(models, clean_value))
  359         return models
  360 
  361     def _name_formatter(self, context, model, name):
  362         data = getattr(model, name)
  363         if not data:
  364             return Markup('<a href="{}">{}</a>'.format(
  365                 url_for('bookmark.index_view', flt2_tags_number_equal=0),
  366                 '&lt;EMPTY TAG&gt;'
  367             ))
  368         return Markup('<a href="{}">{}</a>'.format(
  369             url_for('bookmark.index_view', flt1_tags_contain=data), data
  370         ))
  371 
  372     can_create = False
  373     can_set_page_size = True
  374     column_filters = ['name', 'usage_count']
  375     column_formatters = {'name': _name_formatter,}
  376 
  377     def __init__(self, *args, **kwargs):
  378         self.bukudb = args[0]
  379         custom_model = CustomBukuDbModel(args[0], 'tag')
  380         args = [custom_model, ] + list(args[1:])
  381         self.page_size = kwargs.pop('page_size', DEFAULT_PER_PAGE)
  382         super().__init__(*args, **kwargs)
  383 
  384     def scaffold_list_columns(self):
  385         return ['name', 'usage_count']
  386 
  387     def scaffold_sortable_columns(self):
  388         return {x:x for x in self.scaffold_list_columns()}
  389 
  390     def scaffold_form(self):
  391         class CustomForm(FlaskForm):  # pylint: disable=too-few-public-methods
  392             name = wtforms.StringField(validators=[wtforms.validators.DataRequired()])
  393 
  394         return CustomForm
  395 
  396     def scaffold_list_form(self, widget=None, validators=None):
  397         pass
  398 
  399     def get_list(
  400             self,
  401             page: int,
  402             sort_field: str,
  403             sort_desc: bool,
  404             search: Optional[Any],
  405             filters: List[Tuple[int, str, str]],
  406             page_size: int = None) -> Tuple[int, List[SimpleNamespace]]:
  407         bukudb = self.bukudb
  408         tags = bukudb.get_tag_all()[1]
  409         tags = sorted(tags.items())
  410         tags = self._apply_filters(tags, filters)
  411         sort_field_dict = {'usage_count': 1, 'name': 0}
  412         if sort_field in sort_field_dict:
  413             tags = list(sorted(
  414                 tags, key=lambda x: x[sort_field_dict[sort_field]], reverse=sort_desc))
  415         count = len(tags)
  416         if page_size and tags:
  417             tags = list(chunks(tags, page_size))[page]
  418         data = []
  419         for name, usage_count in tags:
  420             tag_sns = SimpleNamespace(name=None, usage_count=None)
  421             tag_sns.name, tag_sns.usage_count = name, usage_count
  422             data.append(tag_sns)
  423         return count, data
  424 
  425     def get_pk_value(self, model):
  426         return model.name
  427 
  428     def get_one(self, id):
  429         tags = self.bukudb.get_tag_all()[1]
  430         tag_sns = SimpleNamespace(name=id, usage_count=tags[id])
  431         return tag_sns
  432 
  433     def scaffold_filters(self, name):
  434         res = []
  435 
  436         def top_most_common_func(query, value, index):
  437             counter = Counter(x[index] for x in query)
  438             most_common = counter.most_common(value)
  439             most_common_item = [x[0] for x in most_common]
  440             return filter(lambda x: x[index] in most_common_item, query)
  441 
  442         res.extend([
  443             bs_filters.TagBaseFilter(name, filter_type=FilterType.EQUAL),
  444             bs_filters.TagBaseFilter(name, filter_type=FilterType.NOT_EQUAL),
  445             bs_filters.TagBaseFilter(name, filter_type=FilterType.IN_LIST),
  446             bs_filters.TagBaseFilter(name, filter_type=FilterType.NOT_IN_LIST),
  447         ])
  448         if name == 'usage_count':
  449             res.extend([
  450                 bs_filters.TagBaseFilter(name, filter_type=FilterType.GREATER),
  451                 bs_filters.TagBaseFilter(name, filter_type=FilterType.SMALLER),
  452                 bs_filters.TagBaseFilter(name, filter_type=FilterType.TOP_X),
  453                 bs_filters.TagBaseFilter(name, filter_type=FilterType.BOTTOM_X),
  454                 bs_filters.TagBaseFilter(name, 'top most common', top_most_common_func),
  455             ])
  456         elif name == 'name':
  457             pass
  458         else:
  459             return super().scaffold_filters(name)
  460         return res
  461 
  462     def delete_model(self, model):
  463         res = None
  464         try:
  465             self.on_model_delete(model)
  466             res = self.bukudb.delete_tag_at_index(0, model.name, chatty=False)
  467         except Exception as ex:
  468             if not self.handle_view_exception(ex):
  469                 flash(gettext('Failed to delete record. %(error)s', error=str(ex)), 'error')
  470                 LOG.exception('Failed to delete record.')
  471             return False
  472         else:
  473             self.after_model_delete(model)
  474         return res
  475 
  476     def update_model(self, form, model):
  477         res = None
  478         try:
  479             original_name = model.name
  480             form.populate_obj(model)
  481             self._on_model_change(form, model, False)
  482             res = self.bukudb.replace_tag(original_name, [model.name])
  483         except Exception as ex:
  484             if not self.handle_view_exception(ex):
  485                 flash(gettext('Failed to update record. %(error)s', error=str(ex)), 'error')
  486                 LOG.exception('Failed to update record.')
  487             return False
  488         else:
  489             self.after_model_change(form, model, False)
  490         return res
  491 
  492     def create_model(self, form):
  493         pass
  494 
  495 
  496 class StatisticView(BaseView):  # pylint: disable=too-few-public-methods
  497 
  498     def __init__(self, *args, **kwargs):
  499         self.bukudb = args[0]
  500         args = list(args[1:])
  501         super().__init__(*args, **kwargs)
  502 
  503     @expose('/', methods=('GET', 'POST'))
  504     def index(self):
  505         bukudb = self.bukudb
  506         global STATISTIC_DATA
  507         statistic_data = STATISTIC_DATA
  508         if not statistic_data or request.method == 'POST':
  509             all_bookmarks = bukudb.get_rec_all()
  510             netloc = [urlparse(x[1]).netloc for x in all_bookmarks]
  511             tag_set = [x[3] for x in all_bookmarks]
  512             tag_items = []
  513             for tags in tag_set:
  514                 tag_items.extend([x.strip() for x in tags.split(',') if x.strip()])
  515             tag_counter = Counter(tag_items)
  516             title_items = [x[2] for x in all_bookmarks]
  517             title_counter = Counter(title_items)
  518             statistic_datetime = arrow.now()
  519             STATISTIC_DATA = {
  520                 'datetime': statistic_datetime,
  521                 'netloc': netloc,
  522                 'tag_counter': tag_counter,
  523                 'title_counter': title_counter,
  524             }
  525         else:
  526             netloc = statistic_data['netloc']
  527             statistic_datetime = statistic_data['datetime']
  528             tag_counter = statistic_data['tag_counter']
  529             title_counter = statistic_data['title_counter']
  530 
  531         netloc_counter = Counter(netloc)
  532         unique_netloc_len = len(set(netloc))
  533         colors = [
  534             "#F7464A", "#46BFBD", "#FDB45C", "#FEDCBA",
  535             "#ABCDEF", "#DDDDDD", "#ABCABC", "#4169E1",
  536             "#C71585", "#FF4500", "#FEDCBA", "#46BFBD"]
  537         show_netloc_table = False
  538         if unique_netloc_len > len(colors):
  539             max_netloc_item = len(colors)
  540             netloc_colors = colors
  541             show_netloc_table = True
  542         else:
  543             netloc_colors = colors[:unique_netloc_len]
  544             max_netloc_item = unique_netloc_len
  545         most_common_netlocs = netloc_counter.most_common(max_netloc_item)
  546         most_common_netlocs = [
  547             [val[0], val[1], netloc_colors[idx]] for idx, val in enumerate(most_common_netlocs)]
  548 
  549         unique_tag_len = len(tag_counter)
  550         show_tag_rank_table = False
  551         if unique_tag_len > len(colors):
  552             max_tag_item = len(colors)
  553             tag_colors = colors
  554             show_tag_rank_table = True
  555         else:
  556             tag_colors = colors[:unique_tag_len]
  557             max_tag_item = unique_tag_len
  558         most_common_tags = tag_counter.most_common(max_tag_item)
  559         most_common_tags = [
  560             [val[0], val[1], tag_colors[idx]] for idx, val in enumerate(most_common_tags)]
  561 
  562         unique_title_len = len(title_counter)
  563         show_title_rank_table = False
  564         if unique_title_len > len(colors):
  565             max_title_item = len(colors)
  566             title_colors = colors
  567             show_title_rank_table = True
  568         else:
  569             title_colors = colors[:unique_title_len]
  570             max_title_item = unique_title_len
  571         most_common_titles = title_counter.most_common(max_title_item)
  572         most_common_titles = [
  573             [val[0], val[1], title_colors[idx]] for idx, val in enumerate(most_common_titles)]
  574 
  575         return self.render(
  576             'bukuserver/statistic.html',
  577             most_common_netlocs=most_common_netlocs,
  578             netloc_counter=netloc_counter,
  579             show_netloc_table=show_netloc_table,
  580             most_common_tags=most_common_tags,
  581             tag_counter=tag_counter,
  582             show_tag_rank_table=show_tag_rank_table,
  583             most_common_titles=most_common_titles,
  584             title_counter=title_counter,
  585             show_title_rank_table=show_title_rank_table,
  586             datetime=statistic_datetime,
  587             datetime_text=statistic_datetime.humanize(arrow.now(), granularity='second'),
  588         )
  589 
  590 
  591 def chunks(l, n):
  592     n = max(1, n)
  593     return (l[i:i+n] for i in range(0, len(l), n))