Repository: getnikola/nikola

Issue:
post-list makes posts always out-of-date
Ideally: they would only be rebuilt when the actual list of posts they display, or the content of any of those posts, changes.

Practically: they should depend on the whole timeline not changing, at least react to post addition/removal.



Discussion:

Resolving patch:
diff --git a/nikola/plugins/task/posts.py b/nikola/plugins/task/posts.py
--- a/nikola/plugins/task/posts.py
+++ b/nikola/plugins/task/posts.py
@@ -40,10 +40,6 @@ def update_deps(post, lang, task):
     task.file_dep.update([p for p in post.fragment_deps(lang) if not p.startswith("####MAGIC####")])
 
 
-def dependence_on_timeline(post, lang):
-    return "####MAGIC####TIMELINE" not in post.fragment_deps(lang)
-
-
 class RenderPosts(Task):
     """Build HTML fragments from metadata and text."""
 
@@ -59,13 +55,25 @@ def gen_tasks(self):
             "show_untranslated_posts": self.site.config['SHOW_UNTRANSLATED_POSTS'],
             "demote_headers": self.site.config['DEMOTE_HEADERS'],
         }
+        self.tl_changed = False
 
         yield self.group_task()
 
+        def tl_ch():
+            self.tl_changed = True
+
+        yield {
+            'basename': self.name,
+            'name': 'timeline_changes',
+            'actions': [tl_ch],
+            'uptodate': [utils.config_changed({1: kw['timeline']})],
+        }
+
         for lang in kw["translations"]:
             deps_dict = copy(kw)
             deps_dict.pop('timeline')
             for post in kw['timeline']:
+
                 dest = post.translated_base_path(lang)
                 file_dep = [p for p in post.fragment_deps(lang) if not p.startswith("####MAGIC####")]
                 task = {
@@ -79,7 +87,15 @@ def gen_tasks(self):
                     'clean': True,
                     'uptodate': [
                         utils.config_changed(deps_dict, 'nikola.plugins.task.posts'),
-                        lambda p=post, l=lang: dependence_on_timeline(p, l)
+                        lambda p=post, l=lang: self.dependence_on_timeline(p, l)
                     ] + post.fragment_deps_uptodate(lang),
+                    'task_dep': ['render_posts:timeline_changes']
                 }
                 yield task
+
+    def dependence_on_timeline(self, post, lang):
+        if "####MAGIC####TIMELINE" not in post.fragment_deps(lang):
+            return True  # No dependency on timeline
+        elif self.tl_changed:
+            return False  # Timeline changed
+        return True
diff --git a/nikola/post.py b/nikola/post.py
--- a/nikola/post.py
+++ b/nikola/post.py
@@ -29,6 +29,8 @@
 import io
 from collections import defaultdict
 import datetime
+import hashlib
+import json
 import os
 import re
 import string
@@ -229,7 +231,12 @@ def __init__(
         self.compiler.register_extra_dependencies(self)
 
     def __repr__(self):
-        return '<Post: {0}>'.format(self.source_path)
+        # Calculate a hash that represents most data about the post
+        m = hashlib.md5()
+        # source_path modification date (to avoid reading it)
+        m.update(utils.unicode_str(os.stat(self.source_path).st_mtime).encode('utf-8'))
+        m.update(utils.unicode_str(json.dumps(self.meta, cls=utils.CustomEncoder, sort_keys=True)).encode('utf-8'))
+        return '<Post: {0} {1}>'.format(self.source_path, m.hexdigest())
 
     def _has_pretty_url(self, lang):
         if self.pretty_urls and \
diff --git a/nikola/utils.py b/nikola/utils.py
--- a/nikola/utils.py
+++ b/nikola/utils.py
@@ -57,7 +57,7 @@
 
 from nikola import DEBUG
 
-__all__ = ['get_theme_path', 'get_theme_chain', 'load_messages', 'copy_tree',
+__all__ = ['CustomEncoder', 'get_theme_path', 'get_theme_chain', 'load_messages', 'copy_tree',
            'copy_file', 'slugify', 'unslugify', 'to_datetime', 'apply_filters',
            'config_changed', 'get_crumbs', 'get_tzname', 'get_asset_path',
            '_reload', 'unicode_str', 'bytes_str', 'unichr', 'Functionary',


Test patch: