Configuring Douglas

You configure a Douglas blog by setting configuration variables in a Python file called Each Douglas blog has its own file.

This chapter documents the variables. Some of these are required, others are optional.


Douglas comes with a sample config file. This file does not have everything listed below in it. If you want to use a variable that’s not listed in your config file—just add it.

Config variables and syntax

Each configuration variable is set with a line like:

py["blog_title"] = "Another douglas blog"


  • blog_title is the name of the configuration variable
  • "Another douglas blog" is the value

Most configuration values are strings and must be enclosed in quotes, but some are lists, numbers or other types of values.


# this has a string value
py["foo"] = "this is a string"

# this is a long string value
py["foo"] = (
    "This is a really long string value that breaks over "
    "multiple lines.  The parentheses cause Python to "
    "allow this string to span several lines."

# this has an integer value
py["foo"] = 4

# this is a boolean--True has a capital T
py["foo"] = True

# this is a boolean--False has a capital F
py["foo"] = False

# this is a list of strings
py["foo"] = [

# this is the same list of strings formatted slightly differently
py["foo"] = ["list", "of", "strings"]

Since is a Python code file, it’s written in Python and uses Python code conventions.

Plugin variables

If you install any Douglas plugins those plugins may ask you to set additional variables in your file. Those variables will be documented in the documentation that comes with the plugin or at the top of the plugin’s source code file. Additional plugin variables will not be documented here.

Personal configuration variables

You can add your own personal configuration variables to You can put any py["name"] = value statements that you want in You can then refer to your configuration variables further down in your file and in your theme templates. This is useful for allowing you to centralize any configuration for your blog into your file.

For example, you could move all your media files (JPEG images, GIF images, CSS, ...) into a directory on your server to be served by Apache and then set the variable py["media_url"] to the directory with media files and use $media_url to refer to this URL in your theme templates.

Configuration variables

class douglas.settings.Config
base_url = Required

Set base_url in your file to the base url for your blog. If someone were to type this url into their browser, they’d see the front page of your blog.


Your base_url property should not have a trailing slash.

blog_author = (Optional) Default is ''

This is the name of the author of your blog. Very often this is your name or a pseudonym.

If Joe Smith had a blog, he might set his blog_author to “Joe Smith”:

py["blog_author"] = "Joe Smith"

If Joe Smith had a blog, but went by the pseudonym “Magic Rocks”, he might set his blog_author to “Magic Rocks”:

py["blog_author"] = "Magic Rocks"
blog_description = (Optional) Default is ''

This is the description or byline of your blog. Typically this is a phrase or a sentence that summarizes what your blog covers.

If you were writing a blog about restaurants in the Boston area, you might have a blog_description of:

py["blog_description"] = "Critiques of restaurants in the Boston area"

Or if your blog covered development on Douglas, your blog_description might go like this:

py["blog_description"] = (
    "Ruminations on the development of Douglas and "
    "related things that I discovered while working on "
    "the project")
blog_email = (Optional) Default is ''

This is the email address you want associated with your blog.

For example, say Joe Smith had an email address and wanted that associated with his blog. Then he would set the email address as such:

py["blog_email"] = ""
blog_encoding = (Optional) Default is 'utf-8'

This is the character encoding of your blog.

For example, if your blog was encoded in utf-8, then you would set the blog_encoding to:

py["blog_encoding"] = "utf-8"


This value must be a valid character encoding value. In general, if you don’t know what to set your encoding to then set it to utf-8.

This value should be in the meta section of any HTML- or XHTML-based themes and it’s also in the header for any feed-based themes. An improper encoding will gummy up some/most feed readers and web-browsers.

W3C has a nice tutorial on encoding. You may refer to IANA charset registry for a complete list of encoding names.

blog_language = (Optional) Default is ''

This is the primary language code for your blog.

For example, English users should use en:

py["blog_language"] = "en"

This gets used in the RSS themes.

Refer to ISO 639-2 for language codes. Many systems use two-letter ISO 639-1 codes supplemented by three-letter ISO 639-2 codes when no two-letter code is applicable. Often ISO 639-2 is sufficient. If you use very special languages, you may want to refer to ISO 639-3, which is a super set of ISO 639-2 and contains languages used thousands of years ago.

blog_rights = (Optional) Default is ''

These are the rights you give to others in regards to the content on your blog. Generally this is the copyright information, for example:

py["blog_rights"] = "Copyright 2005 Joe Bobb"

This is used in the Atom and RSS 2.0 feeds. Leaving this blank or not filling it in correctly could result in a feed that doesn’t validate.

blog_title = (Optional) Default is 'My blog'

This is the title of your blog. Typically this should be short and is accompanied by a longer summary of your blog which is set in blog_description.

For example, if Joe were writing a blog about cooking, he might title his blog:

py["blog_title"] = "Joe's blog about cooking"
compile_index_themes = (Optional) Default is ['html']

compile_index_themes is just like compile_themes except it’s the themes of the index files: frontpage index, category indexes, date indexes, ...

Defaults to ["html"] which only renders the html theme.

For example:

py["compile_index_themes"] = ["html"]

If you want your index files to also be feeds, then you should add a feed theme to the list.

compile_themes = (Optional) Default is ['html']

The value of compile_themes should be a list of strings representing all the themes that should be rendered.

For example:

py["compile_themes"] = ["html"]
compile_urls = (Optional) Default is []

Any other url paths to compile. Sometimes plugins require you to add additional paths—this is where you’d do it.

For example:

py["compile_urls"] = [
compiledir = (Optional) Default is ''

This is the directory we will save all the output. The value of compiledir should be a string representing the absolute path of the output directory for compiling.

For example, Joe puts the output in his public_html directory of his account:

py["compiledir"] = "/home/joe/public_html"
datadir = Required

This is the full path to where your blog entries are kept on the file system.

For example, if you are storing your blog entries in /home/joe/blog/entries/, then you would set the datadir like this:

py["datadir"] = "/home/joe/blog/entries/"


Must not end with a /.

day_indexes = (Optional) Default is False

Whether or not to generate indexes per day.

For example:

py["day_indexes"] = True
default_theme = (Optional) Default is 'html'

This specified the theme that will be used if the user doesn’t specify a theme in the URI.

For example, if you wanted your default theme to be “joy”, then you would set default_theme like this:

py["default_theme"] = "joy"

Doing this will cause Douglas to use the “joy” theme whenever URIs are requested that don’t specify the theme.

For example, the following will all use the “joy” theme:
depth = (Optional) Default is 0

The depth setting determines how many levels deep in the directory (category) tree that Douglas will display when doing indexes.

  • 0 - infinite depth (aka grab everything) DEFAULT
  • 1 - datadir only
  • 2 - two levels
  • 3 - three levels
  • ...
  • n - n levels deep
entryparsers = (Optional) Default is {}

Lets you override which file extensions are parsed by which entry parsers. The keys are the file extension. The values are the Python module path to the callable that will parse the file.

For example, by default, the blosxom_entry_parser parses files ending with .txt. You can also have it parse files ending in .html:

py["entryparsers"] = {
    'html': ''

The part denotes which Python module the callable is in. The blosxom_entry_parser part is the name of a function in the module which will parse the entry.

ignore_directories = (Optional) Default is []

The ignore_directories variable allows you to specify which directories in your datadir should be ignored by Douglas.

This defaults to an empty list (i.e. Douglas will not ignore any directories).

For example, if you use CVS to manage the entries in your datadir, then you would want to ignore all CVS-related directories like this:

py["ignore_directories"] = ["CVS"]

If you were using CVS and you also wanted to store drafts of entries you need to think about some more in a drafts directory in your datadir, then you could set your ignore_directories like this:

py["ignore_directories"] = ["drafts", "CVS"]

This would ignore all directories named “CVS” and “drafts” in your datadir tree.

load_plugins = (Optional) Default is []

Specifying load_plugins causes Douglas to load only the plugins you name and in in the order you name them.

The value of load_plugins should be a list of strings where each string is the name of a Python module.

If you specify an empty list no plugins will be loaded.


Douglas loads plugins in the order specified by load_plugins. This order also affects the order that callbacks are registered and later executed. For example, if plugin_a and plugin_b both implement the handle callback and you load plugin_b first, then plugin_b will execute before plugin_a when the handle callback kicks off.

Usually this isn’t a big deal, however it’s possible that some plugins will want to have a chance to do things before other plugins. This should be specified in the documentation that comes with those plugins.

log_file = (Optional) Default is <open file '<stderr>', mode 'w'>

This specifies the file that Douglas will log messages to.

If Douglas cannot open the file for writing, then log messages will be sent to sys.stderr.

For example, if you wanted Douglas to log messages to /home/joe/blog/logs/douglas.log, then you would set log_file to:

py["log_file"] = "/home/joe/blog/logs/douglas.log"

If you were on Windows, then you might set it to:

py["log_file"] = "c:/blog/logs/douglas.log"


The web server that is executing Douglas must be able to write to the directory containing your douglas.log file.

log_level = (Optional) Default is 'error'

This is based on the Python logging module, so the levels are the same:

  • critical
  • error
  • warning
  • info
  • debug

This sets the log level for logging messages.

If you set the log_level to critical, then only critical messages are logged.

If you set the log_level to error, then error and critical messages are logged.

If you set the log_level to warning, then warning, error, and critical messages are logged.

So on and so forth.

For “production” blogs (i.e. you’re not tinkering with configuration, new plugins, new themes, or anything along those lines), then this should be set to warning or error.

For example, if you’re done tinkering with your blog, you might set the log_level to info allowing you to see how requests are being processed:

py['log_level'] = "info"
month_indexes = (Optional) Default is False

Whether or not to generate indexes per month.

For example:

py["month_indexes"] = True
num_entries = (Optional) Default is 10

The num_entries variable specifies the number of entries that show up on your home page and other category index pages. It doesn’t affect the number of entries that show up on date-based archive pages.

It defaults to 5 which means “show at most 5 entries”.

If you set it to 0, then it will show all entries that it can.

For example, if you wanted to set num_entries to 10 so that 10 entries show on your category index pages, you sould set it like this:

py["num_entries"] = 10
plugin_dirs = (Optional) Default is []

The plugin_dirs variable tells Douglas which directories to look for plugins in addition to the plugins that Douglas comes with. You can list as many directories as you want.

For example, if your blog used the “paginate” plugin that comes with Douglas and a “myfancyplugin” that you wrote yourself that’s in your blog’s plugins directory, then you might set plugin_dirs like this:

py["plugin_dirs"] = [


Plugin directories are not searched recursively for plugins. If you have a tree of plugin directories that have plugins in them, you’ll need to specify each directory in the tree.

For example, if you have plugins in ~/blog/my_plugins/ and ~/blog/phils_plugins/, then you need to specify both directories in plugin_dirs:

py["plugin_dirs"] = [

You can’t just specify ~/blog/ and expect Douglas to find the plugins in the directory tree:

# This won't work!
py["plugin_dirs"] = [


Plugins that come with Douglas are automatically found—you don’t have to specify anything in your``plugin_dirs`` in order to use core plugins.

renderer = (Optional) Default is 'jinjarenderer'

The renderer variable lets you specify which renderer to use.

static_files_dirs = (Optional) Default is []

Any additional directories you want copied over to the compiledir.

For example:

py['static_files_dirs'] = [
static_url = (Optional) Default is ''

The url where your static assets will be.

If you’re using a CDN, this is the CDN url.

If you’re not using a CDN, this is probably the base_url plus /static.

You can use this variable in your templates. For example:

<link rel="stylesheet" href="{{ static_url }}/css/style.css">
themedir = Required

This is the full path to where your Douglas themes are kept.

If you do not set the themedir, then Douglas will look for your themes and templates in the datadir alongside your entries.


“theme” is spelled using the British spelling and not the American one.

For example, if you want to put your entries in /home/joe/blog/entries/ and your theme templates in /home/joe/blog/themes/ you would set themedir and datadir like this:

py["datadir"] = "/home/joe/blog/entries/"
py["themedir"] = "/home/joe/blog/themes/"
truncate_category = (Optional) Default is True

Whether or not to truncate the number of entries displayed on a category-based index page to num_entries number of entries.

For example, this causes all entries in a category to show up in all category-based index pages:

py["truncate_category"] = False
truncate_date = (Optional) Default is False

Whether or not to truncate the number of entries displayed on a date-based index page to num_entries number of entries.

truncate_frontpage = (Optional) Default is True

Whether or not to truncate the number of entries displayed on teh front page to num_entries number of entries.

For example, this causes all entries to be displayed on your front page (which is probably a terrible idea):

py["truncate_frontpage"] = False
year_indexes = (Optional) Default is True

Whether or not to generate indexes per year.

For example:

py["year_indexes"] = True

Compiling Configuration

If you are using compiling to deploy your Douglas blog you need to set some additional configuration variables in your file, see Deploy Douglas as a Compiled HTML Site.