Merge pull request #262 from justinmayer/feeddomain

Allow for serving feeds from a separate domain.
This commit is contained in:
Alexis Metaireau 2012-03-22 08:58:09 -07:00
commit 38b5b94617
6 changed files with 106 additions and 34 deletions

View file

@ -62,10 +62,13 @@ Setting name (default value) What does it do?
`RELATIVE_URLS` (``True``) Defines whether Pelican should use relative URLs or `RELATIVE_URLS` (``True``) Defines whether Pelican should use relative URLs or
not. not.
`SITENAME` (``'A Pelican Blog'``) Your site name `SITENAME` (``'A Pelican Blog'``) Your site name
`SITEURL` Base URL of your website. Note that this is `SITEURL` Base URL of your website. Not defined by default,
not a way to tell Pelican whether to use relative URLs which means the base URL is assumed to be "/" with a
or static ones. You should instead use the root-relative URL structure. If `SITEURL` is specified
`RELATIVE_URL` setting for that purpose. explicitly, URLs will be generated with an absolute
URL structure (including the domain). If you want to
use relative URLs instead of root-relative or absolute
URLs, you should instead use the `RELATIVE_URL` setting.
`STATIC_PATHS` (``['images']``) The static paths you want to have accessible `STATIC_PATHS` (``['images']``) The static paths you want to have accessible
on the output path "static". By default, on the output path "static". By default,
Pelican will copy the 'images' folder to the Pelican will copy the 'images' folder to the
@ -86,10 +89,10 @@ Setting name (default value) What does it do?
URL settings URL settings
------------ ------------
You can customize the URL's and locations where files will be saved. The URL's and You can customize the URLs and locations where files will be saved. The URLs and
SAVE_AS variables use python's format strings. These variables allow you to place SAVE_AS variables use Python's format strings. These variables allow you to place
your articles in a location such as '{slug}/index.html' and link to then as your articles in a location such as '{slug}/index.html' and link to them as
'{slug}' for clean urls. These settings give you the flexibility to place your '{slug}' for clean URLs. These settings give you the flexibility to place your
articles and pages anywhere you want. articles and pages anywhere you want.
Note: If you specify a datetime directive, it will be substituted using the Note: If you specify a datetime directive, it will be substituted using the
@ -216,15 +219,26 @@ the ``TAG_FEED`` and ``TAG_FEED_RSS`` settings:
================================================ ===================================================== ================================================ =====================================================
Setting name (default value) What does it do? Setting name (default value) What does it do?
================================================ ===================================================== ================================================ =====================================================
`CATEGORY_FEED` ('feeds/%s.atom.xml'[2]_) Where to put the category Atom feeds. `FEED_DOMAIN` (``None``, i.e. base URL is "/") The domain prepended to feed URLs. Since feed URLs
`CATEGORY_FEED_RSS` (``None``, i.e. no RSS) Where to put the category RSS feeds. should always be absolute, it is highly recommended
to define this (e.g., "http://feeds.example.com"). If
you have already explicitly defined SITEURL (see
above) and want to use the same domain for your
feeds, you can just set: `FEED_DOMAIN = SITEURL`
`FEED` (``'feeds/all.atom.xml'``) Relative URL to output the Atom feed. `FEED` (``'feeds/all.atom.xml'``) Relative URL to output the Atom feed.
`FEED_RSS` (``None``, i.e. no RSS) Relative URL to output the RSS feed. `FEED_RSS` (``None``, i.e. no RSS) Relative URL to output the RSS feed.
`TAG_FEED` (``None``, ie no tag feed) Relative URL to output the tag Atom feed. It should `CATEGORY_FEED` ('feeds/%s.atom.xml'[2]_) Where to put the category Atom feeds.
`CATEGORY_FEED_RSS` (``None``, i.e. no RSS) Where to put the category RSS feeds.
`TAG_FEED` (``None``, i.e. no tag feed) Relative URL to output the tag Atom feed. It should
be defined using a "%s" match in the tag name. be defined using a "%s" match in the tag name.
`TAG_FEED_RSS` (``None``, ie no RSS tag feed) Relative URL to output the tag RSS feed `TAG_FEED_RSS` (``None``, ie no RSS tag feed) Relative URL to output the tag RSS feed
`FEED_MAX_ITEMS` Maximum number of items allowed in a feed. Feed item `FEED_MAX_ITEMS` Maximum number of items allowed in a feed. Feed item
quantity is unrestricted by default. quantity is unrestricted by default.
`FEED_MAIN_URL` (``'feeds/all.atom.xml'``) URL appended to domain for the main Atom feed and
used to populate its `<link>` in the base template.
Useful when you want the feed link to differ from the
filesystem path, such as when using web server
aliases/rewrites or FeedBurner (see below).
================================================ ===================================================== ================================================ =====================================================
If you don't want to generate some of these feeds, set ``None`` to the If you don't want to generate some of these feeds, set ``None`` to the
@ -232,6 +246,25 @@ variables above.
.. [2] %s is the name of the category. .. [2] %s is the name of the category.
FeedBurner
----------
If you want to use FeedBurner for your primary Atom feed, there are two
primary fields to configure in the `FeedBurner
<http://feedburner.google.com>`_ interface: "Original Feed" and "Feed Address".
If using the default Pelican `FEED` attribute and assuming your feeds
are served from the `www.example.com` domain, you would enter
`http://www.example.com/feeds/all.atom.xml` in the "Original Feed" field in
FeedBurner.
For the "Feed Address" field in the FeedBurner interface, you may choose
whatever suffix you prefer. In your Pelican settings, assign this suffix to
the `FEED_MAIN_URL` setting. So if your FeedBurner feed address is set to
`http://feeds.feedburner.com/myblogfeed`, in your Pelican settings you would
set: `FEED_MAIN_URL = "myblogfeed"`. Then set the `FEED_DOMAIN` setting to
`http://feeds.feedburner.com`, or `http://feeds.example.com` if you are using
a CNAME on your own domain (i.e., FeedBurner's "MyBrand" feature).
Pagination Pagination
========== ==========

View file

@ -22,6 +22,7 @@ _DEFAULT_CONFIG = {'PATH': None,
'STATIC_PATHS': ['images', ], 'STATIC_PATHS': ['images', ],
'THEME_STATIC_PATHS': ['static', ], 'THEME_STATIC_PATHS': ['static', ],
'FEED': 'feeds/all.atom.xml', 'FEED': 'feeds/all.atom.xml',
'FEED_MAIN_URL': 'feeds/all.atom.xml',
'CATEGORY_FEED': 'feeds/%s.atom.xml', 'CATEGORY_FEED': 'feeds/%s.atom.xml',
'TRANSLATION_FEED': 'feeds/all-%s.atom.xml', 'TRANSLATION_FEED': 'feeds/all-%s.atom.xml',
'FEED_MAX_ITEMS': '', 'FEED_MAX_ITEMS': '',
@ -71,26 +72,45 @@ _DEFAULT_CONFIG = {'PATH': None,
def read_settings(filename=None): def read_settings(filename=None):
if filename:
local_settings = get_settings_from_file(filename)
else:
local_settings = _DEFAULT_CONFIG
configured_settings = configure_settings(local_settings, None, filename)
return configured_settings
def get_settings_from_file(filename, default_settings=None):
"""Load a Python file into a dictionary. """Load a Python file into a dictionary.
""" """
context = _DEFAULT_CONFIG.copy() if default_settings == None:
default_settings = _DEFAULT_CONFIG
context = default_settings.copy()
if filename: if filename:
tempdict = {} tempdict = {}
execfile(filename, tempdict) execfile(filename, tempdict)
for key in tempdict: for key in tempdict:
if key.isupper(): if key.isupper():
context[key] = tempdict[key] context[key] = tempdict[key]
return context
# Make the paths relative to the settings file
def configure_settings(settings, default_settings=None, filename=None):
"""Provide optimizations, error checking, and warnings for loaded settings"""
if default_settings is None:
default_settings = _DEFAULT_CONFIG
# Make the paths relative to the settings file
if filename:
for path in ['PATH', 'OUTPUT_PATH']: for path in ['PATH', 'OUTPUT_PATH']:
if path in context: if path in settings:
if context[path] is not None and not isabs(context[path]): if settings[path] is not None and not isabs(settings[path]):
context[path] = os.path.abspath(os.path.normpath( settings[path] = os.path.abspath(os.path.normpath(
os.path.join(os.path.dirname(filename), context[path])) os.path.join(os.path.dirname(filename), settings[path]))
) )
# if locales is not a list, make it one # if locales is not a list, make it one
locales = context['LOCALE'] locales = settings['LOCALE']
if isinstance(locales, basestring): if isinstance(locales, basestring):
locales = [locales] locales = [locales]
@ -108,11 +128,20 @@ def read_settings(filename=None):
else: else:
logger.warn("LOCALE option doesn't contain a correct value") logger.warn("LOCALE option doesn't contain a correct value")
if not 'TIMEZONE' in context: # If SITEURL is defined but FEED_DOMAIN isn't, set FEED_DOMAIN = SITEURL
if ('SITEURL' in settings) and (not 'FEED_DOMAIN' in settings):
settings['FEED_DOMAIN'] = settings['SITEURL']
# Warn if feeds are generated with both SITEURL & FEED_DOMAIN undefined
if (('FEED' in settings) or ('FEED_RSS' in settings)) and (not 'FEED_DOMAIN' in settings):
logger.warn("Since feed URLs should always be absolute, you should specify "
"FEED_DOMAIN in your settings. (e.g., 'FEED_DOMAIN = "
"http://www.example.com')")
if not 'TIMEZONE' in settings:
logger.warn("No timezone information specified in the settings. Assuming" logger.warn("No timezone information specified in the settings. Assuming"
" your timezone is UTC for feed generation. Check " " your timezone is UTC for feed generation. Check "
"http://docs.notmyidea.org/alexis/pelican/settings.html#timezone " "http://docs.notmyidea.org/alexis/pelican/settings.html#timezone "
"for more information") "for more information")
# set the locale return settings
return context

View file

@ -26,8 +26,6 @@ body {
text-align: left; text-align: left;
} }
/* Headings */ /* Headings */
h1 {font-size: 2em } h1 {font-size: 2em }
h2 {font-size: 1.571em} /* 22px */ h2 {font-size: 1.571em} /* 22px */
@ -304,7 +302,7 @@ img.left, figure.left {float: right; margin: 0 0 2em 2em;}
.social a[href*='digg.com'] {background-image: url('../images/icons/digg.png');} .social a[href*='digg.com'] {background-image: url('../images/icons/digg.png');}
.social a[href*='facebook.com'] {background-image: url('../images/icons/facebook.png');} .social a[href*='facebook.com'] {background-image: url('../images/icons/facebook.png');}
.social a[href*='last.fm'], .social a[href*='lastfm.'] {background-image: url('../images/icons/lastfm.png');} .social a[href*='last.fm'], .social a[href*='lastfm.'] {background-image: url('../images/icons/lastfm.png');}
.social a[href*='atom.xml'] {background-image: url('../images/icons/rss.png');} .social a[type$='atom+xml'], .social a[type$='rss+xml'] {background-image: url('../images/icons/rss.png');}
.social a[href*='twitter.com'] {background-image: url('../images/icons/twitter.png');} .social a[href*='twitter.com'] {background-image: url('../images/icons/twitter.png');}
.social a[href*='linkedin.com'] {background-image: url('../images/icons/linkedin.png');} .social a[href*='linkedin.com'] {background-image: url('../images/icons/linkedin.png');}

View file

@ -4,9 +4,9 @@
<title>{% block title %}{{ SITENAME }}{%endblock%}</title> <title>{% block title %}{{ SITENAME }}{%endblock%}</title>
<meta charset="utf-8" /> <meta charset="utf-8" />
<link rel="stylesheet" href="{{ SITEURL }}/theme/css/{{ CSS_FILE }}" type="text/css" /> <link rel="stylesheet" href="{{ SITEURL }}/theme/css/{{ CSS_FILE }}" type="text/css" />
<link href="{{ SITEURL }}/{{ FEED }}" type="application/atom+xml" rel="alternate" title="{{ SITENAME }} ATOM Feed" /> <link href="{{ FEED_DOMAIN }}/{{ FEED_MAIN_URL }}" type="application/atom+xml" rel="alternate" title="{{ SITENAME }} Atom Feed" />
{% if FEED_RSS %} {% if FEED_RSS %}
<link href="{{ SITEURL }}/{{ FEED_RSS }}" type="application/atom+xml" rel="alternate" title="{{ SITENAME }} RSS Feed" /> <link href="{{ FEED_DOMAIN }}/{{ FEED_RSS }}" type="application/rss+xml" rel="alternate" title="{{ SITENAME }} RSS Feed" />
{% endif %} {% endif %}
<!--[if IE]> <!--[if IE]>
@ -56,9 +56,9 @@
<div class="social"> <div class="social">
<h2>social</h2> <h2>social</h2>
<ul> <ul>
<li><a href="{{ SITEURL }}/{{ FEED }}" rel="alternate">atom feed</a></li> <li><a href="{{ FEED_DOMAIN }}/{{ FEED_MAIN_URL }}" type="application/atom+xml" rel="alternate">atom feed</a></li>
{% if FEED_RSS %} {% if FEED_RSS %}
<li><a href="{{ SITEURL }}/{{ FEED_RSS }}" rel="alternate">rss feed</a></li> <li><a href="{{ FEED_DOMAIN }}/{{ FEED_RSS }}" type="application/rss+xml" rel="alternate">rss feed</a></li>
{% endif %} {% endif %}
{% for name, link in SOCIAL %} {% for name, link in SOCIAL %}

View file

@ -29,7 +29,7 @@ SOCIAL = (('twitter', 'http://twitter.com/ametaireau'),
DEFAULT_METADATA = (('yeah', 'it is'),) DEFAULT_METADATA = (('yeah', 'it is'),)
# static paths will be copied under the same name # static paths will be copied under the same name
STATIC_PATHS = ["pictures",] STATIC_PATHS = ["pictures", ]
# A list of files to copy from the source to the destination # A list of files to copy from the source to the destination
FILES_TO_COPY = (('extra/robots.txt', 'robots.txt'),) FILES_TO_COPY = (('extra/robots.txt', 'robots.txt'),)
@ -37,4 +37,3 @@ FILES_TO_COPY = (('extra/robots.txt', 'robots.txt'),)
# foobar will not be used, because it's not in caps. All configuration keys # foobar will not be used, because it's not in caps. All configuration keys
# have to be in caps # have to be in caps
foobar = "barbaz" foobar = "barbaz"

View file

@ -1,12 +1,13 @@
from os.path import dirname, abspath, join from os.path import dirname, abspath, join
from pelican.settings import read_settings, _DEFAULT_CONFIG from pelican.settings import read_settings, configure_settings, _DEFAULT_CONFIG
from .support import unittest from .support import unittest
class TestSettingsFromFile(unittest.TestCase): class TestSettingsConfiguration(unittest.TestCase):
"""Providing a file, it should read it, replace the default values and """Provided a file, it should read it, replace the default values,
append new values to the settings, if any append new values to the settings (if any), and apply basic settings
optimizations.
""" """
def setUp(self): def setUp(self):
self.PATH = abspath(dirname(__file__)) self.PATH = abspath(dirname(__file__))
@ -31,3 +32,15 @@ class TestSettingsFromFile(unittest.TestCase):
"""providing no file should return the default values.""" """providing no file should return the default values."""
settings = read_settings(None) settings = read_settings(None)
self.assertDictEqual(settings, _DEFAULT_CONFIG) self.assertDictEqual(settings, _DEFAULT_CONFIG)
def test_configure_settings(self):
"""Manipulations to settings should be applied correctly."""
# FEED_DOMAIN, if undefined, should default to SITEURL
settings = {'SITEURL': 'http://blog.notmyidea.org', 'LOCALE': ''}
configure_settings(settings)
self.assertEqual(settings['FEED_DOMAIN'], 'http://blog.notmyidea.org')
settings = {'FEED_DOMAIN': 'http://feeds.example.com', 'LOCALE': ''}
configure_settings(settings)
self.assertEqual(settings['FEED_DOMAIN'], 'http://feeds.example.com')