259 lines
10 KiB
Plaintext
259 lines
10 KiB
Plaintext
|
==========================
|
||
|
The contenttypes framework
|
||
|
==========================
|
||
|
|
||
|
Django includes a "contenttypes" application that can track all of
|
||
|
the models installed in your Django-powered project, providing a
|
||
|
high-level, generic interface for working with your models.
|
||
|
|
||
|
Overview
|
||
|
========
|
||
|
|
||
|
At the heart of the contenttypes application is the ``ContentType``
|
||
|
model, which lives at
|
||
|
``django.contrib.contenttypes.models.ContentType``. Instances of
|
||
|
``ContentType`` represent and store information about the models
|
||
|
installed in your project, and new instances of ``ContentType`` are
|
||
|
automatically created whenever new models are installed.
|
||
|
|
||
|
Instances of ``ContentType`` have methods for returning the model
|
||
|
classes they represent and for querying objects from those models.
|
||
|
``ContentType`` also has a `custom manager`_ that adds methods for
|
||
|
working with ``ContentType`` and for obtaining instances of
|
||
|
``ContentType`` for a particular model.
|
||
|
|
||
|
Relations between your models and ``ContentType`` can also be used to
|
||
|
enable "generic" relationships between an instance of one of your
|
||
|
models and instances of any model you have installed.
|
||
|
|
||
|
.. _custom manager: ../model-api/#custom-managers
|
||
|
|
||
|
Installing the contenttypes framework
|
||
|
=====================================
|
||
|
|
||
|
The contenttypes framework is included in the default
|
||
|
``INSTALLED_APPS`` list created by ``django-admin.py startproject``,
|
||
|
but if you've removed it or if you manually set up your
|
||
|
``INSTALLED_APPS`` list, you can enable it by adding
|
||
|
``'django.contrib.contenttypes'`` to your ``INSTALLED_APPS`` setting.
|
||
|
|
||
|
It's generally a good idea to have the contenttypes framework
|
||
|
installed; several of Django's other bundled applications require it:
|
||
|
|
||
|
* The admin application uses it to log the history of each object
|
||
|
added or changed through the admin interface.
|
||
|
|
||
|
* Django's `authentication framework`_ uses it to tie user permissions
|
||
|
to specific models.
|
||
|
|
||
|
* Django's comments system (``django.contrib.comments``) uses it to
|
||
|
"attach" comments to any installed model.
|
||
|
|
||
|
.. _authentication framework: ../authentication/
|
||
|
|
||
|
The ``ContentType`` model
|
||
|
=========================
|
||
|
|
||
|
Each instance of ``ContentType`` has three fields which, taken
|
||
|
together, uniquely describe an installed model:
|
||
|
|
||
|
``app_label``
|
||
|
The name of the application the model is part of. This is taken from
|
||
|
the ``app_label`` attribute of the model, and includes only the *last*
|
||
|
part of the application's Python import path;
|
||
|
"django.contrib.contenttypes", for example, becomes an ``app_label``
|
||
|
of "contenttypes".
|
||
|
|
||
|
``model``
|
||
|
The name of the model class.
|
||
|
|
||
|
``name``
|
||
|
The human-readable name of the model. This is taken from
|
||
|
`the verbose_name attribute`_ of the model.
|
||
|
|
||
|
Let's look at an example to see how this works. If you already have
|
||
|
the contenttypes application installed, and then add `the sites
|
||
|
application`_ to your ``INSTALLED_APPS`` setting and run ``manage.py
|
||
|
syncdb`` to install it, the model ``django.contrib.sites.models.Site``
|
||
|
will be installed into your database. Along with it a new instance
|
||
|
of ``ContentType`` will be created with the following values:
|
||
|
|
||
|
* ``app_label`` will be set to ``'sites'`` (the last part of the Python
|
||
|
path "django.contrib.sites").
|
||
|
|
||
|
* ``model`` will be set to ``'site'``.
|
||
|
|
||
|
* ``name`` will be set to ``'site'``.
|
||
|
|
||
|
.. _the verbose_name attribute: ../model-api/#verbose_name
|
||
|
.. _the sites application: ../sites/
|
||
|
|
||
|
Methods on ``ContentType`` instances
|
||
|
====================================
|
||
|
|
||
|
Each ``ContentType`` instance has methods that allow you to get from a
|
||
|
``ContentType`` instance to the model it represents, or to retrieve objects
|
||
|
from that model:
|
||
|
|
||
|
``get_object_for_this_type(**kwargs)``
|
||
|
Takes a set of valid `lookup arguments`_ for the model the
|
||
|
``ContentType`` represents, and does `a get() lookup`_ on that
|
||
|
model, returning the corresponding object.
|
||
|
|
||
|
``model_class()``
|
||
|
Returns the model class represented by this ``ContentType``
|
||
|
instance.
|
||
|
|
||
|
For example, we could look up the ``ContentType`` for the ``User`` model::
|
||
|
|
||
|
>>> from django.contrib.contenttypes.models import ContentType
|
||
|
>>> user_type = ContentType.objects.get(app_label="auth", model="user")
|
||
|
>>> user_type
|
||
|
<ContentType: user>
|
||
|
|
||
|
And then use it to query for a particular ``User``, or to get access
|
||
|
to the ``User`` model class::
|
||
|
|
||
|
>>> user_type.model_class()
|
||
|
<class 'django.contrib.auth.models.User'>
|
||
|
>>> user_type.get_object_for_this_type(username='Guido')
|
||
|
<User: Guido>
|
||
|
|
||
|
Together, ``get_object_for_this_type`` and ``model_class`` enable two
|
||
|
extremely important use cases:
|
||
|
|
||
|
1. Using these methods, you can write high-level generic code that
|
||
|
performs queries on any installed model -- instead of importing and
|
||
|
using a single specific model class, you can pass an ``app_label``
|
||
|
and ``model`` into a ``ContentType`` lookup at runtime, and then
|
||
|
work with the model class or retrieve objects from it.
|
||
|
|
||
|
2. You can relate another model to ``ContentType`` as a way of tying
|
||
|
instances of it to particular model classes, and use these methods
|
||
|
to get access to those model classes.
|
||
|
|
||
|
Several of Django's bundled applications make use of the latter
|
||
|
technique. For example, `the permissions system`_ in Django's
|
||
|
authentication framework uses a ``Permission`` model with a foreign
|
||
|
key to ``ContentType``; this lets ``Permission`` represent concepts
|
||
|
like "can add blog entry" or "can delete news story".
|
||
|
|
||
|
.. _lookup arguments: ../db-api/#field-lookups
|
||
|
.. _a get() lookup: ../db-api/#get-kwargs
|
||
|
.. _the permissions system: ../authentication/#permissions
|
||
|
|
||
|
The ``ContentTypeManager``
|
||
|
--------------------------
|
||
|
|
||
|
``ContentType`` also has a custom manager, ``ContentTypeManager``,
|
||
|
which adds the following methods:
|
||
|
|
||
|
``clear_cache()``
|
||
|
Clears an internal cache used by ``ContentType`` to keep track of which
|
||
|
models for which it has created ``ContentType`` instances. You probably
|
||
|
won't ever need to call this method yourself; Django will call it
|
||
|
automatically when it's needed.
|
||
|
|
||
|
``get_for_model(model)``
|
||
|
Takes either a model class or an instance of a model, and returns the
|
||
|
``ContentType`` instance representing that model.
|
||
|
|
||
|
The ``get_for_model`` method is especially useful when you know you
|
||
|
need to work with a ``ContentType`` but don't want to go to the
|
||
|
trouble of obtaining the model's metadata to perform a manual lookup::
|
||
|
|
||
|
>>> from django.contrib.auth.models import User
|
||
|
>>> user_type = ContentType.objects.get_for_model(User)
|
||
|
>>> user_type
|
||
|
<ContentType: user>
|
||
|
|
||
|
Generic relations
|
||
|
=================
|
||
|
|
||
|
Adding a foreign key from one of your own models to ``ContentType``
|
||
|
allows your model to effectively tie itself to another model class, as
|
||
|
in the example of the ``Permission`` model above. But it's possible to
|
||
|
go one step further and use ``ContentType`` to enable truly generic
|
||
|
(sometimes called "polymorphic") relationships between models.
|
||
|
|
||
|
A simple example is a tagging system, which might look like this::
|
||
|
|
||
|
from django.db import models
|
||
|
from django.contrib.contenttypes.models import ContentType
|
||
|
from django.contrib.contenttypes import generic
|
||
|
|
||
|
class TaggedItem(models.Model):
|
||
|
tag = models.SlugField()
|
||
|
content_type = models.ForeignKey(ContentType)
|
||
|
object_id = models.PositiveIntegerField()
|
||
|
content_object = generic.GenericForeignKey('content_type', 'object_id')
|
||
|
|
||
|
def __unicode__(self):
|
||
|
return self.tag
|
||
|
|
||
|
A normal ``ForeignKey`` can only "point to" one other model, which
|
||
|
means that if the ``TaggedItem`` model used a ``ForeignKey`` it would have to
|
||
|
choose one and only one model to store tags for. The contenttypes
|
||
|
application provides a special field type --
|
||
|
``django.contrib.contenttypes.generic.GenericForeignKey`` -- which
|
||
|
works around this and allows the relationship to be with any
|
||
|
model. There are three parts to setting up a ``GenericForeignKey``:
|
||
|
|
||
|
1. Give your model a ``ForeignKey`` to ``ContentType``.
|
||
|
|
||
|
2. Give your model a field that can store a primary-key value from the
|
||
|
models you'll be relating to. (For most models, this means an
|
||
|
``IntegerField`` or ``PositiveIntegerField``.)
|
||
|
|
||
|
3. Give your model a ``GenericForeignKey``, and pass it the names of
|
||
|
the two fields described above. If these fields are named
|
||
|
"content_type" and "object_id", you can omit this -- those are the
|
||
|
default field names ``GenericForeignKey`` will look for.
|
||
|
|
||
|
This will enable an API similar to the one used for a normal ``ForeignKey``;
|
||
|
each ``TaggedItem`` will have a ``content_object`` field that returns the
|
||
|
object it's related to, and you can also assign to that field or use it when
|
||
|
creating a ``TaggedItem``::
|
||
|
|
||
|
>>> from django.contrib.models.auth import User
|
||
|
>>> guido = User.objects.get(username='Guido')
|
||
|
>>> t = TaggedItem(content_object=guido, tag='bdfl')
|
||
|
>>> t.save()
|
||
|
>>> t.content_object
|
||
|
<User: Guido>
|
||
|
|
||
|
Reverse generic relations
|
||
|
-------------------------
|
||
|
|
||
|
If you know which models you'll be using most often, you can also add
|
||
|
a "reverse" generic relationship to enable an additional API. For example::
|
||
|
|
||
|
class Bookmark(models.Model):
|
||
|
url = models.URLField()
|
||
|
tags = generic.GenericRelation(TaggedItem)
|
||
|
|
||
|
``Bookmark`` instances will each have a ``tags`` attribute, which can
|
||
|
be used to retrieve their associated ``TaggedItems``::
|
||
|
|
||
|
>>> b = Bookmark('http://www.djangoproject.com/')
|
||
|
>>> b.save()
|
||
|
>>> t1 = TaggedItem(content_object=b, tag='django')
|
||
|
>>> t1.save()
|
||
|
>>> t2 = TaggedItem(content_object=b, tag='python')
|
||
|
>>> t2.save()
|
||
|
>>> b.tags.all()
|
||
|
[<TaggedItem: django>, <TaggedItem: python>]
|
||
|
|
||
|
If you don't add the reverse relationship, you can do the lookup manually::
|
||
|
|
||
|
>>> b = Bookmark.objects.get(url='http://www.djangoproject.com/)
|
||
|
>>> bookmark_type = ContentType.objects.get_for_model(b)
|
||
|
>>> TaggedItem.objects.filter(content_type__pk=bookmark_type.id,
|
||
|
... object_id=b.id)
|
||
|
[<TaggedItem: django>, <TaggedItem: python>]
|
||
|
|
||
|
Note that if you delete an object that has a ``GenericRelation``, any objects
|
||
|
which have a ``GenericForeignKey`` pointing at it will be deleted as well. In
|
||
|
the example above, this means that if a ``Bookmark`` object were deleted, any
|
||
|
``TaggedItem`` objects pointing at it would be deleted at the same time.
|