Fixed #10738 -- Fixed content type values for deferred and proxy models.

Models with deferred fields, or which are proxying for an existing
model, now return the same ContentType object as the real model they
reflect. Thanks to tomasz.elendt for help with fixing this.

git-svn-id: http://code.djangoproject.com/svn/django/trunk@10523 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
Malcolm Tredinnick 2009-04-11 13:22:32 +00:00
parent e12e0e18a4
commit 92824e7102
3 changed files with 28 additions and 8 deletions

View File

@ -7,7 +7,7 @@ class ContentTypeManager(models.Manager):
# Cache to avoid re-looking up ContentType objects all over the place. # Cache to avoid re-looking up ContentType objects all over the place.
# This cache is shared by all the get_for_* methods. # This cache is shared by all the get_for_* methods.
_cache = {} _cache = {}
def get_for_model(self, model): def get_for_model(self, model):
""" """
Returns the ContentType object for a given model, creating the Returns the ContentType object for a given model, creating the
@ -15,22 +15,25 @@ class ContentTypeManager(models.Manager):
for the same model don't hit the database. for the same model don't hit the database.
""" """
opts = model._meta opts = model._meta
while opts.proxy:
model = opts.proxy_for_model
opts = model._meta
key = (opts.app_label, opts.object_name.lower()) key = (opts.app_label, opts.object_name.lower())
try: try:
ct = self.__class__._cache[key] ct = self.__class__._cache[key]
except KeyError: except KeyError:
# Load or create the ContentType entry. The smart_unicode() is # Load or create the ContentType entry. The smart_unicode() is
# needed around opts.verbose_name_raw because name_raw might be a # needed around opts.verbose_name_raw because name_raw might be a
# django.utils.functional.__proxy__ object. # django.utils.functional.__proxy__ object.
ct, created = self.get_or_create( ct, created = self.get_or_create(
app_label = opts.app_label, app_label = opts.app_label,
model = opts.object_name.lower(), model = opts.object_name.lower(),
defaults = {'name': smart_unicode(opts.verbose_name_raw)}, defaults = {'name': smart_unicode(opts.verbose_name_raw)},
) )
self._add_to_cache(ct) self._add_to_cache(ct)
return ct return ct
def get_for_id(self, id): def get_for_id(self, id):
""" """
Lookup a ContentType by ID. Uses the same shared cache as get_for_model Lookup a ContentType by ID. Uses the same shared cache as get_for_model
@ -44,7 +47,7 @@ class ContentTypeManager(models.Manager):
ct = self.get(pk=id) ct = self.get(pk=id)
self._add_to_cache(ct) self._add_to_cache(ct)
return ct return ct
def clear_cache(self): def clear_cache(self):
""" """
Clear out the content-type cache. This needs to happen during database Clear out the content-type cache. This needs to happen during database
@ -53,7 +56,7 @@ class ContentTypeManager(models.Manager):
this gets called). this gets called).
""" """
self.__class__._cache.clear() self.__class__._cache.clear()
def _add_to_cache(self, ct): def _add_to_cache(self, ct):
"""Insert a ContentType into the cache.""" """Insert a ContentType into the cache."""
model = ct.model_class() model = ct.model_class()
@ -66,7 +69,7 @@ class ContentType(models.Model):
app_label = models.CharField(max_length=100) app_label = models.CharField(max_length=100)
model = models.CharField(_('python model class name'), max_length=100) model = models.CharField(_('python model class name'), max_length=100)
objects = ContentTypeManager() objects = ContentTypeManager()
class Meta: class Meta:
verbose_name = _('content type') verbose_name = _('content type')
verbose_name_plural = _('content types') verbose_name_plural = _('content types')

View File

@ -5,6 +5,7 @@ than using a new table of their own. This allows them to act as simple proxies,
providing a modified interface to the data from the base class. providing a modified interface to the data from the base class.
""" """
from django.contrib.contenttypes.models import ContentType
from django.db import models from django.db import models
@ -171,6 +172,12 @@ FieldError: Proxy model 'NoNewFields' contains model fields.
[<OtherPerson: barney>, <OtherPerson: fred>] [<OtherPerson: barney>, <OtherPerson: fred>]
>>> OtherPerson._default_manager.all() >>> OtherPerson._default_manager.all()
[<OtherPerson: barney>, <OtherPerson: wilma>] [<OtherPerson: barney>, <OtherPerson: wilma>]
# A proxy has the same content type as the model it is proxying for (at the
# storage level, it is meant to be essentially indistinguishable).
>>> ctype = ContentType.objects.get_for_model
>>> ctype(Person) is ctype(OtherPerson)
True
"""} """}

View File

@ -3,6 +3,7 @@ Regression tests for defer() / only() behavior.
""" """
from django.conf import settings from django.conf import settings
from django.contrib.contenttypes.models import ContentType
from django.db import connection, models from django.db import connection, models
class Item(models.Model): class Item(models.Model):
@ -91,5 +92,14 @@ u'c1'
>>> Leaf.objects.select_related().only("child__name", "second_child__name") >>> Leaf.objects.select_related().only("child__name", "second_child__name")
[<Leaf_Deferred_name_value: l1>] [<Leaf_Deferred_name_value: l1>]
Models instances with deferred fields should still return the same content
types as their non-deferred versions (bug #10738).
>>> ctype = ContentType.objects.get_for_model
>>> c1 = ctype(Item.objects.all()[0])
>>> c2 = ctype(Item.objects.defer("name")[0])
>>> c3 = ctype(Item.objects.only("name")[0])
>>> c1 is c2 is c3
True
""" """
} }