[A comment on a recent blog entry of mine](http://www.b-list.org/weblog/2008/feb/25/managers/#c63422) asked about a setup where one model has foreign keys pointing at it from several others, and how to write a manager which could attach to any of those models and query seamlessly on the relation regardless of what it's named.
This is a simple example of how to do it: in this case, both `Movie` and `Restaurant` have foreign keys to `Review`, albeit under different names. However, they both use `ReviewedObjectManager` to provide a method for querying objects whose review assigned a certain rating; this works because an instance of `ReviewedObjectManager` "knows" what model it's attached to, and can introspect that model, using [Django's model-introspection API](http://www.b-list.org/weblog/2007/nov/04/working-models/), to find out the correct name to use for the relation, and then use that to perform the query.
Using model introspection in this fashion is something of an advanced topic, but is extremely useful for writing flexible, reusable code.
**Also**, note that the introspection cannot be done in the manager's `__init__()` method -- at that point, `self.model` is still `None` (it won't be filled in with the correct model until a bit later) -- so it's necessary to come up with some way to defer the introspection. In this case, I'm doing it in a method that's called when the relation name is first needed, and which caches the result in an attribute.
- managers
- models
- introspection
When I initially set up my blog, I put together the archives with URL patterns like so:
* `/weblog/2007/` goes to `archive_year`
* `/weblog/2007/08/` goes to `archive_month`
* `/weblog/2007/08/24/` goes to `archive_day`
* `/weblog/2007/08/24/some-slug` goes to `object_detail`
The same patterns held for links, only the prefix was `/links/` instead of `/weblog/`.
For a forthcoming redesign/rewrite, I'm switching to using abbreviated month names (e.g., "aug", "sep", "oct", etc.) in the URLs, which means I need to redirect from the old-style URLs to the new. This snippet is the solution I hit upon. Two things are notable here:
1. Each one of these views uses [reverse()](http://www.djangoproject.com/documentation/url_dispatch/#reverse), called with the appropriate arguments, to generate the URL to redirect to. This means URLs don't have to be hard-coded in.
2. Each view takes an argument -- `object_type` -- which is used to generate the view name to pass to `reverse`, meaning that only one set of redirect views had to be written to handle both entries and links.
This is just one of many handy tricks `reverse` can do :)
**Before using this snippet**, please note that it's largely been superseded by [comment_utils](http://code.google.com/p/django-comment-utils/), which includes a more featureful and extensible version of this system, particularly with respect to additional moderation options and useful things like email notifications of comments.
Once upon a time I hacked the copy of `django.contrib.comments` I'm using on my blog, so that I could have comments get set to `is_public=False` if posted more than 30 days after the entry's publication, and to add Akismet spam filtering. I've regretted it ever since, because it's made upgrading my copy of Django a pain.
So here's an improved version which doesn't require hacking directly on Django. To use it, you'll need to do a few things:
1. Grab the [Python Akismet module](http://www.voidspace.org.uk/python/modules.shtml#akismet) and install it somewhere on your server.
2. In your settings file, add `AKISMET_API_KEY`, and make sure its value is a valid Akismet key. If you don't have an Akismet key, you can [get one at wordpress.com](http://wordpress.com/api-keys/).
3. Put this code -- both the function and the dispatcher calls -- somewhere in your project that's _guaranteed_ to be imported early (until this code is executed, the moderation function won't be set up to listen for comments posting).
To have comments on a certain type of object (say, weblog entries) automatically go into moderation when the object reaches a certain age, define a method on that object's model called `comments_open`, and have it return `False` when comments should be auto-moderated.
- akismet
- comments
- moderation
I'm a big fan of Markdown, and often set up models to automatically apply it to certain fields before saving. But that's not really flexible, because if I then distribute the code someone else might want to use reStructuredText or Textile or whatever, and then they have to hack my code.
So here's a function which looks for a setting called `MARKUP_FILTER` and, based on what it finds there (see the docstring for what it looks at), chooses a text-to-HTML conversion function and applies it to a piece of text. Since Textile, Markdown and reStructuredText all support various useful options, it has the ability to pick up arbitrary keyword arguments and pass them through.
- markup
- markdown
- textile
- restructuredtext
This is part of the user-registration code used on this site (see [the django-registration project on Google Code](http://code.google.com/p/django-registration/) for full source code), and shows a couple of interesting tricks you can do with manager methods.
In this case there's a separate `RegistrationProfile` model used to store an activation key and expiration time for a new user's account, and the manager provides a couple of useful methods for working with them: `create_inactive_user` creates a new user and a new `RegistrationProfile` and emails an activation link, `activate_user` knows how to activate a user's account, and `delete_expired_users` knows how to clean out old accounts that were never activated.
Putting this code into custom manager methods helps a lot with re-use, because it means that this code doesn't have to be copied over into different views for each site which uses registration, and also makes more sense in terms of design, because these are methods which need to "know about" the model and work with it, and so they belong in a place close to the model.