Working with models, part 1
In order to do all the things it does, Django has to contain quite a bit of generic code that’s capable of working with pretty much any model or combination of models you want to throw at it, and has to be able to introspect those models to get information about their fields, their relationships and other useful tidbits. There are also plenty of real-world use cases where your code needs to be able to do the same sorts of things, and so knowing how Django does it — and how you can take advantage of the machinery it already provides — is an important step to becoming a fluent Django user. So in this and the next couple of tips we’ll take a look at some of those features, and how you can put them to use in your own applications.
Querying on any of multiple models
There are all sorts of cases where you need to be able to do a “generic” sort of lookup without knowing in advance what model you’ll be working with. If all you need is to have a view which displays a list of objects or detail of one particular object, Django’s generic views will be all you need, but that’s really a smaller subset of a larger problem; if you’re writing a template tag, for example, a generic view can’t help you.
One solution — the one used by generic views — is to write functions which take a QuerySet
as an argument, which neatly sidesteps the problem of “which model do I use” by shifting it onto the person who’s using the generic view. If you don’t mind doing that, it’s an easy and effective way to deal with this problem, since the QuerySet
API is the same no matter what model the QuerySet
comes from: you can always use filter
, get
, etc., no matter what model you’re looking at.
Another solution would be to make up a list of models and related information. If you know that the set of models you might be working with is going to be fairly small and relatively constant, this is workable; one common case is working with comments, where you can be fairly certain that you’ll only ever have to deal with the Comment
and FreeComment
models in django.contrib.comments
. For example, comment_utils relies on this to provide a consistent way to take a single comment and get all comments of that type posted by the same person; it uses a simple dictiionary which generates the appropriate lookup arguments:
kwarg_builder = { Comment: lambda c: { 'user__username__exact': c.user.username }, FreeComment: lambda c: { 'person_name__exact': c.person_name }, }
Then it’s a simple matter, given a comment object which might be from either model, to get all comments of that type from the same person:
comment_class = comment_obj.__class__ person_kwargs = kwarg_builder[comment_class](comment) comment_list = comment_class.objects.filter(**person_kwargs)
(the double asterisks, in case you’re wondering, are Python’s syntax for taking a dictionary and turning it into keyword arguments to a function)
But this still isn’t ideal; you need to maintain a hard-coded list of the models you want to work with, and the relevant information you need for each one. What we really want here is a generic solution.
Loading and querying on any model
The solution is to use Django’s model-loading system, which lives in django.db.models.loading
but has some useful things which get imported up into the django.db.models
namespace. The most important one, for what we’re considering here, is the function get_model()
, which takes two pieces of information — an “app label” and “model name” — and returns the model which matches them. The app label is simply the (normalized to lower-case) name of the application the model lives in, and the model name is the (also normalized to lower case) name of the model class. So, for example, the User
model in django.contrib.auth
has an app label of “auth” and a model name of “user”. Given this, you can get at the User
model without having to import it:
from django.db.models import get_model user_model = get_model('auth', 'user')
From there you can query on it just as if you’d imported it directly:
active_users = user_model.objects.filter(is_active=True)
The place where this trick really shines is cases where you know you’re going to work with some model, just not which model; as long as something, somewhere, can generate two strings — the app label and model name — get_model()
can get the model class for you. It’s somewhat conventional to pass these as a single string with a dot between them (so, in the case of the User
model, “auth.user”), and then just split it and pass to get_model()
for the result:
model_class = get_model(*model_string.split('.'))
(the single asterisk here is the Python syntax for taking a list and turning it into positional arguments to a function)
For a real-life example, the generic content-retrieval tags in template_utils
use this to open up the ability to dynamically retrieve objects from any model you have installed; one of the arguments to each of those tags is a string consisting of an app label, a dot and a model name, which means you can use any of these tags like so:
{% load generic_content %} {% get_latest_object comments.freecomment as latest_comment %}
As you might guess, this fetches the latest FreeComment
out of your database, by using get_model()
to fetch the model class, and then accesses the model’s default manager to do the query.
It’s worth emphasizing the “default manager” bit there; it’d be tempting to write the code like this, for example:
model_class = get_model(*model_str.split('.')) latest = model_class.objects.latest()
But this might end up crashing with an AttributeError
, because Django lets you define custom managers with any names you like, and doesn’t actually require that the default manager be named “objects”. When the default manager isn’t named “objects”, trying to access model_class.objects
simply won’t work, so you need to use the special attribute _default_manager
, which Django always sets to the default manager for the model regardless of what that manager is called:
model_class = get_model(*model_str.split('.')) latest = model_class._default_manager.latest()
In the examples above with hard-coded information about the comments models, this wasn’t a problem because we were deliberately limiting the set of possible models to just two, both of which have default managers named “objects”, but if you’re going to write truly generic code this is something you need to be aware of.
You’re soaking in it
The generic model-loading ability provided by get_model()
is used in several places in Django:
-
The admin application makes heavy use of it; you’ve probably noticed that the admin URLs tend to look like, say,
/auth/user/5/
. The admin app just reads the app label and model name right out of the URL, and then usesget_model()
to fetch the model class, which means that it can define a single view for a function like showing a list of objects or editing an object, and use it to work with any model. -
If you extend the authentication system with a custom profile model, the value you put into the
AUTH_PROFILE_MODULE
setting is a string containing a dot-separated app label and model name, which are passed toget_model()
to fetch your profile model. -
Django’s contenttypes framework stores information about your models keyed to — you guessed it — app label and model name, which lets you go from a
ContentType
object to a model class pretty easily (and, in fact,ContentType
objects have amodel_class()
method which does just that).
Other model-loading goodies
While get_model()
is really the most useful thing you’ll find in Django’s model-loading code, there are a few other things in there you’ll probably want to know about (all of which can be imported from django.db.models
):
-
get_models()
takes a Python module (the actual module, not a string naming it) and returns a list of all the model classes it contains. -
get_app()
takes an app label (of an application installed in your project) and returns the Python module containing that application’s models. -
get_apps()
takes no argument; it simply returns all of the Python modules in use in your project which contain models.
For an example of how these can be useful, consider the main page of the admin app, which displays a big list of applications and associdated models. That’s actually implemented as a template tag (in django.contrib.admin.templatetags.adminapplist
), and uses a pretty simple technique to build up the list: first it calls get_apps()
to get the list of modules with models, then loops through that list calling get_models()
on each one to get the list of its models and determine whether to display them.
There are a few more functions in Django’s model-loading module, but they’re really only useful for Django’s own internal use (and all of the functions listed above are actually implemented as methods on a class called AppCache
, which serves as a registry of your project’s available models).
Choose your weapon
Depending on exactly what you need to do, any of the methods above — taking a QuerySet
argument, building a list of models to work with, or using Django’s own generic model-loading code — can be useful, so keep them in mind for situations where you won’t always know in advance what model you’ll be working with. Tomorrow we’ll look at how to go from having the model class to getting useful information about it, so stay tuned.