Vinod Kurup

Hospitalist/programmer in search of the meaning of life

Feb 16, 2006 - 5 minute read - Comments - blog openacs programming web

Creating a Blog Application using the OpenACS Content Repository

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.

Feb 14, 2006 - 1 minute read - Comments - quotes friendship

Friendship

Happy Valentine’s Day! Writing about love is a little risque for me, so here instead is a wonderful quote about friendship. I heard it on the University Channel Podcast where Robert Seiple mistakenly attributed it to George Eliot. It was actually written by Dinah Craik in her novel A life for a life (1859).

Oh, the comfort –
The inexpressible comfort of feeling
safe with a person,
Having neither to weigh thoughts,
Nor measure words – but pouring them
All right out – just as they are –
Chaff and grain together –
Certain that a faithful hand will
Take and sift them –
Keep what is worth keeping –
and with the breath of kindness
Blow the rest away.

Feb 13, 2006 - 2 minute read - Comments - shopping fees

Foreign transaction fees

Now that credit card companies are charging extra fees for foreign transactions, you need to know where online companies are physically located. For example, did you know that Abebooks, an online aggregator of bookstores, is based in Canada? We found out when we bought books from them and found our credit card statement littered with foreign transaction fees. The ironic thing is that the book actually shipped from the U.S. (from Amazon.com, in fact). Of course, there’s not much Abebooks can do about this, but you need to calculate the extra costs into the transaction to see whether you’re still getting a good deal.

Comments from old site

Amazon.com charges credit cards directly

Hi Vinod. I work in the online book industry. If you bought the book from the Amazon.com website, they charge credit cards directly, rather than passing it along to Marketplace/zShops booksellers. The charges couldn't have been generated by Abebooks, because they never got your credit card information.

Is it possible that (1) you bought the book directly from Abebooks, rather than Amazon.com, (2) you bought the book from a non-US Amazon.com site (e.g. Amazon.ca, Amazon.co.uk), or (3) that the foreign transaction fees are for something altogether different?

Unregistered Visitor 2006-02-14 16:52:21

Yes, I bought directly from Abebooks

Sorry if I wasn't clear about that. I didn't buy from Amazon.com. As I said in the post, I bought online on the Abebooks.com website. The book just happened to come "packaged" in an Amazon cardboard box.

Vinod Kurup 2006-02-14 17:18:24

Abe CC

This will be more and more of an issue as Abebooks moves to process the credit card transactions for all of their booksellers.

You can be an American buyer purchasing from an American seller and still pay the 2-3% surcharge solely because ABE is in Canada.

Unregistered Visitor 2006-02-17 22:56:00

Feb 8, 2006 - 2 minute read - Comments - investing quarterly

Doing nothing

It’s time for my quarterly investment update. Every 3 months, I look at my portfolio and sell stocks that I don’t like and then buy ones that I do. This quarter, I’m doing nothing.

This is one of the hardest things for me to do. I like to trade. Sell a stock here, buy a new one here. It’s exciting. Especially when you sell a stock that’s been down for a while because then you no longer have to look at that red smirch on your portfolio every day. But while activity is a good thing in almost every other aspect of life, it can hurt you in investing. The undisputable fact is that the more you trade, the more you’ll lose on commissions and spreads. What’s more disputable, but true in my mind is that investing overactivity makes me do dumb things.

A couple of the stocks that I had sold over the past year have made strong runs since I sold them. AMN has gained 47% and DUCK has gained 20%. I probably shouldn’t have sold either of them, if I had followed my strict evaluation process. But I wanted to sell them. Each company had its problems, so I found good reasons to sell and justifed them to myself. The problem was that I didn’t follow the sell criteria that I had set up before I bought the stocks. I made up new ones just so I could sell the stock. If I had stuck with my initial criteria, I’d still be holding on to those two and enjoying my gains.

I guess my lesson is that you should decide under what conditions you would sell any stock before you buy it. Until those conditions appear, hold tight. This is especially true for small undervalued stocks because it can take a long time for the market to come around and properly value them. James Cloonan, over at The American Association of Individual Investors runs a Shadow Stock Portfolio on which my portfolio is loosely based. He recommends giving small-cap value stocks at least 2 years to be recognized by the market.

Patience, young grasshopper!

Jan 5, 2006 - 2 minute read - Comments - investing performance annual

Investment Performance 1996-2005

As promised last year, here’s my 2005 Investment Performance update. My 403b account (VFINX, VWNDX, VEXPX and VTRIX) was up 6.7% and my individual stock investments were up 22.0% as compared to the stock market (VFINX) being up 4.7%. That makes 4 years in a row that my overall investments have beat the index and 2 years in a row that my stock picks have. It’s that second comparison that is most important to me - my individual stock picks versus the index. I have little control over our 403b accounts, being limited only to mutual funds, so I have much less confidence that I’ll be able to consistently outperform there.

Date Me VFINX
1996-2005 5.7% 5.9%
1996 22.7% 35.7%
1997 15.3% 28.6%
1998 15.1% 24.9%
1999 66.0% 26.1%
2000 -25.8% -7.4%
2001 -33.1% -12.3%
2002 -21.1% -22.1%
2003 31.7% 30.1%
2004 13.5% 11.7%
2005 13.2% 4.7%

Notes: These are calculated using the XIRR function in OpenOffice. The XIRR function takes a series of cash flows in or out of an account and returns the internal rate of return for that account. It’s the best way to compare portfolio returns when investments or withdrawals are made on an irregular basis. The VFINX column shows how I would have done if I had simply invested all my money into VFINX, a low-cost mutual fund which tracks the S&P 500. This number will not match numbers you see posted for VFINX for a given year because it depends on the timing of my investments. Note that I include the cost of commissions in my performance, but not in VFINX’s.

Jan 4, 2006 - 2 minute read - Comments - coffee

Senseo Coffee Maker

Oh, in my last post, I forgot to mention one other cause we supported - The New York Botanical Gardens. I don’t really think of it as charity, since we definitely get our money’s worth of enjoyment out of the park. In addition, the gardens gave their members a gift this year - the Phillips Senseo Coffee Maker. This is one of those Pod coffee makers. You stick in a coffee-pod and the machine shoots boiling water through the pod and into your coffee mug. It makes 1 cup at a time, but it takes only 30 seconds, so making coffee for a group is just a matter of lining up a bunch of cups and changing the pod each time. Cleanup is easy and the coffee is suprisingly good.

But, the pods are a little expensive - it comes to about 30-40 cents per cup. And, while the coffee is good, I sure miss my MUD. Well, good news. You can make your own coffee pods!. Once I find the right tools (vitamin jars?), I’m going to try this, despite this ominous warning:

Be careful not to use too big of filter or to make sloppy folds. If you plug up your machine the pressure build up will either spray hot water all over the place or will engage the safety locks which prevent the lid from being re-opened.

Dec 31, 2005 - 2 minute read - Comments - charity

Good causes

It’s that time of year again - time to reduce my tax bill! Here are the charities that we’re donating to this year. I think they’re all great causes, so I encourage you to check them out.

  • Electronic Frontier Foundation - The internet is the most innovative force in our lives and the EFF working to preserve freedom on the net.
  • Free Software Foundation - When software is free, it means that we don’t have to reinvent the wheel over and over again. That allows developers to focus on more and more interesting problems.
  • American Red Cross - Too many tragedies this year.
  • Asha for Education - A secular group bringing education to the most needy in India
  • World Federalist Movement - Supporting the idea of a global community, where countries interact through law and cooperation, not through force.
  • Grameen Foundation - Helping the world’s poorest escape poverty through self-sufficiency
  • WNYC - To support my NPR Podcast habit. I wanted to give directly to NPR since I only listen to podcasts and not to the local radio station, but I couldn’t figure out a way to donate to NPR on their website.
  • Epilepsy Foundation of South Central Wisconsin, American Cancer Society - donations for friends who were raising money.

You can look up most charities on the Charity Navigator website to see how efficient and organized they are. Most of the ones above received the highest rating, 4 stars.