Main Page Content
A Quick And Dirty Blog Using Zope
If you're a keen collector of evolt contributor bios, you'll notice that I'm gently working on moving my own site over to Zope. Now I'm a wee way through this, and in the process, thinking about how I might make managing the site easier with the Content Management capabilities Zope has to offer. One of the obvious candidates is the news on the home page. The fact that I've hardly touched it in months will give you the idea of how much of a pain it is. What that news update is is a single static text file which is included into the main page at run time. I manage the include by telnetting (using SSH - I'm not totally stupid) into the server and hand-editing using vi. This leaves much to be desired - not least that firewalls in various locations I'd like to edit it from prevent me from doing it - and ensures that it's generally too much hassle to actually do. So with this Brave New World of Content Management, I thought I could improve it.
Let's face it, my news system is basically a Blog, as common as muck. So the problem is pretty well defined - everyone knows what a Blog should look like - a list of items with links to permanent URLs. The fun bit comes when you break that down into what a system should do, and then how to code it for Zope. The first bit is what I'm used to doing, the second is frustrating because Zope is not a well-documented system, particularly with regard to code samples. When you hear that it has a Cliff-like Learning Curve, you know what you're in for... But as Zope is an Object Oriented database which thinks it's a file-system, and has a bunch of simple (if badly documented) APIs, it should be possible to work out and implement a simple set of system requirements for a blog using the small amount of OOP theory I know.
With your new object, fill in some dummy data - the content will need to be normal HTML, but the rest is just a simple string for the title, or dates formatted as above. Now if you've browsed the Zope Book you'll be asking yourself
Is this a Blog I see before me?
Let's face it, my news system is basically a Blog, as common as muck. So the problem is pretty well defined - everyone knows what a Blog should look like - a list of items with links to permanent URLs. The fun bit comes when you break that down into what a system should do, and then how to code it for Zope. The first bit is what I'm used to doing, the second is frustrating because Zope is not a well-documented system, particularly with regard to code samples. When you hear that it has a Cliff-like Learning Curve, you know what you're in for... But as Zope is an Object Oriented database which thinks it's a file-system, and has a bunch of simple (if badly documented) APIs, it should be possible to work out and implement a simple set of system requirements for a blog using the small amount of OOP theory I know.OOP Theory 101
(Apologies if this is either wrong or just plain over-simplified to the point of error - it's a mental model which works for me at this level of complexity) In an OOP system, you have two kinds of things:- Objects - things with properties which are exposed to other things.
- Methods - actions you can carry out on Objects without worrying about exactly how they work.
System Requirements
Note that this means the requirements the system you're working needs to fulfil, rather than the software you need to buy. At its simplest, then, each news item is an Object containing content and meta-content - information about the content. What we want to do is have each news object as self-contained as possible so that we can build pages and indices just by calling simple methods on that object - and that will be our entire system. So it makes sense for that meta-content to be as rich as is needed to support all the requirements we might reasonably have. To work out what meta-content we'll need, let's think about our requirements for a moment. I'm going to use a simplified version of a methodology called Use Cases. Each Use Case is a description of what happens in a situation when the system is used (not how it happens - that's a lower level of design). You'll notice that requirements in the first Use Case have implications for later ones.Blog Use Cases
- User visits Home Page
- Home Page displays last five news items, ordered by date, most recent first.
- If there are no news items, the user is notified.
- Only news items which are past their launch date should be displayed.
- Only news items which have not expired should be displayed.
- Each item should have a link to a permanent URL to avoid Linkrot.
- Each item should display a headline, its launch date and its content.
- This short list of the most recent items should contain a link to a full list of news items.
- If the content has been updated since launch time, the user should see it flagged with the update date.
- User visits permanent URL
- The URL is displayed on its own page
- The news item is displayed in the site's standard style (layout, stylesheet etc).
- The page contains the item's headline, launch-date and body content.
- The item is only available between its launch and expiry dates inclusive (ie they are the first and last dates it is available). At other times, an error message is displayed.
- User visits full news listing page
As per Home Page, but with all available news items listed (not just the most recent five).
News Object Properties
So, going through the Use Cases, here's what we need in terms of a news object's properties:- Headline The standard
title
Zope property can do this nicely - Content The main content of the news item
- URL The uniqueness can be derived from the standard Zope object
id
property - they need to be unique anyway. The content manager will have to manually choose these, but with a sensible naming schema (egyyyy_mm_dd
, this shouldn't be a problem. - Launch Date - the first date the item is available. To give the content manager flexibility, this should be manually specified, and will have to be a date-format property.
- Expiry Date See Launch Date
- Updated Date This needn't be manually edited, and is probably better not anyway so that the system can automatically pick up re-edited items. This might be a problem if you want to sneakily re-edit something embarrassing, and another issue could be that changes in properties (eg launch date) are classed as an edit by Zope.
Blog Methods
As we're in an OOP environment, the other thing we need to do is specify what methods (aka functions) we can perform on the news item objects. Fortunately, looking at the Use Cases above, we've done that already - we essentially need one method for each Use Case. And it's even simpler than that, because Zope gives us one of them for free - theDisplay Permanent URL
Use Case is close enough to the basic Zope View
method as makes little difference. We'll need to do a tiny bit of conditionalisation around launch and expiry dates, but not enough to be worth writing a separate method specifically for it.System setup and coding
Right, now that's the hard work done, let's get our hands dirty. I've put together target="_blank" title="Opens in a new window">a demo for you to have a look at while you're doing yours.Object setup
Create a folder below your site root callednews_items
. This is where we'll store all our news objects. Now create a basic DTML Document in that folder - this is the base object type we're going to use as it lets us easily add all object properties we'll need. Ignore the Edit
screen for a moment, and pop over to the Properties
tab. You're going to add three properties:Property | Type | Format for values |
---|---|---|
launchDate | date | yyyy/mm/dd (in other words - the ISO standard for short dates) |
expireDate | date | yyyy/mm/dd |
newsContent | text | Text - this will be a <textarea> form field. |
Why keep the content in a property rather than in the basic Edit
slot?
The answer is that we're going to be producing a list of news objects, including their main content. If we'd stuck it in the Edit
slot, we'd either get the standard page headers and footers (via standard_html_header
and standard_html_footer
) nested in our list, or we'd have problems with missing headers and footers when we accessed the object's URL. There's another cunning thing we can do if it's here, but I'll leave that surprise until later...Method Setup
Enabling the View Permanent URL method
If you view the object now via its normal URL, you'll get a page without a lot on it. That's fine, because theView
method only uses the basic Zope object properties like Title
. What we're going to do now is add to that basic method to drop in our content. Here's the basic code for the View
method, assuming that your standard_html_header
takes care of <title>
and sticking a sensible <h1>
title in:<dtml-var standard_html_header> <p><strong><dtml-var launchDate fmt="%d %B %Y"></strong></p> <dtml-var newsContent><dtml-var standard_html_footer>Easy, eh? The one slight complexity is the date formatting, which uses these codes in a similar way to SSI date formatting. Now view the object - you should see something closer to a correct rendition of the news item in your site's style (you may need to add stylesheet references to the above of course). The other thing that the
Visit Permanent URL
Use Case called for was a conditionalisation on launch and expiry dates. So we're going to add that tiny bit of conditionalisation I mentioned:<dtml-var standard_html_header> <dtml-if "ZopeTime() - launchDate>= 0 and expireDate - ZopeTime()>= -1"> <p><strong><dtml-var launchDate fmt="%d %B %Y"></strong></p> <dtml-var newsContent fmt=structured-text> <dtml-else> <h3>This news item is not available</h3> <p>Please return to the <a href="./">full list of news stories</a>.</p> </dtml-if><dtml-var standard_html_footer>
ZopeTime
is just a variable containing the current date and time. If you want to see what it contains, just try <dtml-var ZopeTime>
(all the custom date formats work with this).Adding the View Full News Listing Method
This method is more complex, and maybe we want to access it in more than one place, so we're going to keep it separate from the page design and news content (this is A Good Thing). In your root folder, create a DTML Method callednews_full_list
. We're going to build this up like the last method, starting from the simple core, and adding conditionalisation as we go. What we need to do is iterate through the news_items
folder object and retrieve its subobjects - the news item objects which are all DTML Documents. This is pretty simple stuff, it's the basic dtml-in
tag. So, with similar outputs to the View
method:<dtml-in expr="news_items.objectValues(['DTML Document'])" reverse sort=launchDate> <h2> <dtml-var title> (<dtml-var launchDate fmt="%d %B %Y">) </h2> <dtml-var newsContent> <small><a href="/news_items/<dtml-var id>">Permanent link</a></small></p> </dtml-in>Note the
reverse
and sort
attributes in the dtml-in
statement. Again, your stylesheet and heading-level mileage may vary. This will give you a full list of all the news objects in the news_items folder - if you view the method, you'll just get that output, with no HTML round it. So we need to drop it into a page somehow. Create a DTML document called news_html
. By the miracle of acquisition, this can be anywhere, but to make life easy, drop it in your root folder. All you need in this document is a title and the following DTML in the main View
method: <dtml-var standard_html_header> <dtml-var news_full_list> <dtml-var standard_html_footer>View that object, and you should get your one dummy object popping up. Add some more and you'll have a whole list. Right, back to the method, and fulfilling the date conditions. Unsurprisingly, it's the same condition as in the
View URL
method:<dtml-in expr="news_items.objectValues(['DTML Document'])" reverse sort=launchDate> <dtml-if "ZopeTime() - launchDate>= 0 and expireDate - ZopeTime()>= -1"> <h5> <dtml-var title> (<dtml-var launchDate fmt="%d %B %Y">) </h5> <dtml-var newsContent> <small><a href="/news_items/<dtml-var id>">Permanent link</a></small></p> </dtml-if> </dtml-in>If you play about a bit with the launch and expiry properties for your objects, you'll have them popping in and out like nobody's business. Around midnight is a good time to do this so you can see the condition ticking over. The last bit of the puzzle is the automatic "Updated" note if an item is updated past its launch date. My note is highlighted with a superscript, styled to be white text on red (IMO, about one step down from 'blink'), but once more, yours may vary:
<dtml-in expr="news_items.objectValues(['DTML Document'])" reverse sort=launchDate> <dtml-if "ZopeTime() - launchDate>= 0 and expireDate - ZopeTime()>= -1"> <h2> <dtml-var title> (<dtml-var launchDate fmt="%d %B %Y">) <dtml-if "bobobase_modification_time() - launchDate >= 1 and ZopeTime() - bobobase_modification_time()<=5"> <sup class="new"> Updated <dtml-var bobobase_modification_time fmt="%d %B %Y"> </sup> </dtml-if> </h2> <dtml-var newsContent> <small><a href="/news_items/<dtml-var id>">Permanent link</a></small></p> </dtml-if> </dtml-in>You've probably guessed what
bobobase_modification_time
contains. We're nearly there - just one more method to go.The Home Page Listing Method
This is very, very similar to the full listing method, except that we only want the last four objects (by launch date). No problem - stick aend=5
attribute in the dtml-in
and we're there. But to give it the full instructions, add another DTML Method called news
, with the following code:<dtml-in expr="news_items.objectValues(['DTML Document'])" reverse sort=launchDate end=5> <dtml-if "ZopeTime() - launchDate>= 0 and expireDate - ZopeTime()>= -1"> <h5> <dtml-var title> (<dtml-var launchDate fmt="%d %B %Y">) <dtml-if "bobobase_modification_time() - launchDate >= 1 and ZopeTime() - bobobase_modification_time()<=5"> <sup class="new"> Updated <dtml-var bobobase_modification_time fmt="%d %B %Y"> </sup> </dtml-if> </h5> <dtml-var newsContent> <small><a href="/news_items/<dtml-var id>">Permanent link</a></small></p> </dtml-if> </dtml-in>I've also changed the heading tag to fit better into my home page hierarchy which has other content in it. Dropping it into your
index_html
is a piece of cake:<h4>In the News</h4> <dtml-var news> <p><a href="./news_html">All the news</a></p>
Final Finesse - Text Entry and Formatting
I mentioned one final piece of finesse which having the content in a defined property allows us. That finesse is allowing formatted entry without knowing one bit of HTML, using Structured Text Structured Text is great. It lets a content editor do pretty much most of the things most content editors want to do in the way of formatting, without having to know HTML. It adds<p>
tags for double lines, <ul>
and <li>
tags with a simple indented paragraph starting with an asterisk or an o
- all kinds of nice stuff. (If you want, you can intersperse HTML in there too). So for an easy to use Blog, it makes a lot of sense - you don't have to think about code, just content (in fact, this article was written using Structured Text, although I needed a couple of touchups for final presentation). But how to get the content rendered into HTML in the pages and full listings? Well it turns out that if you're reading the content in from an object property, it's really simple. All you have to do is add fmt=structured-text
to the dtml-var
statment like so:<dtml-var newsContent fmt=structured-text>So our full and final
news View Full News Listing
method looks like this:<dtml-in expr="news_items.objectValues(['DTML Document'])" reverse end=5 sort=launchDate> <dtml-if "ZopeTime() - launchDate>= 0 and expireDate - ZopeTime()>= -1"> <h5> <dtml-var title> (<dtml-var launchDate fmt="%d %B %Y">) <dtml-if "bobobase_modification_time() - launchDate >= 1 and ZopeTime() - bobobase_modification_time()<=5"> <sup class="new"> Updated <dtml-var bobobase_modification_time fmt="%d %B %Y"> </sup> </dtml-if> </h5> <dtml-var newsContent fmt=structured-text> <small><a href="/news_items/<dtml-var id>">Permanent link</a></small></p> </dtml-if> </dtml-in>and our final View Permanent URL method is this:
<dtml-var standard_html_header> <dtml-if "ZopeTime() - launchDate>= 0 and expireDate - ZopeTime()>= -1"> <p><strong><dtml-var launchDate fmt="%d %B %Y"></strong></p> <dtml-var newsContent fmt=structured-text> <dtml-else> <h3>This news item is not available</h3> <p>Please return to the <a href="./">full list of news stories</a>.</p> </dtml-if> <dtml-var standard_html_footer>Now you've got a working Blog system, the only thing left to do is to write some original content. Ah, but that's when it get really hard...
More info
If your system is more complex than this, and you have lots of people working on different elements, you could do worse than looking to the Zope Fishbowl Process for some effective ways of working worth borrowing.