I created a very simple blog application using the OpenACS Content Repository (CR). When I say simple, I mean simple, probably to the point of being useless. I just wanted to try writing something as quickly as possible using the CR. Here’s a quick run-through of the code, in case it might help someone else understand how to use the CR. I’m not including much commentary; email me (or comment) if you’d like me to expound more.
####blog.info
<?xml version="1.0"?>
<!-- Generated by the OpenACS Package Manager -->
<package key="blog" url="http://openacs.org/repository/apm/packages/blog" type="apm_application">
<package-name>Blog</package-name>
<pretty-plural>Blogs</pretty-plural>
<initial-install-p>f</initial-install-p>
<singleton-p>f</singleton-p>
<version name="0.1d" url="http://openacs.org/repository/download/apm/blog-0.1d.apm">
<owner url="mailto:admin@localhost">Admin User</owner>
<summary>A blog application.</summary>
<description format="text/html">A simple Blog application using the Content Repository.</description>
<maturity>0</maturity>
<provides url="blog" version="0.1d"/>
<requires url="acs-content-repository" version="5.2.2"/>
<callbacks>
<callback type="after-install" proc="blog::apm::after_install"/>
<callback type="after-instantiate" proc="blog::apm::after_instantiate"/>
<callback type="before-uninstantiate" proc="blog::apm::before_uninstantiate"/>
<callback type="before-uninstall" proc="blog::apm::before_uninstall"/>
</callbacks>
<parameters>
<!-- No version parameters -->
</parameters>
</version>
</package>
####tcl/blog-apm-procs.tcl
namespace eval blog::apm {}
ad_proc -private blog::apm::after_install {} {
Install the blog datamodel.
} {
content::type::new -content_type blog_post \
-pretty_name "Blog Post" \
-pretty_plural "Blog Posts" \
-table_name "cr_blog_posts" \
-id_column "post_id"
}
ad_proc -private blog::apm::after_instantiate { -package_id } {
Setup one instance of a blog. Creates a content folder and associate
blog_posts with it.
} {
set folder_id [content::folder::new \
-name $package_id \
-label "Blog Folder $package_id" \
-package_id $package_id \
-context_id $package_id]
content::folder::register_content_type \
-folder_id $folder_id \
-content_type "blog_post" \
-include_subtypes "t"
}
ad_proc -private blog::apm::before_uninstantiate { -package_id } {
Remove 1 instance of blog application.
} {
set folder_id [blog::folder_id $package_id]
content::folder::delete -folder_id $folder_id -cascade_p t
content::type::delete -content_type blog_post \
-drop_children_p t \
-drop_table_p t
}
ad_proc -private blog::apm::before_uninstall {} {
Drop the application.
} {
content::type::delete -content_type blog_post \
-drop_children_p t \
-drop_table_p t
}
####tcl/blog-procs.tcl
namespace eval blog {}
ad_proc -public blog::folder_id {package_id} {
Return the CR folder_id associated with this package_id
@param package_id Current package_id
} {
return [db_string get_folder_id "select folder_id from cr_folders where package_id=:package_id"]
}
####www/index.tcl
ad_page_contract {
List posts
@author Vinod Kurup [vinod@kurup.com]
@creation-date Tue Feb 14 20:56:24 2006
@cvs-id $Id:$
} {
} {
set package_id [ad_conn package_id]
db_multirow posts posts "
select i.item_id,
r.title,
r.content,
to_char(o.creation_date,'YYYY-MM-DD HH24:MI:SS') as creation_date,
o.creation_user
from cr_blog_posts p, cr_items i, cr_revisions r, acs_objects o
where i.item_id=o.object_id
and p.post_id=r.revision_id
and o.package_id=:package_id
and i.live_revision=p.post_id
order by creation_date desc"
}
####www/index.adp
<master>
<p><a href="post/ae">Add Post</a></p>
<multiple name="posts">
<include src="../lib/one-post"
item_id="@posts.item_id@"
title="@posts.title@"
content="@posts.content@"
creation_user="@posts.creation_user@"
creation_date="@posts.creation_date@"
/>
</multiple>
####www/post/ae.tcl
ad_page_contract {
Add or edit a post
@author Vinod Kurup [vinod@kurup.com]
@creation-date Tue Feb 14 20:39:39 2006
@cvs-id $Id:$
} {
item_id:integer,optional
} {
set package_id [ad_conn package_id]
set user_id [ad_conn user_id]
set ip_addr [ad_conn peeraddr]
permission::require_permission -object_id $package_id -privilege write
set context [list "Add/Edit Post"]
ad_form -name add_post -form {
item_id:key(t_acs_object_id_seq)
title:text(text)
content:text(textarea)
} -select_query {
select r.title, r.content
from cr_items i, cr_revisions r
where i.item_id=:item_id
and i.live_revision=r.revision_id
} -edit_data {
content::revision::new -item_id $item_id \
-title $title \
-content $content \
-package_id $package_id \
-is_live t
ns_returnredirect ..
} -new_data {
content::item::new -name "Post $item_id" \
-item_id $item_id \
-creation_user $user_id \
-text $content \
-package_id $package_id \
-parent_id [blog::folder_id $package_id] \
-creation_ip $ip_addr \
-content_type "blog_post" \
-title $title
ns_returnredirect ..
}
####www/post/ae.adp
<master>
<property name="title">Add/Edit Post</property>
<property name="context">@context;noquote@</property>
<formtemplate id="add_post"></formtemplate>
####www/post/del.tcl
ad_page_contract {
Delete a post
@author Vinod Kurup [vinod@kurup.com]
@creation-date Tue Feb 14 22:49:11 2006
@cvs-id $Id:$
} {
item_id:integer
} {
permission::require_permission -object_id $item_id -privilege write
content::item::delete -item_id $item_id
ns_returnredirect ..
}
####www/post/view.tcl
ad_page_contract {
View one post
@author Vinod Kurup [vinod@kurup.com]
@creation-date Wed Feb 15 20:24:32 2006
@cvs-id $Id:$
} {
item_id:integer
} {
set package_id [ad_conn package_id]
set package_url [ad_conn package_url]
db_0or1row post " select i.item_id,
r.title,
r.content,
to_char(o.creation_date,'YYYY-MM-DD HH24:MI:SS') as creation_date,
o.creation_user
from cr_blog_posts p, cr_items i, cr_revisions r, acs_objects o
where i.item_id=o.object_id
and p.post_id=r.revision_id
and o.package_id=:package_id
and i.live_revision=p.post_id
and i.item_id=:item_id"
set context [list $title]
####www/post/view.adp
<master>
<property name="context">@context;noquote@</property>
<property name="title">@title@</property>
<include src="../../lib/one-post"
item_id="@item_id@"
title="@title@"
content="@content@"
creation_user="@creation_user@"
creation_date="@creation_date@"
/>
####lib/one-post.tcl
set package_url [ad_conn package_url]
set now [clock format [clock seconds] -format "%Y-%m-%d %H:%M:%S"]
set creation_date_pretty [util::age_pretty \
-timestamp_ansi $creation_date \
-sysdate_ansi $now]
set author [acs_community_member_link -user_id $creation_user]
set write_p [permission::permission_p -object_id $item_id -privilege write]
set perm_url [export_vars -base "${package_url}post/view" {item_id}]
####lib/one-post.adp
<div class="post">
<h2><a href="@perm_url@">@title@</a></h2>
<p class="byline">
Posted by @author;noquote@
<span class="date">@creation_date_pretty@</span>
</p>
<div class="content">
@content;noquote@
</div>
<p class="actions">
<if @write_p@>
<a href="@package_url@post/ae?item_id=@item_id@">Edit</a> |
<a href="@package_url@post/del?item_id=@item_id@">Delete</a>
</if>
</p>
</div>
####Notes
You’ll notice that there are no SQL datamodel files. Since I’m using the CR, all of the datamodel is created by using the CR TCL API (specifically content::type::new). This package is subsite aware meaning you can install apps on multiple subsites and the data in each subsite will remain isolated from other subsites. It should uninstall itself cleanly since I’ve written callbacks for before-uninstantiate and before-uninstall.
Like I said, this is very simplistic. You can create, read, update and delete posts, but that’s about it. No commenting. No tags or categories. No RSS feeds. No search. But, since we’ve used the CR as our base, I think it would be very easy to extend the app to do all those things. I’m going to work on that, just for fun, of course. Thanks to Dave Bauer for writing the TCL interface to the CR and for writing the wiki package which made me see the light about the benefits of using the CR.