Skip to content
Permalink
Browse files
Merge pull request #2394 from getnikola/post-list-date
Fix #1889 -- support dates in post_list
  • Loading branch information
Kwpolska committed Jul 18, 2016
2 parents 14ad199 + dd02d68 commit f47705f427a3c11ac62109146a2b8f007cfd0801
@@ -4,9 +4,11 @@ New in master
Features
--------

* Support ``date`` filtering in the post list directive
(Issue #1889)
* Support ``doc`` shortcode (equivalent to reST ``doc`` role — part of
Issue #2170)
* Albanian translation by Vango Stavro
* Added Albanian translation by Vango Stavro
* Added ``post-(type)`` class to ``story.tmpl`` (uses the ``type``
meta field, defaults to ``post-text`` — same behavior as posts)
* New ``compiled`` signal after post is compiled (Issue #2369)
@@ -1972,6 +1972,15 @@ The following options are recognized:
Sort post list by one of each post's attributes, usually ``title`` or a
custom ``priority``. Defaults to None (chronological sorting).

* ``date``: string
Show posts that match date range specified by this option. Format:

* comma-separated clauses (AND)
* clause: attribute comparison_operator value (spaces optional)
* attribute: year, month, day, hour, month, second, weekday, isoweekday; or empty for full datetime
* comparison_operator: == != <= >= < >
* value: integer or dateutil-compatible date input

* ``tags`` : string [, string...]
Filter posts to show only posts having at least one of the ``tags``.
Defaults to None.
@@ -3,3 +3,5 @@ We ship some third-party things with Nikola. They live here, along with their l
Packages:

* tzlocal by Lennart Regebro, CC0 license (modified)
* datecond by Chris Warrick (Nikola contributor), 3-clause BSD license
(modified)
@@ -0,0 +1,30 @@
Copyright © 2016, Chris Warrick.
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:

1. Redistributions of source code must retain the above copyright
notice, this list of conditions, and the following disclaimer.

2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions, and the following disclaimer in the
documentation and/or other materials provided with the distribution.

3. Neither the name of the author of this software nor the names of
contributors to this software may be used to endorse or promote
products derived from this software without specific prior written
consent.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,85 @@
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
# Date Conditionals (datecond)
# Version 0.1.2
# Copyright © 2015-2016, Chris Warrick.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# 1. Redistributions of source code must retain the above copyright
# notice, this list of conditions, and the following disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions, and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
# 3. Neither the name of the author of this software nor the names of
# contributors to this software may be used to endorse or promote
# products derived from this software without specific prior written
# consent.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

"""Date range parser."""

from __future__ import print_function, unicode_literals
import dateutil.parser
import re
import operator


__all__ = ('date_in_range',)
CLAUSE = re.compile('(year|month|day|hour|minute|second|weekday|isoweekday)?'
' ?(==|!=|<=|>=|<|>) ?(.*)')
OPERATORS = {
'==': operator.eq,
'!=': operator.ne,
'<=': operator.le,
'>=': operator.ge,
'<': operator.lt,
'>': operator.gt,
}


def date_in_range(date_range, date, debug=True):
"""Check if date is in the range specified.
Format:
* comma-separated clauses (AND)
* clause: attribute comparison_operator value (spaces optional)
* attribute: year, month, day, hour, month, second, weekday, isoweekday
or empty for full datetime
* comparison_operator: == != <= >= < >
* value: integer or dateutil-compatible date input
"""
out = True

for item in date_range.split(','):
attribute, comparison_operator, value = CLAUSE.match(
item.strip()).groups()
if attribute in ('weekday', 'isoweekday'):
left = getattr(date, attribute)()
right = int(value)
elif attribute:
left = getattr(date, attribute)
right = int(value)
else:
left = date
right = dateutil.parser.parse(value)
if debug: # pragma: no cover
print(" <{0} {1} {2}>".format(left, comparison_operator, right))
out = out and OPERATORS[comparison_operator](left, right)
return out
@@ -37,6 +37,7 @@

from nikola import utils
from nikola.plugin_categories import RestExtension
from nikola.packages.datecond import date_in_range

# WARNING: the directive name is post-list
# (with a DASH instead of an UNDERSCORE)
@@ -86,10 +87,19 @@ class PostList(Directive):
Reverse the order of the post-list.
Defaults is to not reverse the order of posts.
``sort``: string
``sort`` : string
Sort post list by one of each post's attributes, usually ``title`` or a
custom ``priority``. Defaults to None (chronological sorting).
``date`` : string
Show posts that match date range specified by this option. Format:
* comma-separated clauses (AND)
* clause: attribute comparison_operator value (spaces optional)
* attribute: year, month, day, hour, month, second, weekday, isoweekday; or empty for full datetime
* comparison_operator: == != <= >= < >
* value: integer or dateutil-compatible date input
``tags`` : string [, string...]
Filter posts to show only posts having at least one of the ``tags``.
Defaults to None.
@@ -136,6 +146,7 @@ class PostList(Directive):
'lang': directives.unchanged,
'template': directives.path,
'id': directives.unchanged,
'date': directives.unchanged,
}

def run(self):
@@ -154,17 +165,21 @@ def run(self):
lang = self.options.get('lang', utils.LocaleBorg().current_lang)
template = self.options.get('template', 'post_list_directive.tmpl')
sort = self.options.get('sort')
date = self.options.get('date')

output = _do_post_list(start, stop, reverse, tags, categories, slugs, post_type,
show_all, lang, template, sort, state=self.state, site=self.site)
show_all, lang, template, sort, state=self.state, site=self.site, date=date)
self.state.document.settings.record_dependencies.add("####MAGIC####TIMELINE")
return [nodes.raw('', output, format='html')]
if output:
return [nodes.raw('', output, format='html')]
else:
return []


def _do_post_list(start=None, stop=None, reverse=False, tags=None, categories=None,
slugs=None, post_type='post', show_all=False, lang=None,
template='post_list_directive.tmpl', sort=None, id=None,
data=None, state=None, site=None):
data=None, state=None, site=None, date=None):
if lang is None:
lang = utils.LocaleBorg().current_lang
if site.invariant: # for testing purposes
@@ -213,6 +228,9 @@ def _do_post_list(start=None, stop=None, reverse=False, tags=None, categories=No
if sort:
filtered_timeline = natsort.natsorted(filtered_timeline, key=lambda post: post.meta[lang][sort], alg=natsort.ns.F | natsort.ns.IC)

if date:
filtered_timeline = [p for p in filtered_timeline if date_in_range(date, p.date)]

for post in filtered_timeline[start:stop:step]:
if slugs:
cont = True

0 comments on commit f47705f

Please sign in to comment.