How to use django-tagging in your apps

So, as promised, here’s a high level summary of what I’ve found playing with django-tagging for the past week. If you’re interested, you can take a look at the documentation. There are also a number of blog posts out there that summarize the framework decently. This pair of posts is decent, as is this post.

Downloading django-tagging and installing it in Django

Getting the .egg is easy – just run `easy-install django-tagging`. After you go through the hassle of adding the .egg to the virtual environment’s various paths, things are pretty straightforward: add ‘tagging’ to INSTALLED_APPS in settings.py, and then run `django syncdb`.

This will create two tables – TAG and TAGGEDITEM. TAG is a simple table; besides the primary key, it just has a name field. This makes it easy to rename tags. TAGGEDITEM is used to maintain the many-to-many relationship between objects and tags. Its columns hold the foreign key to the TAG table, the tagged object’s primary key, and the tagged objects type.

It’s not strictly necessary to change the model classes to allow themselves to be tagged. However, if you want to add an attribute to your model classes to allow access to that object’s tags, have the following code run (put it at the bottom of a models.py file, in an __init__.py file…)

try:     tagging.register(ClassName) except tagging.AlreadyRegistered:     pass 


This will create a ‘tags’ property for that class.

Modifying the tags associated with an object

If you want, you can add a tagging.fields.TagField property to a model class. This acts as a CharField in the admin interface, but maintains the tag relationships behind the scene. TagField comes with default behaviour for splitting a string into tag names (dealing with spaces, commas, quotation marks, etc.).

(I noticed some strangeness when working with TagField – if I renamed a tag in the admin interface, the string held in a model object’s TagField would not be updated. I was fiddling around with a few things at the time, so I don’t know if this is a bug or something I didn’t configure correctly.)

In any case, I’m not sure a single text field is the best approach for adding and removing tags from an object. I’d prefer displaying a list of hyperlinked tags with a ‘remove’ link for each. A text field would still be used for adding new tags (ideally using AJAX for auto-complete). But your mileage may vary – what would you prefer?

Generic views and other goodies

tagging.views.tagged_object_list is a view for displaying a list of objects that have a specific tag. django-tagging will manage the list of associated tags, all you need to provide is the template and the model class.*

A number of handy template tags are also ready for use.

  • tagged_objects performs the same function as the previously mentioned generic view – retrieves all objects of a certain type* that have the specified tag.
  • tags_for_object retrieves all tags associated with the specified object.
  • tags_for_model retrieves all the tags associated with a specific model class.*
  • There’s also a tag_cloud_for_model tag. It retrieves all tags associated with the specified model* along with their font size weights. It’s fairly customizable, but by default it spreads the font sizes on a logarithmic scale. Some assembly is required; see the example below (CSS purists may want to avert their eyes from the dirty hack):

    {% load tagging_tags %} (...) {% tag_cloud_for_model tickets.Ticket as tags %} {% for tag in tags %}        {{ tag }}    {% endfor %} 

    “Nifty! With all these built-in features, adding tagging to Basie should be a cinch!”

    It shouldn’t be difficult, but there are a few bumps to deal with along the way:

    Tagging should not allow you to see objects that aren’t visible to you.

    The TAGGEDITEM table has no concept of projects or visibility in Basie. That mean when accessing all tickets tagged with ‘foo’, we need to filter the list depending on whether the tickets are visible to the current user. This means that the template tags cannot be used as is – creating our own versions of them should be possible though.

    We’ll also have to think about to implement ’seach for objects by tag’ functionality. What should the default behaviour be when clicking in a tag cloud? Only display objects for the current project? Display all the objects that the user can see?

    * It’s difficult to retrieve all objects associated with a tag, not just objects of a certain type.

    As you’ve probably noticed, django-tagging requires you to pass in a type when retrieving objects associated with a tag. But we don’t want to have a separate tagging system for Tickets, Wiki Pages, and so one. We want to use tagging to do things like associate milestones with their tickets, or wiki pages with blog posts. I’ve tried to come up with a number of ways around the problem, but each is less elegant that the last. Like before, we probably need to write our own queries and template tags. We don’t want to just ignore the type of tagged objects either – we’ll need that for creating hyperlinks.

    I’ve very much a django.db n00b, so if anyone has a suggestion, I’m all ears.

    The tagging app will need to know about the structure of other apps.

    This is more of an observation than a problem. Say we create a small tags/ app. You view the list of all objects with a given tag name – Tickets, Wiki Pages, whatever. The ‘tags’ app will need to know the URL mappings of the ‘tickets’ and ‘wiki’ apps for creating hyperlinks to the tagged objects. It’s just another dependency that will get in the way if other apps need to be refactored. Avoidable? Probably not, but still something to keep in mind as Basie grows.

    (Again, still a django novice. If there’s a D.R.Y. way of making one application use another’s URL mappings, let me know. Zuzel/Ted, does the dashboard do something similar?)

    That’s it from me for tonight this morning. If there’s anything I forgot, I’ll edit it in later on. Comments are welcome.

    EDIT: Rough estimation for deliverables (for each Wednesday):

    Oct. 28 – Main focus for this checkpoint is fixing Basie’s tests and doing reviews. But I want to go through django-tagging’s source and figure out how to extend it for Basie. If my other projects don’t give me any surprises, get a bare bones tagging application submitted to ReviewBoard – list all tags in the system, rename a tag, and list all objects with a certain tag.
    Nov. 04 – Create some template tags for modifying the tags attached to a generic object. Spamming ReviewBoard with changes for every object’s detail view would be a bad idea, so I’ll need an idea of what areas to add tagging support to first (for instance, it’s clear that tickets will need tagging, but something like IRC conversations might not be stable enough to add it to yet).
    Nov. 11 – By now the basics should be completed, so start looking at some small but significant features to add, like auto-complete. Around this checkpoint I’d like to post on the UCOSP blog to get some UI feedback. Not sure how many responses I’d get, but worth a shot.
    Nov. 18 – Code freeze is in two days, so all features should be implemented. From now on, just focused on testing and documentation (and getting everyone else’s changes through ReviewBoard.)

    No comments:

    Post a Comment