Wed 15 September 2010 | -- (permalink)
The other day at Hacker Dojo we had an informal Django meetup for people to bring their projects and ask each other questions.
It went pretty well I think! Five people showed up, which I think is pretty near the limit of the number of people you can have collaborating before you have to break into subgroups.
One participant (Adelein) first proposed the meetup because she had some questions about many-to-many relationships between Django models. Because I also learned some stuff as we talked through her problem, I've decided to write it up here.
The Problem
Adelein is building a social app where developers can talk about what tools they use for different kinds of development. The app lets users create one or more toolboxes, each with one or more tools in it. The same tool (say Vim, or Apache) may be in any number of toolboxes, owned by any number of users. Users can also leave comments on tools to explain why and how they use them. Her Django models (aka database schema) looked something like this:
(Schema diagram purists: keep your criticisms of my graphs to yourself. I'm trying to quickly get a point across, not please a pedantic professor.)
She had a form that showed a tool, the toolbox you had put it in, and your comment on it. Her initial description of her problem was that when she saved the form the information about the tool in the toolbox was getting saved, but the comment wasn't. As it turned out though, this was more a symptom of a deeper problem. In the schema above, the Tool Note table really knows nothing about the relationship between tools and toolboxes. So there was no way for Django to know that it needed to update the note as well when saving a tool-toolbox relation.
The Solution
Since the UI for the app seemed to assume that there would be one note for each tool you put in your toolbox, we decided that it would be better to have the note refer to that relationship rather than to the tool itself. This meant cracking open the black box of the "thru-table" that Django would normally manage automatically for a many-to-many relationship.
(Sidebar: When I've had similar problems in the past, I've avoided Django's built in many-to-many feature because I didn't understand or trust the magic behind it. So instead I explicitly created and managed the table connecting individual tools with individual toolboxes, or in my case individual (but re-usable) pages with individual volumes. This meetup has given me a bit more confidence in the many-to-many feature though, so I'm pretty sure I'll use that next time.)
The new schema looked like this:
Nice! But how do we implement that in Django?
Here's the code to do it, based on a similar example from the excellent Django documentation:
~~~~ {lang="python"} class Tool(models.Model): name = models.CharField(max_length=64)
class Toolbox(models.Model): name = models.CharField(max_length=64) user = models.ForeignKey(User) #This is the magic part. We specify the through-table when #declaring the many-to-many field. #The name has to be given as a string because ToolboxTool #hasn't been declared yet at this point in the file. tools = models.ManyToManyField(Tool, through='ToolboxTool')
class ToolboxTool(models.Model): tool = models.ForeignKey(Tool) toolbox = models.ForeignKey(Toolbox) note=models.CharField(max_length=512)
class Meta: #This tells the DB that a tool can only be in a given toolbox once. #That last comma is to make it clear to python that we're making a tuple of #strings, inside another tuple. Otherwise it would collapse the outer #parentheses and try to parse each string as a separate tuple. Not what we #want. see http://docs.djangoproject.com/en/dev/ref/models/options/#unique-together unique_together = (('tool', 'toolbox'),) ~~~~
What's Next
If there's sufficient interest, I may sponsor more Django Hacking nights. I'd also like to mix it up a bit and have similar events on other tools I use or am interested in (Javascript, Vim, Haskell). As shown by this meeting, an informal event like this can be thrown together with very minimal effort over just a couple of days. Feel free to leave a comment if you'd like to participate in future ones, especially on any of the topics I've listed here.