Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement period_archives common context variable #3148

Merged
merged 5 commits into from
Oct 28, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 15 additions & 12 deletions docs/settings.rst
Original file line number Diff line number Diff line change
Expand Up @@ -561,44 +561,47 @@ written over time.
Example usage::

YEAR_ARCHIVE_SAVE_AS = 'posts/{date:%Y}/index.html'
YEAR_ARCHIVE_URL = 'posts/{date:%Y}/'
MONTH_ARCHIVE_SAVE_AS = 'posts/{date:%Y}/{date:%b}/index.html'
MONTH_ARCHIVE_URL = 'posts/{date:%Y}/{date:%b}/'

With these settings, Pelican will create an archive of all your posts for the
year at (for instance) ``posts/2011/index.html`` and an archive of all your
posts for the month at ``posts/2011/Aug/index.html``.
posts for the month at ``posts/2011/Aug/index.html``. These can be accessed
through the URLs ``posts/2011/`` and ``posts/2011/Aug/``, respectively.

.. note::
Period archives work best when the final path segment is ``index.html``.
This way a reader can remove a portion of your URL and automatically arrive
at an appropriate archive of posts, without having to specify a page name.

.. data:: YEAR_ARCHIVE_URL = ''

The URL to use for per-year archives of your posts. Used only if you have
the ``{url}`` placeholder in ``PAGINATION_PATTERNS``.

.. data:: YEAR_ARCHIVE_SAVE_AS = ''

The location to save per-year archives of your posts.

.. data:: MONTH_ARCHIVE_URL = ''
.. data:: YEAR_ARCHIVE_URL = ''

The URL to use for per-month archives of your posts. Used only if you have
the ``{url}`` placeholder in ``PAGINATION_PATTERNS``.
The URL to use for per-year archives of your posts. You should set this if
you enable per-year archives.

.. data:: MONTH_ARCHIVE_SAVE_AS = ''

The location to save per-month archives of your posts.

.. data:: DAY_ARCHIVE_URL = ''
.. data:: MONTH_ARCHIVE_URL = ''

The URL to use for per-day archives of your posts. Used only if you have the
``{url}`` placeholder in ``PAGINATION_PATTERNS``.
The URL to use for per-month archives of your posts. You should set this if
you enable per-month archives.

.. data:: DAY_ARCHIVE_SAVE_AS = ''

The location to save per-day archives of your posts.

.. data:: DAY_ARCHIVE_URL = ''

The URL to use for per-day archives of your posts. You should set this if
you enable per-day archives.

``DIRECT_TEMPLATES`` work a bit differently than noted above. Only the
``_SAVE_AS`` settings are available, but it is available for any direct
template.
Expand Down
63 changes: 63 additions & 0 deletions docs/themes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,8 @@ All templates will receive the variables defined in your settings file, as long
as they are in all-caps. You can access them directly.


.. _common_variables:

Common Variables
----------------

Expand All @@ -92,6 +94,10 @@ dates The same list of articles, but ordered by date,
ascending.
hidden_articles The list of hidden articles
drafts The list of draft articles
period_archives A dictionary containing elements related to
time-period archives (if enabled). See the section
:ref:`Listing and Linking to Period Archives
<period_archives_variable>` for details.
authors A list of (author, articles) tuples, containing all
the authors and corresponding articles (values)
categories A list of (category, articles) tuples, containing
Expand Down Expand Up @@ -348,6 +354,63 @@ period_archives.html template
<https://github.com/getpelican/pelican/blob/master/pelican/themes/simple/templates/period_archives.html>`_.


.. _period_archives_variable:

Listing and Linking to Period Archives
""""""""""""""""""""""""""""""""""""""

The ``period_archives`` variable can be used to generate a list of links to
the set of period archives that Pelican generates. As a :ref:`common variable
<common_variables>`, it is available for use in any template, so you
can implement such an index in a custom direct template, or in a sidebar
visible across different site pages.

``period_archives`` is a dict that may contain ``year``, ``month``, and/or
``day`` keys, depending on which ``*_ARCHIVE_SAVE_AS`` settings are enabled.
The corresponding value is a list of dicts, where each dict in turn represents
a time period (ordered according to the ``NEWEST_FIRST_ARCHIVES`` setting)
with the following keys and values:

=================== ===================================================
Key Value
=================== ===================================================
period The same tuple as described in
``period_archives.html``, e.g.
``(2023, 'June', 18)``.
period_num The same tuple as described in
``period_archives.html``, e.g. ``(2023, 6, 18)``.
url The URL to the period archive page, e.g.
``posts/2023/06/18/``. This is controlled by the
corresponding ``*_ARCHIVE_URL`` setting.
save_as The path to the save location of the period archive
page file, e.g. ``posts/2023/06/18/index.html``.
This is used internally by Pelican and is usually
not relevant to themes.
articles A list of :ref:`Article <object-article>` objects
that fall under the time period.
dates Same list as ``articles``, but ordered according
to the ``NEWEST_FIRST_ARCHIVES`` setting.
=================== ===================================================

Here is an example of how ``period_archives`` can be used in a template:

.. code-block:: html+jinja

<ul>
{% for archive in period_archives.month %}
<li>
<a href="{{ SITEURL }}/{{ archive.url }}">
{{ archive.period | reverse | join(' ') }} ({{ archive.articles|count }})
</a>
</li>
{% endfor %}
</ul>

You can change ``period_archives.month`` in the ``for`` statement to
``period_archives.year`` or ``period_archives.day`` as appropriate, depending
on the time period granularity desired.


Objects
=======

Expand Down
137 changes: 82 additions & 55 deletions pelican/generators.py
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,7 @@ def __init__(self, *args, **kwargs):
self.drafts = [] # only drafts in default language
self.drafts_translations = []
self.dates = {}
self.period_archives = defaultdict(list)
self.tags = defaultdict(list)
self.categories = defaultdict(list)
self.related_posts = []
Expand Down Expand Up @@ -483,64 +484,17 @@ def generate_period_archives(self, write):
except PelicanTemplateNotFound:
template = self.get_template('archives')

period_save_as = {
'year': self.settings['YEAR_ARCHIVE_SAVE_AS'],
'month': self.settings['MONTH_ARCHIVE_SAVE_AS'],
'day': self.settings['DAY_ARCHIVE_SAVE_AS'],
}

period_url = {
'year': self.settings['YEAR_ARCHIVE_URL'],
'month': self.settings['MONTH_ARCHIVE_URL'],
'day': self.settings['DAY_ARCHIVE_URL'],
}

period_date_key = {
'year': attrgetter('date.year'),
'month': attrgetter('date.year', 'date.month'),
'day': attrgetter('date.year', 'date.month', 'date.day')
}
for granularity in self.period_archives:
for period in self.period_archives[granularity]:

def _generate_period_archives(dates, key, save_as_fmt, url_fmt):
"""Generate period archives from `dates`, grouped by
`key` and written to `save_as`.
"""
# `dates` is already sorted by date
for _period, group in groupby(dates, key=key):
archive = list(group)
articles = [a for a in self.articles if a in archive]
# arbitrarily grab the first date so that the usual
# format string syntax can be used for specifying the
# period archive dates
date = archive[0].date
save_as = save_as_fmt.format(date=date)
url = url_fmt.format(date=date)
context = self.context.copy()
context['period'] = period['period']
context['period_num'] = period['period_num']

if key == period_date_key['year']:
context["period"] = (_period,)
context["period_num"] = (_period,)
else:
month_name = calendar.month_name[_period[1]]
if key == period_date_key['month']:
context["period"] = (_period[0],
month_name)
else:
context["period"] = (_period[0],
month_name,
_period[2])
context["period_num"] = tuple(_period)

write(save_as, template, context, articles=articles,
dates=archive, template_name='period_archives',
blog=True, url=url, all_articles=self.articles)

for period in 'year', 'month', 'day':
save_as = period_save_as[period]
url = period_url[period]
if save_as:
key = period_date_key[period]
_generate_period_archives(self.dates, key, save_as, url)
write(period['save_as'], template, context,
articles=period['articles'], dates=period['dates'],
template_name='period_archives', blog=True,
url=period['url'], all_articles=self.articles)

def generate_direct_templates(self, write):
"""Generate direct templates pages"""
Expand Down Expand Up @@ -680,6 +634,9 @@ def _process(arts):
self.dates.sort(key=attrgetter('date'),
reverse=self.context['NEWEST_FIRST_ARCHIVES'])

self.period_archives = self._build_period_archives(
self.dates, self.articles, self.settings)

# and generate the output :)

# order the categories per name
Expand All @@ -694,10 +651,80 @@ def _process(arts):
'articles', 'drafts', 'hidden_articles',
'dates', 'tags', 'categories',
'authors', 'related_posts'))
# _update_context flattens dicts, which should not happen to
# period_archives, so we update the context directly for it:
self.context['period_archives'] = self.period_archives
self.save_cache()
self.readers.save_cache()
signals.article_generator_finalized.send(self)

def _build_period_archives(self, sorted_articles, articles, settings):
"""
Compute the groupings of articles, with related attributes, for
per-year, per-month, and per-day archives.
"""

period_archives = defaultdict(list)

period_archives_settings = {
'year': {
'save_as': settings['YEAR_ARCHIVE_SAVE_AS'],
'url': settings['YEAR_ARCHIVE_URL'],
},
'month': {
'save_as': settings['MONTH_ARCHIVE_SAVE_AS'],
'url': settings['MONTH_ARCHIVE_URL'],
},
'day': {
'save_as': settings['DAY_ARCHIVE_SAVE_AS'],
'url': settings['DAY_ARCHIVE_URL'],
},
}

granularity_key_func = {
'year': attrgetter('date.year'),
'month': attrgetter('date.year', 'date.month'),
'day': attrgetter('date.year', 'date.month', 'date.day'),
}

for granularity in 'year', 'month', 'day':
save_as_fmt = period_archives_settings[granularity]['save_as']
url_fmt = period_archives_settings[granularity]['url']
key_func = granularity_key_func[granularity]

if not save_as_fmt:
# the archives for this period granularity are not needed
continue

for period, group in groupby(sorted_articles, key=key_func):
archive = {}

dates = list(group)
archive['dates'] = dates
archive['articles'] = [a for a in articles if a in dates]

# use the first date to specify the period archive URL
# and save_as; the specific date used does not matter as
# they all belong to the same period
d = dates[0].date
archive['save_as'] = save_as_fmt.format(date=d)
archive['url'] = url_fmt.format(date=d)

if granularity == 'year':
archive['period'] = (period,)
archive['period_num'] = (period,)
else:
month_name = calendar.month_name[period[1]]
if granularity == 'month':
archive['period'] = (period[0], month_name)
else:
archive['period'] = (period[0], month_name, period[2])
archive['period_num'] = tuple(period)

period_archives[granularity].append(archive)

return period_archives

def generate_output(self, writer):
self.generate_feeds(writer)
self.generate_pages(writer)
Expand Down
Loading