Repository: getnikola/nikola

Issue:
Archive hierarchy navigation generates broken links in some cases
If a blog has more than one language and not all posts are available in all languages (and `SHOW_UNTRANSLATED_POSTS` is `False`), and if the archive plugin generates archives per day and `CREATE_ARCHIVE_NAVIGATION` is `True`, broken links can appear:

Say you have posts on 2017/04/22 and 2017/04/23, but on 2017/04/22 you have posts in all languages and on 2017/04/23 there's no translation for a specific language, then the archive page for 2017/04/23 for that language is not created, but the archive navigation for 2017/04/22 for that language has a next link to that non-existing archive.

This only happens on day level, since on month or year level, there's special code which generates the empty archives (so you get links to empty archives). This special logic was added in 650affef3ceb1895b18fcc7f8ef25165d76ffce9, which replicates the behavior of the old archives plugin (https://github.com/getnikola/nikola/blob/v7.8.1/nikola/plugins/task/archive.py).

There are two possible solutions:

  1. We modify the behavior of the archives taxonomy to not generate empty indexes (except for the main archive), i.e. we revert 650affef3ceb1895b18fcc7f8ef25165d76ffce9. Additionally we fix `Archive.postprocess_posts_per_classification` to ignore empty archives as follows:
``` .py
                for node in flat_hierarchy:
                    if not self.site.config["SHOW_UNTRANSLATED_POSTS"]:
                        if not [x for x in posts_per_section_per_language[lang][node.classification_name] if x.is_translation_available(lang)]:
                            continue
                    self.archive_navigation[lang][len(node.classification_path)].append(node.classification_name)
```

  2. We keep this strange logic, and modify `Archive.postprocess_posts_per_classification` to ignore empty archives only on the day level:
``` .py
                for node in flat_hierarchy:
                    if not self.site.config["SHOW_UNTRANSLATED_POSTS"] and len(node.classification_path) == 3:
                        if not [x for x in posts_per_section_per_language[lang][node.classification_name] if x.is_translation_available(lang)]:
                            continue
                    self.archive_navigation[lang][len(node.classification_path)].append(node.classification_name)
```

So the main question is: do we want to keep the strange behavior of the archive (and generate empty archives for year and month level, but not for day level), or just generate a empty global archive and don't generate other empty archives?

Or do we want to generate empty archives (so that there's a symmetry between different languages and you can easily point from the archive in one language to the same archives in other languages), but then generate all of them, and somehow fix the navigation links so you don't get links to empty archives? (Links to empty archives kind of suck from my point of view.)



Discussion:
How could users get to those *strange* empty archive pages? If it is just users messing with URLs, and (very rare) bugs in archive navigation, let’s stop creating them.
If you end up on the archive page for a date where a post exists in that language, and you use the navigation features which point to a broken link, you'll end up with a 404.

So assuming you can end up in a daily archive, it can happen that navigation links are broken.

Resolving patch:
diff --git a/nikola/plugins/task/archive.py b/nikola/plugins/task/archive.py
--- a/nikola/plugins/task/archive.py
+++ b/nikola/plugins/task/archive.py
@@ -219,7 +219,7 @@ def provide_context_and_uptodate(self, classification, lang, node=None):
         kw.update(context)
         return context, kw
 
-    def postprocess_posts_per_classification(self, posts_per_section_per_language, flat_hierarchy_per_lang=None, hierarchy_lookup_per_lang=None):
+    def postprocess_posts_per_classification(self, posts_per_archive_per_language, flat_hierarchy_per_lang=None, hierarchy_lookup_per_lang=None):
         """Rearrange, modify or otherwise use the list of posts per classification and per language."""
         # Build a lookup table for archive navigation, if we’ll need one.
         if self.site.config['CREATE_ARCHIVE_NAVIGATION']:
@@ -229,14 +229,17 @@ def postprocess_posts_per_classification(self, posts_per_section_per_language, f
             for lang, flat_hierarchy in flat_hierarchy_per_lang.items():
                 self.archive_navigation[lang] = defaultdict(list)
                 for node in flat_hierarchy:
+                    if not self.site.config["SHOW_UNTRANSLATED_POSTS"]:
+                        if not [x for x in posts_per_archive_per_language[lang][node.classification_name] if x.is_translation_available(lang)]:
+                            continue
                     self.archive_navigation[lang][len(node.classification_path)].append(node.classification_name)
 
                 # We need to sort it. Natsort means it’s year 10000 compatible!
                 for k, v in self.archive_navigation[lang].items():
                     self.archive_navigation[lang][k] = natsort.natsorted(v, alg=natsort.ns.F | natsort.ns.IC)
 
-        return super(Archive, self).postprocess_posts_per_classification(posts_per_section_per_language, flat_hierarchy_per_lang, hierarchy_lookup_per_lang)
+        return super(Archive, self).postprocess_posts_per_classification(posts_per_archive_per_language, flat_hierarchy_per_lang, hierarchy_lookup_per_lang)
 
     def should_generate_classification_page(self, classification, post_list, lang):
         """Only generates list of posts for classification if this function returns True."""
-        return len(classification.split('/')) < 3 or len(post_list) > 0
+        return classification == '' or len(post_list) > 0


Test patch: