Skip to content

Commit

Permalink
Merge pull request #3148 from djramones/period-archives-context
Browse files Browse the repository at this point in the history
  • Loading branch information
justinmayer authored Oct 28, 2023
2 parents 8a7e016 + b8d5919 commit 85bf982
Show file tree
Hide file tree
Showing 4 changed files with 289 additions and 67 deletions.
27 changes: 15 additions & 12 deletions docs/settings.rst
Original file line number Diff line number Diff line change
Expand Up @@ -564,44 +564,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 @@ -493,64 +494,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 @@ -690,6 +644,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 @@ -704,10 +661,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

0 comments on commit 85bf982

Please sign in to comment.