Nov

29

Your comments are appreciated

I was gearing up to lay down some TDD on a simple comment e-mailing tool. Then, as I was flipping open the Django source to check something, I noticed a "feeds" module in the comments app. I aspire to Larry Wall's [three cardinal virtues of the programmer] (http://www.hhhh.org/wiml/virtues.html) and a news feed of comments sounds at least as useful as a comment e-mailer, and it had the virtue of already existing, right there in Django.

Ah, but then I noticed: What comments gives you is an RSS news feed. Ahem. I'm cultivating a refined sense of snobbery and hating on MySQL is only the beginning. It has to be Atom or nothing. Don't worry; this time I kept things pretty DRY:

class LatestCommentsAtomFeed(LatestCommentFeed):
def subtitle(self):
return self.description()
 
feed_type = Atom1Feed
 
def item_author_name(self, item):
return item.name

All I did was set the feed type and make the subtitle field stand in for the description field. And hey, I figured since we have a name field in the comment, we may as well use it as the author name for each item. I expect there's some obvious reason why I wouldn't want to do this, but in my minimal, unscientific testing with Net News Wire, it seems to work fine.

I don't know where to embed a link to something of as questionable interest as a comments-only news feed. Well, maybe I do. Here you go.

Nov

29

Flat, But Smart

I just wanted to note something neat.

When I was setting up my flatpage template last night I realized I could run template filters on the two strings, flatpage,title and flatpage.content. So now, instead of having to embed raw HTML in my flatpage entry like a brutish savage, I can enter pure, clean-burning markdown and just pipe it through the parser in the template.

Here's the flatpages/default.html template:

{% extends "base.html" %}
 
{% load markup %}
 
{% block title %}{{ block.super }} - {{ flatpage.title }}{% endblock %}
 
{% block content %}
<h1>{{ flatpage.title }}</h1>
{{ flatpage.content|markdown }}
{% endblock %}

Funny side note: I actually felt shamed into editing this template to conform to Eric Holscher's Gentleman's Agreemant on Django Templates

Nov

28

Submitted without comment, no longer!

Hey, y'all.

As soon as I got home today i set to work getting commenting working. And sure enough, a mere four hours later, here we are! I haven't changed much from the out-of-the-box django commenting solution, so I'm hoping it just purrs along.

I also added an about page so you can learn some unhelpful trivia about me. Click on my name to get there.

Nov

27

The Florenzano Factor

Eric Florenzano (A Django Wizard) has instigated a meme (Update: Now with URL back to his post. Guh. I'm a yutz.):

Rules:

  1. Implement a program that takes in a user's name and their age, and prints hello to them once for every year that they have been alive.
  2. Post these rules, the source code for your solution, and the following list (with you included) on your blog.
  3. Bonus points if you implement it in a language not yet seen on the following list!

The List:

  1. [Python] http://www.eflorenzano.com/blog/post/trying-start-programming-meme
  2. [Bash] http://aartemenko.com/texts/bash-meme/
  3. [C] http://dakrauth.com/media/site/text/hello.c
  4. [Java] http://adoleo.com/blog/2008/nov/25/programming-meme/
  5. [Python 3] http://mikewatkins.ca/2008/11/25/hello-meme/
  6. [Ruby] http://stroky.l.googlepages.com/gem
  7. [Ruby] http://im.camronflanders.com/archive/meme/
  8. [Lisp] http://justinlilly.com/blog/2008/nov/25/back-on-the-horse/
  9. [JavaScript] http://www.taylanpince.com/blog/posts/responding-to-a-programming-meme/
  10. [Django] http://www.pocketuniverse.ca/archive/2008/november/27/florenzano-factor/

Yes, friends, that's right. I did it in Django. But not only that, I did it in one file in Django.

Dig it:

{# #} {% comment %}
}
from django import forms
from django.conf.urls.defaults import *
from django.http import HttpResponse
from django.template.loader import render_to_string
 
DEBUG = True
TEMPLATE_DEBUG = DEBUG
 
DATABASE_ENGINE = 'sqlite3'
DATABASE_NAME = ':memory:'
 
TEMPLATE_DIRS = (
'./',
)
 
ROOT_URLCONF = 'settings'
 
class NameAgeForm(forms.Form):
name = forms.CharField()
age = forms.IntegerField()
 
def view(request):
if 'name' in request.GET:
form = NameAgeForm(request.GET)
if form.is_valid():
name = form.cleaned_data['name']
ages = range(1, form.cleaned_data['age'] + 1)
else:
form = NameAgeForm()
return HttpResponse(render_to_string('settings.py', locals()))
 
urlpatterns = patterns('',
(r'^$', view),
)
 

INSTALLED_APPS = ()
 
# {% endcomment %} <html><head><title>The Age Machine</title></head><body>{% if name and ages %} {% comment %}
# {% endcomment %} <ul>{% for age in ages %}<li>{{ age }} - Hello {{ name }}</li>{% endfor %}</ul> {% comment %}
# {% endcomment %} <form action="/"><input type="submit" value="Reset" /></form> {% comment %}
# {% endcomment %} {% else %}<form method="GET" action="/">{{ form.as_p }}<input type="submit" /></form> {% endif %}</body></html>

To run it, just put this code in a file called settings.py and run:
django-admin.py runserver --settings="settings"

I managed to get everything crammed into the settings file, and then I made the settings file double as the template. You're either going to be really impressed or really angry. Let me know, either way.

sam - at - pocketuniverse.ca

Nov

27

Yes, I know

The source code in the last post is probably too wide for your browser window and the blog post window isn't expanding properly. And, if you have a horizontal scroll bar and use it, you'll see the blog post design disconnect from the background. (Update: Fixed. I told the pre elements to apply scroll bars to text not content to be bound by the requirements of reasonable width.)

Not pretty.

It looks like I'll have to make this a tiling, scrolling background after all. Boo.

This is why it's better to have other people around who can take care of all this HTML and CSS.

Nov

27

These Are the Months Of Our Lives

Behold! An update!

I've just added month-based archiving. It's just another wrapper around a generic view. I did write a couple template tags to help build my list of archive links the way I wanted to. I wanted to present a flat list of months with posts in them, leaving out the year-archive pages entirely.

Here's the template code I wrote to present the list of archive month links on the home page:

<ul class="nav-bar">
<li>Archive: </li>
{% for d in date_list|months_with_content %}
{% with d|date:"F" as m %}
<li><a href="{% url blog_archive_month year=d.year,month=m.lower %}">{{ m }} {{ d.year }}</a></li>
{% endwith %}
{% endfor %}
</ul>

For the month archive template I wrote a different block of code to link to list the current month and the months on either side, since that's what the archive_month view affords me

<ul class="nav-bar">
<li>Archive: </li>
 
{% if previous_month|has_posts_in_month %}
{% with previous_month|date:"F" as month_str %}
<li><a href="{% url blog_archive_month year=previous_month.year,month=month_str.lower %}">{{ month_str }} {{ previous_month.year }}</a></li>
{% endwith %}
{% endif %}
 
{% with month|date:"F" as month_str %}
<li><a class="active" href="{% url blog_archive_month year=month.year,month=month_str.lower %}">{{ month_str }} {{ month.year }}</a></li>
{% endwith %}
 
{% if next_month|has_posts_in_month %}
{% with next_month|date:"F" as month_str %}
<li><a href="{% url blog_archive_month year=next_month.year,month=month_str.lower %}">{{ month_str }} {{ next_month.year }}</a></li>
{% endwith %}
{% endif %}
</ul>

And finally, here are the two template tags I wrote that are used in that template code:

@register.filter
def months_with_content(date_list):
"""
Returns a list of months where there are blog posts within the year range
provided.
 
Syntax:
 
{{ date_list|months_with_content }}
 
"""
dates = []
for year_date in date_list:
dates += list(BlogPost.objects.filter(pub_dateyear=year_date.year).dates('pub_date', 'month'))
return dates
 

@register.filter
def has_posts_in_month(date):
if not date:
return False
return BlogPost.objects.filter(pub_date
year=date.year, pub_date__month=date.month).count() > 0

Can you tell I'm a back-end programmer from that beastly Django templating?

Nov

27

I Bite My Tongue, Sir

This is me not complaining about my cruddy day at work. I can always fire up my livejournal account if I feel like contributing a steaming pile of "content" like that.

Nov

26

Your Ever-bloggin' Man

I changed something else on the site just now, but you can't see it. I changed the home page view to wrap the date-based archive_index view instead of just direct_to_template.

So now, when I write my 16th news post, the first post will slide out of view on the home page. And if I get sneaky and write any posts dated into the future, they'll stay tucked away until the appointed date and time.

Here's how my home view looks now:

def home(request):
return date_based.archive_index(
request,
BlogPost.objects.all(),
date_field='pub_date',
template_name='blog/home.html',
template_object_name='latest_blogposts')

And clever (for once) me, I remembered to run my tests and discovered that two no longer worked because I'd changed the name of the list of blog posts. That's fixed and all tests are passing. I haven't added any tests to confirm that archive_index is doing its special thing, though. That's bad. I should add a couple tests tomorrow. It's too late to be TDD, but it would be something.

Nov

26

I Have The Power!

As you can now see, this site is brought to you courtesy of magical ponies.

In the end (once I get categories implemented) I don't think Django is going to be the only thing I discuss here, but it's what I do all day, and it's what I'm using to build this whole ramshackle thing. And believe me, you're all better off with this limitation. Sooner or later I'll start blathering about my cute kitty-cat or housework and nobody really needs that.

So, for now, welcome to my Django blog!

Nov

25

Good PR

Now this is a smart phone. And an excellent PR campaign.

Nov

25

A Bridge Too Far: Simple Redirects

No new work on the blog today. Boo.

Unless you count spending half an hour trying again to get pocketuniverse.ca to redirect to www.pocketuniverse.ca. Django only does the redirect when it's in the mood, so I tried adding an Apache rule. Thus enters (dramatic music) mod_rewrite, slinking its way in, complicating the simple, and confounding the brilliant.

This is in my virtual host for pocketuniverse.ca (with a ServerAlias to www.pocketuniverse.ca). Does it make any sense? (contact email = my first name at this domain name)

  RewriteCond %{HTTP_HOST}   !^www.pocketuniverse.ca [NC]
RewriteCond %{HTTP_HOST} !^$
RewriteRule ^/(.*) http://www.pocketuniverse.ca/$1 [L,R]

Oh yeah. I did get one other thing set up: Google Analytics. It's set to track traffic to pocketuniverse.ca, so I should see at least some of you guys as you come in.

Update:
I got it working. You know what helped?

  RewriteEngine on

Nov

23

Before I Forget

My good friend Taylan has just launched his actually functional, handsome and fancy blog. Behold: http://www.taylanpince.com/ (Update: Hey, now it's a link!) (Update 2: And hey, now it's not only a link, but a link to the right place. Why is blogging so hard?)

Nov

23

Feed Me Seymour

In response to a flood of calls, telegrams and carrier pigeons, I have given in and implemented an Atom news feed for these posts. As a side effect, I've also implemented a blog post detail view.

Still to come: categories, comments, and pagination. For now, check out my date-based object_detail wrapper view.

def post_detail(request, year, month, day, slug):
return date_based.object_detail(
request,
year=year,
month=month, month_format="%B",
day=day,
slug=slug, slug_field='slug',
date_field='pub_date',
queryset=BlogPost.objects.all(),
template_object_name='blogpost'
)

At this point I could have just as easily used the generic view directly, but some of my fellow Django nerds have picked up two funny habits with views:

  1. Never use generic views directly
  2. Always use them indirectly.

That is, even if your view doesn't add anything you should still create it and call the generic view from inside instead of by using the generic view directly and passing a dict in the url conf. And even if your view does a whole bunch of stuff that's not at all covered by the generic view, you should still ultimately let some generic view generate the response. If nothing else, just use simple.direct_to_template and pass in your context.

The shortest explanation for why this makes sense is: we don't like business logic in our url confs, and we don't like having to construct RequestContext objects all the time. Beyond that, it's more hazy. To be honest, I'm still mostly giving it a test drive to see if my friends are crazy or not.

Nov

22

I said it, I did it.

I got syntax highlight working. I didn't do it during the same calendar day as when I said I'd do it, but everyone knows that your day can't actually end until you go to sleep. I'm currently in 2 hours of overtime on the 21st of November.

I figured this would be a little tricky, but I was honestly surprised that the solutions people had described in their blogs and on djangosnippets were so... complicated. Pygments as a markdown pre-processor. Ideally one would be able to just pass this through as an extension to markdown using the functionality in the existing template tag. Unfortunately, pre-processors seem to be a different sort of animal. In the end I couldn't keep things very DRY. I had to copy and alter the markdown template filter (I trimmed out a lot of the backwards compatibility stuff because I knew what version of markdown I'd be using), and (even worse), I had to copy and alter the actual markdown function.

Here's the code that I copied and modified. The only other things I needed to get this going were Pygments itself and a bunch of illegible css rules that Pygments generated for me. This is the "manni" colouring style, by the way.

from django import template
from django.conf import settings
from django.utils.encoding import smart_str, force_unicode
from django.utils.safestring import mark_safe
 
from markdown import Markdown
 
from pg_md_processor import CodeBlockPreprocessor
 
register = template.Library()
 
@register.filter
def markdown(value, arg=''):
"""
Runs Markdown over a given value, optionally using various
extensions python-markdown supports.
 
Syntax::
 
{{ value|markdown:"extension1_name,extension2_name..." }}
 
To enable safe mode, which strips raw HTML and only returns HTML
generated by actual Markdown syntax, pass "safe" as the first
extension in the list.
 
If the version of Markdown in use does not support extensions,
they will be silently ignored.
 
"""
extensions = [e for e in arg.split(",") if e]
if len(extensions) > 0 and extensions[0] == "safe":
extensions = extensions[1:]
safe_mode = True
else:
safe_mode = False
 
return mark_safe(_pygmented_markdown(force_unicode(value), extensions, safe_mode=safe_mode))
markdown.is_safe = True
 
def _pygmented_markdown(text,
extensions = [],
safe_mode = False):
 
extension_names = []
extension_configs = {}
 
for ext in extensions:
pos = ext.find("(")
if pos == -1:
extension_names.append(ext)
else:
name = ext[:pos]
extension_names.append(name)
pairs = [x.split("=") for x in ext[pos+1:-1].split(",")]
configs = [(x.strip(), y.strip()) for (x, y) in pairs]
extension_configs[name] = configs
 
md = Markdown(extensions=extension_names,
extension_configs=extension_configs,
safe_mode = safe_mode)
 
md.textPreprocessors.insert(0, CodeBlockPreprocessor())
 
return md.convert(text)

Other than stripping out all the compatibility stuff in the template filter, the only change there is from "markdown.markdown" to "_pygmented_markdown". And in _pygmented_markdown, I'm just adding one line towards the bottom:

md.textPreprocessors.insert(0, CodeBlockPreprocessor())

Seems like there ought to be a tidier way to do this.

Commenting should be set up soon. If you need to reach me. My email address is my first name "at" this blog's domain.

Nov

21

Gosh

You know what turns out to be pretty hard?

Resisting the urge to go back and clean up these clunky posts when I re-read them. Editing past posts is a no-no, right?

Hang on, gang. I'll get comments set up soon. Along with RSS, categories and a sensible way to embed images.

Nov

21

Django: First Blood

I realized that markdown will still nicely display code blocks even if there's no fancy syntax colouring (Update: I've got syntax highlighting now. Dig it!), so I thought I'd post the little bit of code I've already written. You may or may not be surprised by just how little I've written to make this thing go. I only have one app, "blog", with one view, and one template. Here's the model:

class BlogPost(models.Model):
"""A simple blog post"""
title = models.CharField(('title'), max_length=100)
slug = models.SlugField(
('slug'), max_length=100, unique_for_date='pub_date')
pub_date = models.DateTimeField(('pub_date'), default=datetime.datetime.now)
body = models.TextField(
('body'), blank=True)
 
class Meta:
verbose_name = ('blog post')
verbose_name_plural =
('blog posts')
ordering = ('-pub_date',)
 
def unicode(self):
return _('%(title)s (Posted: %(pub_date)s)') % {
'title': self.title,
'pub_date': self.pub_date.strftime('%B %d, %Y')
}

I'm trying to stick to Test Driven Development with this project. Here are the tests I've written. The code that makes these tests pass is so boring I won't even post it.

class HomePageTestCase(TestCase):
fixtures = ['test.json']
 
def setUp(self):
self.client = client.Client()
 
def testHomePageLoads(self):
response = self.client.get('/')
self.assertEqual(response.status_code, 200)
 
def testBlogHomeTemplateUsed(self):
response = self.client.get('/')
self.assertTemplateUsed(response, 'blog/home.html')
 
def testBlogListInContext(self):
response = self.client.get('/')
self.assertTrue('blogpost_list' in response.context[0])
 
def testBlogPostInResponse(self):
response = self.client.get('/')
self.assertTrue(len(response.context[0]['blogpost_list']) > 0)
blog_post = response.context[0]['blogpost_list'][0]
self.assertContains(response, blog_post.title)

Nov

21

Today: The Plan

Today I plan to wield my mighty hammer of Django-making and meet my commitments at work. We're trying to follow an agile process now, which means we can all very precisely track how much I do or don't get done in a week.

I also plan to steal some time to add support for syntax highlighting in this blog. I'd like to post bits of the code that I've written to power this blog. This is to fulfill some of the meta/navel-gazing requirements.

Nov

20

Patience

I have big plans for this site.

I don't know what this is going to be like in the end but I'm hoping I can spruce this place up one feature at a time.

Nov

20

What what what

The stylish stripes along the left here are going to denote categories on the blog.

I'm hoping that I never need more than four categories. I'm also hoping that I can think of at least one. So far I think blue is the "meta navel-gazing" category.

Nov

20

Did I just blog?

How about that?

There's nothing here yet, but check this out: I have launched my own blog.

I've moved up from the Internet underclass to the lowest rung of Internet commoners. It feels good!

And bashing this thing together has made me late for work.