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

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.

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...

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

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 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.