not enough random bytes...

Jan192007

Because the world needs another blog...

Filed under: pentropy breve pylons blog 

Okay, maybe it doesn't. But I do. Fed up with Bitakora, I cracked down and started a blog from scratch (well, building on Splee's SimpleBlog tutorial - mostly for the SQLAlchemy tips).

What you see here is the first basic incarnation of it. It's built using Pylons, Breve and SQLAlchemy. Not that it's required, but it also happens to have PostgreSQL living underneath of it.

This is also my second small Pylons application (the first being the Breve site). This also makes it the second live Breve site in existence (that I'm aware of). This is actually much of the reason I'm doing it: I need to learn Pylons and I need to test Breve on real applications (I'm already seeing features that would make life a bit simpler).

Anyway, I've got a lot to do but my basic goals are as follows:

  1. Very basic blog core - no Javascript. No web 2.0. Nothing but posts, comments and tags (and a few themes).
  2. Plugins (for adding all the stuff explicitly excluded in #1).
  3. One-off pages (e.g. "About", "My Projects")
  4. Admin interface.
  5. Multiuser

Of these, #1 is around 90% finished. #2 is in exploratory testing. #3 will take all of an hour. #4 and #5 are pipe-dreams for now.

Some of the first plugins I have in mind are:

  1. Threaded comment system.
  2. Gallery

Oh, and as you can see, the blog is named "Pentropy". I'll have a site/trac/svn up for it soonish.



0 comments Leave a comment


Jan192007

First Pentropy plugin test successful

Filed under: pentropy breve pylons 

The tag cloud on the right side is now a plugin. I utilized the new xinclude feature of Breve to create a component-based page.

The main blog controller:

class PostController ( BaseController ):

    # ...

    @jsonify
    api_tags ( self ):
        tag_index = Tag.select ( order_by = [ asc ( Tag.c._name ) ] )
        tags = [ ( t.name,
                   ( h.url_for ( controller='post', action='by_tag', id=t.id ),
                     len ( t.posts ) ) )
                 for t in tag_index ]
        return dict ( tags )

the plugin controller:

class TagcloudController ( BaseController ):
    def cloud ( self ):
        body = urlopen ( 'http://pentropy.twisty-industries.com/post/api_tags' ).read ( )
        c.tags = simplejson.loads ( body ).items ( )
        c.max = float ( max ( zip ( *zip ( *c.tags )[ 1 ] )[ 1 ] ) )
        return render_response ( 'tagcloud/cloud?fragment=1' )

the plugin's template:

div [
    ol ( class_ = 'tag-list' ) [ [
        li [
            span ( class_ = 'tag-context' ) [ '%d posts are tagged %s' % ( _posts, _tag ) ],

            a ( href = _link, class_ = 'tag',
                style = 'font-size: %0.2fem;' % ( 0.8 + _posts / c.max ) +
                        'color: rgb(%d,100,120);' % ( _posts / c.max * 180 ) +
                        'padding: %0.2fem;' % ( c.max / ( _posts or 1 ) / 10 ) )
            [ _tag ]

        ] for _tag, ( _link, _posts ) in c.tags
    ] ]
]

and finally, the index template looks like this:

# index.b
html [
    body [
        ...
        xinclude ( 'http://pentropy.twisty-industries.com/tagcloud/cloud' )
        ...
    ]
]

The basic sequence of events goes like this: a request is made for index.b (or rather a child template that inherits index, but that's not relevant here), index.b requests the plugin via the xinclude directive. The controller at the url passed to xinclude accesses the published API available in the main controller to get the JSON data describing the available tags (a list of ( tagname, ( link, article_count ) ) ). It then renders its own template (cloud.b) and returns the XHTML output back to the main template which injects it into its own final output.

It seems like a lot of steps for something that could have been (and originally was) simply an included template. However what it provides in return is the ability to define completely encapsulated plugins that have no knowledge of the internal workings of the main application. All they require is a defined JSON (or XMLRPC) interface to retrieve their data from.

The next step is to define a caching mechanism to reduce the number of times this complete sequence must be traversed. In the case of the tag cloud, it's clear we can cache until a new post is made (tags are never created independently of posts in Pentropy). To this end I'll probably add a generic cache directive that will be utilized something like this:

html [
    body [
        cache ( expires = c.tags_changed ) [
            h1 [ 'Tags' ],
            xinclude ( 'http://pentropy.twisty-industries.com/tagcloud/cloud' )
        ]
    ]
]

Having a generic cache directive will allow for static HTML fragments to be stored based upon specific criteria. More on this later.



0 comments Leave a comment


Jan202007

Pentropy gets Akismet support

Filed under: pentropy akismet 

I utilized Michael Foord's Python library to integrate Akismet into Pentropy. I'm keeping the "are you human" simple addition test in place for now, since that appears to work nicely against bots (although I need to make it less machine-parsable eventually).



0 comments Leave a comment


Jan212007

Bayesian blog spam filtering

Filed under: pentropy spam bayesian 

My initial tests with Akismet were not promising. I submitted four comments, all four came back flagged "spam". There was nothing remotely spammish about any of them. I've been told that false positives are a serious issue with Akismet, so I'll move away from that (also the bandwidth required for each post could eventually become an issue). I considered using the Akismet results to decide whether to put comments in a moderation queue, but at that point you may as well simply put them all there and hand-sort them.

Anyway, I found another solution using a Bayesian filter (based on Divmod's Reverend). It requires some training but that's okay.



0 comments Leave a comment


Jan232007

Pentropy gets closer

Filed under: pentropy breve rss 

Had some time to hack on Pentropy last night and managed to get RSS 2.0 working (remarkably easy using xsd2breve), one-off pages (i.e. the "about" link) and the beginnings of an admin interface.

To give a quick example of how easy it was to create an RSS feed in Breve, here's the steps I followed:

First I needed to obtain an xsd file for RSS (this took some Googling as apparently there isn't an actual formal standard). I ended up using the one found here.

The next step is to create a test RSS feed. To this end I copied an example off some random site and edited it by hand:

rss (version="2.0") [
    channel [
        title [ 'Google Jobs' ],
        link [ 'http://www.google.com/support/jobs/' ],
        description [ 'Information about job openings at Google Inc.' ],
        item [
            title [ 'HR Analyst - Mountain View' ],
            link [ 'http://www.google.com/support/jobs/bin/topic.py?dep_id=1077&loc_id=1116 ],
            description [ '''
                We have an immediate need for an experienced
                analytical HR professional.
                The ideal candidate has a proven record of developing
                analytical frameworks to make fact-based decisions.
            ''' ]
        ]
    ]
]

I saved this template in templates/feeds/rss20.b. This gives us a bit of test data to verify our custom tags and template are working.

Next we need to create our custom tags and put them in a place where Breve can find them:

$ mkdir custom_tags
$ cd custom_tags
$ xsd2breve http://www.westinkriebel.com/Public/RSS20.xsd > rss20.py

Now we need to add our custom_tags directory to our Python path and tell Breve to actually use them:

# a Pylons controller
import sys; sys.path.append ( 'pentropy/custom_tags' )

class FeedController ( BaseController ):
    def rss20 ( self ):
        return render_response ( 'feeds/rss20?format=rss20' )

Now I can visit '/feeds/rss20' and see the result (a fantastic RSS feed).

The argument format=rss20 tells Breve to import tags from a module with that name.

Next is to put some actual data into the feed. Modify the controller to return the actual data:

def rss20 ( self ):
     c.posts = Post.select (
         order_by = [ desc ( Post.c.post_date ) ],
         limit = 50
     )
     return render_response ( 'feeds/rss20?format=rss20' )

and in our template:

rss ( version="2.0" ) [
    channel [
        title [ 'Pentropy' ],
        link [ 'http://pentropy.twisty-industries.com/' ],
        description [ 'Random Stuff' ],
        [ item [
              title [ _p.title ],
              link [ 'http://pentropy.twisty-industries.com/%s' % _p.slug ],
              description [ cdata ( _p.html_content ) ]
        ] for _p in c.posts ]
    ]
]

Now clearly, at some point I'll want to pull some of that data from the database or a config file, but for now it actually works.



0 comments Leave a comment


Apr252008

Reclaiming my old blog title

Filed under: pentropy 

I wanted it back, so there you have it.



0 comments Leave a comment


Copyright © 2007, Cliff Wells