Repository: getnikola/nikola

Issue:
indexes plugin results in exception if index link is created too early.
The `indexes` plugin needs `self.number_of_pages` and `self.number_of_pages_section` to be initialized for its path handlers to work. If some other task plugin tries to create a link to a (section) index before `gen_tasks()` of the `indexes` plugins is called, it will raise an exception.



Discussion:
Can you create steps to reproduce? I can take a look at fixing it.

It is prominently visible in the `earlytask_impl` branch, as soon as you tell nikola to build only certain tasks (instead of all). Since every plugin's tasks are only generated when they are about to be executed, the `indexes` tasks can easily not be generated when some other tasks are executed.

It is also possible to reproduce this with current, Nikola, though: if you write a task plugin which tries to generate a link during its task creation might or might not work: it depends on whether that plugin is called before or after the `indexes` plugin to generate its tasks.

Actually, changing the `indexes` plugin so that it can generate links to the (section) index at any time, but to specific pages only after its tasks have been generated, is totally fine (since almost nobody outside the `indexes` plugin is using that feature).


Resolving patch:
diff --git a/nikola/plugins/task/indexes.py b/nikola/plugins/task/indexes.py
--- a/nikola/plugins/task/indexes.py
+++ b/nikola/plugins/task/indexes.py
@@ -46,12 +46,29 @@ class Indexes(Task):
 
     def set_site(self, site):
         """Set Nikola site."""
+        self.number_of_pages = dict()
+        self.number_of_pages_section = {lang: dict() for lang in site.config['TRANSLATIONS']}
         site.register_path_handler('index', self.index_path)
         site.register_path_handler('index_atom', self.index_atom_path)
         site.register_path_handler('section_index', self.index_section_path)
         site.register_path_handler('section_index_atom', self.index_section_atom_path)
         return super(Indexes, self).set_site(site)
 
+    def _get_filtered_posts(self, lang, show_untranslated_posts):
+        """Return a filtered list of all posts for the given language.
+
+        If show_untranslated_posts is True, will only include posts which
+        are translated to the given language. Otherwise, returns all posts.
+        """
+        if show_untranslated_posts:
+            return self.site.posts
+        else:
+            return [x for x in self.site.posts if x.is_translation_available(lang)]
+
+    def _compute_number_of_pages(self, filtered_posts, posts_count):
+        """Given a list of posts and the maximal number of posts per page, computes the number of pages needed."""
+        return min(1, (len(filtered_posts) + posts_count - 1) // posts_count)
+
     def gen_tasks(self):
         """Render the blog indexes."""
         self.site.scan_posts()
@@ -72,9 +89,6 @@ def gen_tasks(self):
         }
 
         template_name = "index.tmpl"
-        posts = self.site.posts
-        self.number_of_pages = dict()
-        self.number_of_pages_section = dict()
         for lang in kw["translations"]:
             def page_link(i, displayed_i, num_pages, force_addition, extension=None):
                 feed = "_atom" if extension == ".atom" else ""
@@ -86,13 +100,10 @@ def page_path(i, displayed_i, num_pages, force_addition, extension=None):
                 return utils.adjust_name_for_index_path(self.site.path("index" + feed, None, lang), i, displayed_i,
                                                         lang, self.site, force_addition, extension)
 
-            if kw["show_untranslated_posts"]:
-                filtered_posts = posts
-            else:
-                filtered_posts = [x for x in posts if x.is_translation_available(lang)]
+            filtered_posts = self._get_filtered_posts(lang, kw["show_untranslated_posts"])
 
             indexes_title = kw['indexes_title'](lang) or kw['blog_title'](lang)
-            self.number_of_pages[lang] = (len(filtered_posts) + kw['index_display_post_count'] - 1) // kw['index_display_post_count']
+            self.number_of_pages[lang] = self._compute_number_of_pages(filtered_posts, kw['index_display_post_count'])
 
             context = {}
             context["pagekind"] = ["main_index", "index"]
@@ -113,9 +124,7 @@ def page_path(i, displayed_i, num_pages, force_addition, extension=None):
                     continue
 
                 for section_slug, post_list in groups.items():
-                    if lang not in self.number_of_pages_section:
-                        self.number_of_pages_section[lang] = dict()
-                    self.number_of_pages_section[lang][section_slug] = (len(post_list) + kw['index_display_post_count'] - 1) // kw['index_display_post_count']
+                    self.number_of_pages_section[lang][section_slug] = self._compute_number_of_pages(post_list, kw['index_display_post_count'])
 
                     def cat_link(i, displayed_i, num_pages, force_addition, extension=None):
                         feed = "_atom" if extension == ".atom" else ""
@@ -230,11 +239,16 @@ def index_path(self, name, lang, is_feed=False):
             index_file = os.path.splitext(self.site.config['INDEX_FILE'])[0] + extension
         else:
             index_file = self.site.config['INDEX_FILE']
+        if lang in self.number_of_pages:
+            number_of_pages = self.number_of_pages[lang]
+        else:
+            number_of_pages = self._compute_number_of_pages(self._get_filtered_posts(lang, self.site.config['SHOW_UNTRANSLATED_POSTS']), self.site.config['INDEX_DISPLAY_POST_COUNT'])
+            self.number_of_pages[lang] = number_of_pages
         return utils.adjust_name_for_index_path_list([_f for _f in [self.site.config['TRANSLATIONS'][lang],
                                                                     self.site.config['INDEX_PATH'],
                                                                     index_file] if _f],
                                                      name,
-                                                     utils.get_displayed_page_number(name, self.number_of_pages[lang], self.site),
+                                                     utils.get_displayed_page_number(name, number_of_pages, self.site),
                                                      lang,
                                                      self.site,
                                                      extension=extension)
@@ -253,11 +267,17 @@ def index_section_path(self, name, lang, is_feed=False):
             index_file = os.path.splitext(self.site.config['INDEX_FILE'])[0] + extension
         else:
             index_file = self.site.config['INDEX_FILE']
+        if name in self.number_of_pages_section[lang]:
+            number_of_pages = self.number_of_pages_section[lang][name]
+        else:
+            posts = [post for post in self._get_filtered_posts(lang, self.site.config['SHOW_UNTRANSLATED_POSTS']) if post.section_slug(lang) == name]
+            number_of_pages = self._compute_number_of_pages(posts, self.site.config['INDEX_DISPLAY_POST_COUNT'])
+            self.number_of_pages_section[lang][name] = number_of_pages
         return utils.adjust_name_for_index_path_list([_f for _f in [self.site.config['TRANSLATIONS'][lang],
                                                                     name,
                                                                     index_file] if _f],
                                                      None,
-                                                     utils.get_displayed_page_number(None, self.number_of_pages_section[lang][name], self.site),
+                                                     utils.get_displayed_page_number(None, number_of_pages, self.site),
                                                      lang,
                                                      self.site,
                                                      extension=extension)


Test patch: