Working with models, part 2

Published November 4, 2007. Filed under: Django.

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:

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.

You got _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 _meta:

>>> 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 Field object):

>>> 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 django.db.models.fields.FieldDoesNotExist.

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 Meta:

>>> opts.ordering
('username',)

If you’ve ever needed a way to get at the verbose_name and 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 app_label and module_name:

>>> 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.fields, _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 CharField:

>>> 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 Options and RelatedObject.