Sep

8

Step 1: Disinterring and Reviving To Life

I shelved/buried my blog when I upgraded to a new slicehost linode six years ago. I didn't have it on github or gitlab, or on my personal dev machine, and it seemed to be completely purged from my little server. My last hope was the tarball labelled fullbackup.tar.gz in the home dir of the server. Oh, but by the way, my server was out of disk space and I was doing all this while on vacation in rural Nova Scotia, where high speed Internet is still a far-away dream.

Read on for how I unseized my old server, recovered my old blog code and data, and finally got it up and running.

More...

Sep

8

This Is An Old Blog

This blog started in the winter of 2008. I'd been in the industry for a few years and Django had reached 1.0. I figured it was time to make myself a blog, knowing, even then, that it was completely unnecessary. I just wanted the practice.

My last post (something not-that-great about Christmas) was, until now, from December 2012.

Anyway, a lot has changed in the software world. I've also caught up with a few things I wish I'd known 10 years ago.

So, lacking anything else to write about. I'm going to try and refurbish this blog and write about that.

Here's just a few of the things to address:

More...

Dec

24

Happy Holidays

As an atheist who celebrates Christmas, I don't see what all the fuss is about.

I mean, I think I do, but wouldn't it be nicer to find a way to live with these rituals rather than purge them all?

I don't want to have big, unavoidable religious hullabaloos in our public spaces, but I think we've already proven that we know how to take a major religious feast and turn it into a secular party for the rest of us. Nothing mandatory, but nothing reasonable excluded.

Isn't it time we had a crass, commercialized, mass market celebration of Holi?

More...

Jul

27

Things To Remember

  • When I'm with him: How scary it is when I think I've lost him*
  • When I think I've lost him: How in-your-face crazy it is when I'm with him

More...

Jul

7

Like QuerySets, but crazier

I have a crazy QuerySet idea.

Imagine we have this:

class Task(models.Model):
foo = models.CharField()
 
class TaskStatus(models.Model):
task = models.ForeignKey(Task)
user = models.ForeignKey('auth.User')
status = models.CharField(default='not-done')
 
class Meta:
unique_together = ('task', 'user')

Now imagine we're listing Task.objects.all() on a page. We need to show the user's status for each task, so we do something like this:

b_list = []
for a in A.objects.all():
b_list.append(a.b_set.get_or_create(user=request.user))

That sucks because it's doing an extra db query for each object. We can work around that problem, but this also sucks because we're sort of needlessly creating new records when we don't need them. If the TaskStatus is being created with a default status, and we know that's what it will be because we're creating it, couldn't we just assume that status if there's no TaskStatus object and skip the creation? Yes, we could probably do that too. But now our code's going to look a little messier. I won't both writing it out, but you can imagine, right?

Here's my crazy idea:

Write a manager method that returns a custom QuerySet subclass that will do something like this internally:

    def get_for_user(self, user);
tasks = Task.objects.all()
task_status = dict([(ts.pk, ts) for ts in self.get_query_set().filter(user=user)])
for task in tasks:
if task.pk in task_status:
task = task_status[task.pk]
else:
task = TaskStatus(task=task, user=user))
yield task

But, you know, done up so it behaved like a queryset instead of a plain iterator, where you could filter, exclude, get, etc.. So what you get is a queryset that includes all the objects you would normally find from the db, plus some freshly created (and un-saved) objects.

It seems like something like this could let you avoid a lot of db hits and extra db entries and would be transparent to the code that was using it.

Implementing this for general use would require some deep Query fiddling, but I think it could be manageable if I wrote it for this one application instead.

So crazy it might work, or is it the wrong amount of craziness?

Nov

30

URLs on multi-lingual sites: A solution

Let me tell you about two problems:

  1. Your site is localized into multiple languages, but the words in its URLs can only be in one language. For example, "/products/" isn't a great URL if you can't read English.

  2. Django's language switching is kind of crappy. You have to post to a view to change the language, and it's hard to expose all the site's translations to search engines for indexing.

Ladies and gentlemen, the solution is at hand: Transurlvania!

More...

Mar

19

Winter Walk

When we first moved to Nova Scotia we had one car and it wasn't very good. Winters were very isolating. They were colder and snowier too, I think.

I remember one time when my mom, my brother and I walked to the convenience store. It was a 4 kilometre walk along the shoulder of the backroad that was our only road. It was the day after a big snowstorm. The sky was blue and the wind was low. The snow made the walking harder. We didn't see anybody on the road until about halfway to the store, when we saw a strange angular structure crest the next hill. I remember it felt like it took a long time to come close. It was the home dune buggy contraption of Danny, the recluse who lived in the woods around there. I can't remember if we waved as he drove by.

What I remember is the silhouette of a black trapezoid rising up over the hill with white snow and spruce trees and blue sky all around.

Nov

24

Oh Apache

Man oh man.

I just resolved a stupid Apache issue that has been driving me nuts for over a year.

The problem was that one of the domains for this very blog, pocketuniverse.ca, wasn't working, while the other domain, www.pocketuniverse.ca, was working just fine. If you went to the later domain you saw the site, if you went to the former domain, you say Apache's default "It Works!" message. For some reason the default virtual host was being selected for pocketuniverse.ca even though it was listed as the ServerName of my blog's virtual host.

Read on for the exciting conclusion!

More...

Sep

3

Guess Who's Going On Vacation

oh boy oh boy oh boy!

Sep

1

Unhelpful Anger

Man, I just read this TorStar story about a hit and run and I'm just mad. I don't have a reasoned opinion. I want to beat in the brains of the driver of that car.

Here's what I think. Drivers: you blew it. You had this incredible luxury, and you were a bunch of stupid assholes about it so now it's done. All your licenses are revoked. If you want to drive your car, get in line for your new license. Only this time the tests will be harder. You'll have to demonstrate that you're the sort of person who should be entrusted with a giant killing machine. And if you want to drive your SUV, you're going to have to find four other people to rid with you or you'll be taken off the road. Those who do not need to drive four people around regularly are invited to buy smart cars. Or bicycles.

May

26

Bike Lanes in Toronto

Here I am. Driven by desperation to actually blog about something.

I built up a head of steam and ranted a bit about cars and bikes. Read on.

More...

May

12

PyCon - Day 2

So much for writing about day 2 at the end of day 2. This is my heavily abridged, well after the fact recap of Day 2 of Pycon.

(it's short but still to be big to get posted right on the home page, read on for more)

More...

Apr

14

Dear CRTC

Bell wants to start billing its resellers (like TekSavvy) extra for usage over 60GB per user. Basically, they'd like to bill TekSavvy customers the way they bill their own customers, removing what little opportunity for competition they were forced to offer. Here's the message I sent to the CRTC:

I strongly object to Bell's tariff application to introduce Usage Based Billing. I switched from Bell to one of its DSL competitors because Bell's service was poor and Rogers has at least as bad a reputation. It's completely unreasonable for Bell to force their own business decisions on the competitors. They would remove what little room their is for competition and hold us back even further.

Canadian broadband is already falling behind. It's critical not just that the CRTC act as a check against massive self-interested parties like Bell, but to move to create more room for actual competition. Bell does not have the best interests of Canadians at heart (nor, arguably, should it). We Canadians depend on the CRTC for that.

Please reject Bell's tariff application. Please support small, independent ISPs. Please help Canada reclaim its reputation as a telecommunications leader, instead of the backwater it is becoming.

You can let the CRTC know how you feel by filling out the form here: http://support.crtc.gc.ca/crtcsubmissionmu/forms/Telecom.aspx?lang=e. Select "Tariff" and then put "File Number # 8740-B2-200904989 - Bell Canada - TN 7181" in the subject line. TekSavvy sent me this email, but I think this is a big deal for all Canadians who care about the Internet.

More...

Mar

28

Schwaggy Dog

Dig All the Schwag:

Mar

28

PyCon - Day 1

Day 1 of PyCon was great. I should have started writing this yesterday because I'm already forgetting some of it.

Read on. This is a long post.

More...

Mar

27

A Noob In King Guido's Court

Here I am, somehow, impossibly, in Chicago and attending PyCon.

Somewhere in this crowd are other Djangonauts. I need to figure out how to find them.

Feb

22

Pictures! I hear these are popular with the kids.

My friends, I am pleased to present our two adorable kitties, Nacho and Mika.

I'm also pleased to present my new image attachment feature.

I'll put something wordier up in the Developer category at some point. This is just to celebrate (and confirm that it's really working).

Jan

3

Software Design Paralysis

Friends, I have come to an impasse.

There's a reason why I still don't have image embedding up on the site. Read on and help me!

More...

Jan

1

Good Deeds In The New Year

Read on for my sort of incoherent thoughts on philanthropy in the new year.

More...

Dec

23

Markdown w/ Pygments: Round 2

Dig back into my vast blog archive and you will find this post: I said it, I did it. Therein you will find my bumbling, ham-fisted solution for integrating pygment code highlighting into markdown.

I just wandered back past this code today and then stumbled across the actual right way to do this.

More...

Dec

23

What I did on the Monday before Christmas (yesterday)

My mom is in town. This is what she and I got up to yesterday:

More...

Dec

17

I know! I'll talk about video games!

I just listened to a short documentary on video games on the Search Engine podcast. It's by Jesse Brown, the host of the show, and it's a reflection on video games prompted by the recent death of a teen who ran away from home because his parents took away his XBox.

I really like this podcast, and I think Jesse Brown does a great job producing it. This documentary, however, was pretty bad. It was produced for one of CBC Radio's more mainstream shows and I felt that it pandered to the audience's sense of worry and fear about video game culture.

I wrote two comments on the Search Engine blog. Here they are.

More...

Dec

14

Run Sam Run (Attempt 2)

My roommate A is athletic. I, on the other hand follow the usual fitness habits of a programmer. For the past two Saturdays I have tried joining A when he goes on his 6 mile run. I'm falling a little short of six miles. But that's ok. I expected that. I made it a little further yesterday than I did last week. If I can get a little further each week I'll be happy.

Here's the nerd angle: I used my account on mapmyride to log my run. If I can manage to abuse my legs and lungs every week I should be able to come back and play with the fun Google Maps doodad.

Anyway, here's the doodad:

More...

Dec

11

On PIL and aggdraw

This isn't in "Developer Stuff" because it isn't related to Django and I don't want to pollute the Django Community Aggregator.

Along with my addition of the "public" field last night, I also refactored my clock rendering code to use the aggdraw library because it's compatible with PIL and it renders nice, anti-aliased lines.

The refactoring wasn't completely trivial, but it wasn't so bad, but I ran into a whole other mess when I tried compiling aggdraw on my slice.

More...

Dec

11

Testy

Anyone attempting to dip into my modest archive this morning would have seen an error message instead.

I discovered a bug in how my archive_month view was working and I fixed it. And in the process I opened a much worse bug. This is one of the classic narratives, along with "boy meets girl", "stranger comes to town" and the rest.

Let me tell you all about it...

More...

Dec

11

Private Posts and Custom Managers

I have all these cool ideas for how this blog is going to knock the socks off of sliced bread, or at least, all things since sliced bread. But to get there, I keep stumbling over all this pesky functionality I don't have.

In a way it's one big lesson in project management. All the little, round-down-to-zero-time tasks add up to an alarmingly non-zero number.

Today's addition is a public checkbox on the BlogPost object. That, plus the custom manager that goes with it. Plus the updates to the views. Plus the test to make sure it works. Plus the Django Evolution code.

Read on for more! I've got some racy opinions on custom managers in here that you won't want to miss!

More...

Dec

9

Cue The Sun

This is my post for showing off how the sun gets rendered during evening twilight. I am tweaking the publish date a bit because I -ahem- forgot to post this a little earlier.

Dec

9

Hickory Dickory Dock...

This is the nerdiest, least useful thing I've added to this site so far: an analog clock renderer!

Check it out: you get a regular clock with an hour hand and a minute hand, but you also get a sun indicator, to help you tell AM from PM. When it's daylight, the sun travels through the sky in nice happy yellow. When it's night time the sun travels around the lower half of the circle and gets drawn a dark blue. The extra fun part is that if it's near sunrise or sunset, the sun's colour is drawn from a gradient between the noon-day yellow and a bright red. I'll have to make a point of posting at dusk tomorrow.

I have no idea if this technique for representing time is at all intuitive, but I really enjoyed making it.

More...

Dec

8

Free Previews

My friends, I have something exciting (and completely unnecessary) in the works.

To pave the way for all the stuff I want to say about that, I've added previewing to my blog.

Let me tell you all about it, below the fold.

More...

Dec

7

Darwin Rears His Head

I'm using Django Evolution with this project because I knew I would need to muck with my models at some point. Ladies and gentlemen, that point has come!

In order to get categories set up I had to add a foreign key to the Category model from my BlogPost model. I've used the "./manage.py --hint" → "./manage.py --hint --execute" pattern before in other projects, but I decided to use Django Evolution's "Stored Evolutions" feature this time. It turned out to be painless, and I think i could smell rose petals and the absence of stress.

  • I ran ./manage.py --hint

  • I copied the lines between the "---------" bits into a file inside blog/evolutions. That looks like this:

from django_evolution.mutations import *
from django.db import models
 
MUTATIONS = [
AddField('BlogPost', 'category', models.ForeignKey, null=True, related_model='blog.Category')
]

I also created a __init__.py file in there.

  • I added this line to the __init__ file:

SEQUENCE = ['add_category_fk']

  • I got all my changes checked in, pushed the code up to my slice and ran ./manage.py evolve.
    Django Evolution found the evolution file, warned me that it might destroy my data or blot out the sun if I let it run, and then executed the evolution perfectly.

This is a simple case, I know, but this was still a lot easier than firing up psql. A tool that only helps with the easy 80% of the cases is still a useful tool, I think.

Dec

7

Category: Not Dead

Good evening, world!

I have been wracked by horrible guilt this past week, knowing that my new, youthful blog was lying fallow.

Here I am, making amends!

I've added categories to my blog. I now have the ability to separate my developer nonsense from my "personal" nonsense. (Note: "Personal" here is used not to mean "private" but rather, "less interesting to others".) I've also added category-based news feeds. Just go to the category page and subscribe to the feed from there.

Adding categories required new code in a bunch of places. It's not all very interesting. I split my blog post list displaying template code out into a sub-template called "_blogpost_list.html", because I'm using it in a few different templates now, but the code's basically the same.

I did create two new template tags. One is the archive nav code I had earlier, refactored:

@register.inclusion_tag('blog/_archive_nav.html')
def archive_nav(month=None, category=None):
queryset = BlogPost.objects.all()
if category:
queryset = queryset.filter(category=category)
return {'category': category,
'active_month': month,
'month_list': queryset.dates('pub_date', 'month')[::-1]
}

With the new archive nav code, I'm giving up on using the date_list object that the date-based generic views provide. date_list only lists the years that contain content. I was using a little template filter that would take a date representing a year, and return a list of all months with content in that year. It worked great when I always wanted to examine all the content, but it became a nuisance when I sometimes wanted to restrict it to only those blog posts that were in a certain category. I could have fiddled some more with the filter but I decided it would be tidier to write a complete template tag instead and leave date_list behind.

Here's the template:

<ul class="nav-bar">
<li>Archive: </li>
{% for month_date in month_list %}
<li{% ifequal month_date.date active_month %} class="active"{% endifequal %}>
{% with month_date|date:"F" as month_str %}
{% if category %}
<a href="{% url category_archive_month category_slug=category.slug,year=month_date.year,month=month_str.lower %}">
{{ month_str }} {{ month_date.year }}
</a>
{% else %}
<a href="{% url blog_archive_month year=month_date.year,month=month_str.lower %}">
{{ month_str }} {{ month_date.year }}
</a>
{% endif %}
{% endwith %}
</li>
{% endfor %}
</ul>

I discovered something new when I was updating my other templates to use archive_nav properly: Django templates don't support the "None" keyword. When I'm loading the archive nav inside the category detail page I need to pass the category, but I don't have an active month to pass. Here's what I settled on:

{% archive_nav "" category %}

It seems like there ought to be a nicer way to do this, but keyword arguments don't work with simple or inclusion tags and I can't use None.

Anyway, at the end of the day, we're left with an exciting new feature that every other blog already had without even thinking about it. I've got some great ideas on rotating, circular vehicle conveyance tools, as well.

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.