Working with models, part 2
Now that we’ve got a handle on how to generically load models without necessarily knowing in advance which models, it’s time to start looking at interesting things we can do with them once we’ve got them. There are lots of uses for code which can introspect any model and extract information from it, some of which you’ve probably already seen:
- The Django admin interface introspects model classes to determine how to display and edit them.
Shortcuts in both the old and new forms systems exist for generating forms automatically from a model (
Django’s serialization system can transform a
QuerySetinto JSON or XML, and back again into objects in your database.
So how can your code do the same sorts of things?
A small warning
We’re going to be digging into some of Django’s own internal APIs here, which isn’t a bad thing to do but does come with an important warning: be careful and realize that these APIs exist for Django’s convenience, not yours, and so are subject to change if Django’s internals need something to work differently.
_meta all over my model!
Every Django model class and every instance of every Django model class has an attribute on it named
_meta, which is (as the name implies) where Django stores meta-information about the model for its own use, and which you can access to get at the same information. This attribute is an instance of the class
django.db.models.options.Options, and it contains all sorts of useful things; if you’ve ever wondered, for example, where all the options you specify in a model’s inner “Meta” class end up, this is the answer.
One of the more important bits of information you can get out of
_meta is a list of the model’s fields; for example:
>>> from django.contrib.auth.models import User >>> opts = User._meta >>> opts.fields [<django.db.models.fields.AutoField object at 0xebd4d0>, <django.db.models.fields.CharField object at 0xe77530>, <django.db.models.fields.CharField object at 0xe62490>, <django.db.models.fields.CharField object at 0xe4a630>, <django.db.models.fields.EmailField object at 0xe7ac10>, <django.db.models.fields.CharField object at 0xe7cc30>, <django.db.models.fields.BooleanField object at 0xe82b50>, <django.db.models.fields.BooleanField object at 0xe88ad0>, <django.db.models.fields.BooleanField object at 0xe8da30>, <django.db.models.fields.DateTimeField object at 0xe93970>, <django.db.models.fields.DateTimeField object at 0xe96910>]
These are the actual
Field objects on the model, which may not be the most useful thing if you’re just poking around in a Python interpreter. But each one of them has a name, and you can easily print a list of those names:
>>> [f.name for f in opts.fields] ['id', 'username', 'first_name', 'last_name', 'email', 'password', 'is_staff', 'is_active', 'is_superuser', 'last_login', 'date_joined']
Many-to-many relations are handled as a special case, and so don’t show up in this list; you can access them via the
many_to_many attribute of
>>> opts.many_to_many [<django.db.models.fields.related.ManyToManyField object at 0xe988f0>, <django.db.models.fields.related.ManyToManyField object at 0xea1810>] >>> [f.name for f in opts.many_to_many] ['groups', 'user_permissions']
If you want to know whether a model has a field with a specific name, you can use
get_field() to find out (and to get the
>>> opts.get_field('email') <django.db.models.fields.EmailField object at 0xe7ac10>
If the field you’re looking for doesn’t exist, this will raise
I mentioned that this is where the options from the inner
Meta class end up, and for the most part you can just access them by the names of the attributes used in
>>> opts.ordering ('username',)
If you’ve ever needed a way to get at the
verbose_name_plural of a model, they’re in
_meta as well, but if they’ve been marked for translation they’ll come back as proxy objects; they won’t actually produce a string until forced to, so that the end result can change based on the active translation:
>>> opts.verbose_name <django.utils.functional.__proxy__ object at 0xea9710> >>> unicode(opts.verbose_name) u'user' >>> unicode(opts.verbose_name_plural) u'users'
The app label and model name used for
get_model() (which we looked at yesterday) also live in here, as attributes called
>>> opts.app_label 'auth' >>> opts.module_name 'user'
Database and relational information
Just being able to poke around and see things like lists of fields and attributes from the
Meta class is handy (and there are plenty of uses for this; that’s how Django auto-generates forms from models, for example), but there’s a lot more lurking in
_meta that can be put to use. For example, if you need to execute some custom SQL and bypass Django’s ORM,
_meta has all the information you’ll need on the appropriate tables and column names:
>>> opts.db_table 'auth_user' >>> opts.get_field('email').column 'email'
You can also pull out the names of the join tables used for many-to-many relationships (although they don’t show up in
_meta.get_field() will locate many-to-many fields for you), as well as the column used for this model’s “side” of the relationship and the column used for the other model’s side::
>>> group_field = opts.get_field('groups') >>> group_field.m2m_db_table() 'auth_user_groups' >>> group_field.m2m_column_name() 'user_id' >>> group_field.m2m_reverse_name() 'group_id'
And if you need to know the name of the field which serves as the model’s primary key, that’s also available:
>>> opts.pk <django.db.models.fields.AutoField object at 0xebd4d0> >>> opts.pk.name 'id'
Working from attributes and methods like these, it’s easy to start building up code which can take any model and execute custom queries against it. You can also introspect to see if a model has a particular type of field you’re interested in; if, for example, you wanted to do something special if the model has a
DateTimeField, you can find out:
>>> from django.db.models import DateTimeField >>> opts.has_field_type(DateTimeField) True
Another useful trick is pulling out a list of all the foreign keys which point at the model:
>>> opts.get_all_related_objects() [<RelatedObject: coltrane:entry related to author>, <RelatedObject: coltrane:link related to posted_by>, <RelatedObject: admin:logentry related to user>, <RelatedObject: auth:message related to user>, <RelatedObject: registration:registrationprofile related to user>, <RelatedObject: comments:comment related to user>, <RelatedObject: comments:karmascore related to user>, <RelatedObject: comments:moderatordeletion related to user>, <RelatedObject: comments:userflag related to user>]
Each of these is an instance of
django.db.models.related.RelatedObject, which is a class that stores information about a foreign-key relationship; as you can see from what’s spit out by
__repr__ in the shell, you can use the
RelatedObject to get at the model with the foreign key and find out the name of the foreign-key field it’s using. There’s a corresponding method for retrieving many-to-many relationships,
get_all_related_many_to_many_objects(), but in the sample project I’m using right now there aren’t any models with many-to-many relationships pointing at
User and so it returns an empty list.
Forms and validation
Every field on a model carries around some information about what type of form field should be used to represent it in automatically-generated forms; the method
formfield(), for example, will return a newforms
Field object suitable for representing that field in a form:
>>> email_field = opts.get_field('email') >>> email_field.formfield() <django.newforms.fields.EmailField object at 0x18a86b0> >>> groups_field = opts.get_field('groups') >>> groups_field.formfield() <django.newforms.models.ModelMultipleChoiceField object at 0x18b34b0>
You can also access validation-related parameters of fields; for example, to find out the maximum length of a
>>> opts.get_field('username').max_length 30
For fields with choices, there’s also a
choices attribute which will return the full set of choices, and fields with default values will have an attribute
default. That one’s worth looking at briefly, because it might return an appropriate value or it might return a function which generates the default value; for example, the
date_joined field on
User is specified like so:
date_joined = models.DateTimeField(_('date joined'), default=datetime.datetime.now)
In this case,
datetime.datetime.now is a function, and the intent is for it to be called each time a
User is created, which means that
default returns the function:
>>> opts.get_field('date_joined').default <built-in method now of type object at 0xb88720>
You can use Python’s built-in function
callable() to test for this:
>>> date_joined_field = opts.get_field('date_joined') >>> if callable(date_joined_field.default): ... print date_joined_field.default() ... else: ... print date_joined_field.default ... 2007-11-04 00:50:32.114722
And so much more
This really just scratches the surface of what you can find in
_meta and on the
Field objects and other things you can pull out of it, but hopefully you’ve already got an idea of how useful this can be for generically introspecting a model class and figuring out how to work with it; being able to pull out all the same information Django uses about database tables and columns, ordering, form fields and relationships makes that sort of thing almost trivially easy.
Because it’s an internal API and could change in the future (though for many parts of
_meta, this would also require major reworking of other parts of Django, so it’s not necessarily likely that it’ll change), there’s not any official documentation for
_meta right now, but using
dir() in a Python shell will show you all the attributes of
_meta and let you drill down into attributes of the things it contains; python’s built-in
help() function will also let you see any docstrings attached to things in
_meta, as well as classes like