Even though Stuart Langridge is no longer using and developing Vellum, I'm aiming to stick with it and add any features I need in it until I come up with my very own NIH redundant and unnecessary web publishing platform. Anyway, one of the Tactical Tech people asked if she could get a grouping of just my Africa Source web log entries. So, I hacked together this to generate per-category pages...

The page-building process uses "funky caching" to generate pages via the CGI if they don't exist. When they do exist, the web server just grabs it off the filesystem, bypassing the CGI. Thus, to generate a new page, you delete the file on the filesystem. This is how Vellum "rebuilds" a page.

It has a dictionary in each Entry object in the attribute links with the name of the page as a key, and a template as the value. The code in question:

    theseEntries = []
    for blog in Blog.all():
        for entry in blog.entries():
            #print "Looking in the following keys: %s
" % str(entry.links.keys())
if url in entry.links.keys(): tmpl = entry.links[url] theseEntries.append(entry) if not theseEntries: print "Looking for url %s: not found, so sorry." % url return ('','Status: 404')

I created a simple startup script to mess around with the objects in the python interpreter:

#!/usr/bin/env python

import os
import sys
sys.path.append('/usr/mirror/web/mithrandr.moria.org/vellum')
os.chdir('/usr/mirror/web/mithrandr.moria.org/vellum')
import vellum.plugins
import vellum.functions
import vellum.auth

vellum.functions.pluginInfo = vellum.plugins.pluginInfo

import vellum.Entry

Entry = vellum.Entry

With this, I could look around the Entry object a little easier:

(nbm@dirk) /mirror/db/vellum# env PYTHONSTARTUP=startup.py python
Python 2.2.2 (#1, Jul 22 2003, 14:50:26)
[GCC 3.2.2 [FreeBSD] 20030205 (release)] on freebsd5
Type "help", "copyright", "credits" or "license" for more information.
>>> e = Entry.get(1)
>>> print e.links.keys()
['/blog/index.html', '/blog/include.html', '/blog/2003-04.html', '/blog/1.html', '/blog/rss2.0.xml']

So, Vellum determines whether the file to be built is valid, which template to use, and which entries to pass to the template by walking through all the entries and looking in their links attribute. (It also uses it to determine which files to delete when an entry is changed so they can be rebuilt the next time they're requested.)

Next, determine where the links attribute is set; Entry's setMyPermalink method. It initialises links as a new dictionary, and walks through each of the templates set up with the web log, and associates the filename generated from the template expression with an instance of the template.

setMyPermalink is after the Vellum hook 'entry-save-pre', so that could not be used to add the links. And no other hooks existed before the Entry is saved to the database. So, I added a hook call after the setMyPermalink. Here's the simple patch to Entry.py:

--- Entry.py.old        Wed Apr  7 16:07:17 2004
+++ Entry.py    Wed Apr  7 12:30:28 2004
@@ -70,6 +70,7 @@
         if b.staticdir == '': raise "The entry was saved but the rebuild failed because this blog has no static directory set"

         self.setMyPermalink(b)
+        hooks.call_hook("entry-permalink-post", self)
         first = self.firstpost
         self.firstpost = 0
         vellumObject.save(self)

Vellum's hook system is a nice feature, and is used by its plugin system to allow plugins to affect changes to the standard workflow. In this case, the category functionality is implemented as a plugin, and is available on the Vellum web site. I just needed a simple patch to add the hook to add the additional entries in the links attribute:

--- category.py.old     Wed Apr  7 16:12:09 2004
+++ category.py Wed Apr  7 13:26:42 2004
@@ -9,6 +9,29 @@
 # Add a new blank category attribute to every Entry
 vellum.Entry.Entry.category = ''

+import copy
+
+def addLinks(entry):
+    category_template = None
+    for tmpl in entry.blog().templates:
+        if tmpl.templateName == "Category":
+            category_template = tmpl
+            break
+
+    if entry.links.has_key("/blog/category_.html"):
+        del entry.links["/blog/category_.html"]
+    if not category_template:
+        return
+    if not hasattr(entry, "category"):
+        return
+    for category in entry.category.split(','):
+        category = category.strip()
+        ct = copy.copy(category_template)
+        ct.outputPage = '"category_%s.html"' % category
+        entry.links["/blog/category_%s.html" % category] = ct
+
+vellum.hooks.register_hook("entry-permalink-post",addLinks)
+
 # Print an entry's category in the entry list
 def printListCategory(entry):
     print ''

First, I find the Category template that I'd set up in my Vellum configuration (or return gracefully). I remove the fake "category_.html" entry that template is forced to create, and then set up an entry in the Entry's links dictionary for each category the entry belongs to. Due to the way Vellum's build function works, I need to change the outputPage attribute in the template, so I have to make a copy of the category template for this.

Ok, the template configuration for adding the per-category pages is:

  • Template name: Category
  • Template filename: templates/category.tmpl
  • Output page: "category_.html"

Leave the rest blank.

I saved an existing entry again to trigger the creation of the new links, and checked it out in the interpreter:

(nbm@dirk) /mirror/db/vellum# env PYTHONSTARTUP=startup.py python
Python 2.2.2 (#1, Jul 22 2003, 14:50:26)
[GCC 3.2.2 [FreeBSD] 20030205 (release)] on freebsd5
Type "help", "copyright", "credits" or "license" for more information.
>>> e = Entry.get(1)
>>> print e.links.keys()
['/blog/category_meta.html', '/blog/index.html', '/blog/include.html', '/blog/2003-04.html', '/blog/1.html', '/blog/rss2.0.xml']

Went to category_meta.html, and I had a single entry.

So, I needed to resave all my files again. I used this:

#!/usr/bin/env python
import os
import sys
sys.path.append('/usr/mirror/web/mithrandr.moria.org/vellum')
os.chdir('/usr/mirror/web/mithrandr.moria.org/vellum')
import vellum.plugins
import vellum.functions
import vellum.auth

vellum.functions.pluginInfo = vellum.plugins.pluginInfo

import vellum.Entry

Entry = vellum.Entry

entries = Entry.all()

for entry in entries:
    entry.save(skipRebuild = True)

So, after all this, I now have categories, and Stephanie got a page of my Africa Source entries.