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 (
AddManipulator
andChangeManipulator
in oldforms,form_for_model
andform_for_instance
in newforms). -
Django’s serialization system can transform a
QuerySet
into 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.
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
.