Fixed #24099 -- Removed contenttype.name deprecated field

This finsishes the work started on #16803.
Thanks Simon Charette, Tim Graham and Collin Anderson for the
reviews.
This commit is contained in:
Claude Paroz 2015-01-08 16:58:23 +01:00 committed by Markus Holtermann
parent 374c2419e5
commit b4ac232907
15 changed files with 110 additions and 49 deletions

View File

@ -69,7 +69,7 @@
{% endif %} {% endif %}
<br/> <br/>
{% if entry.content_type %} {% if entry.content_type %}
<span class="mini quiet">{% filter capfirst %}{% trans entry.content_type.name %}{% endfilter %}</span> <span class="mini quiet">{% filter capfirst %}{{ entry.content_type }}{% endfilter %}</span>
{% else %} {% else %}
<span class="mini quiet">{% trans 'Unknown content' %}</span> <span class="mini quiet">{% trans 'Unknown content' %}</span>
{% endif %} {% endif %}

View File

@ -110,8 +110,9 @@ def create_permissions(app_config, verbosity=2, interactive=True, using=DEFAULT_
for perm in perms: for perm in perms:
if len(perm.name) > permission_name_max_length: if len(perm.name) > permission_name_max_length:
raise exceptions.ValidationError( raise exceptions.ValidationError(
"The verbose_name of %s is longer than %s characters" % ( "The verbose_name of %s.%s is longer than %s characters" % (
perm.content_type, perm.content_type.app_label,
perm.content_type.model,
verbose_name_max_length, verbose_name_max_length,
) )
) )

View File

@ -568,7 +568,7 @@ class PermissionTestCase(TestCase):
models.Permission._meta.verbose_name = "some ridiculously long verbose name that is out of control" * 5 models.Permission._meta.verbose_name = "some ridiculously long verbose name that is out of control" * 5
six.assertRaisesRegex(self, exceptions.ValidationError, six.assertRaisesRegex(self, exceptions.ValidationError,
"The verbose_name of permission is longer than 244 characters", "The verbose_name of auth.permission is longer than 244 characters",
create_permissions, auth_app_config, verbosity=0) create_permissions, auth_app_config, verbosity=0)

View File

@ -52,24 +52,20 @@ class LoadDataWithNaturalKeysAndMultipleDatabasesTestCase(TestCase):
default_objects = [ default_objects = [
ContentType.objects.db_manager('default').create( ContentType.objects.db_manager('default').create(
model='examplemodela', model='examplemodela',
name='example model a',
app_label='app_a', app_label='app_a',
), ),
ContentType.objects.db_manager('default').create( ContentType.objects.db_manager('default').create(
model='examplemodelb', model='examplemodelb',
name='example model b',
app_label='app_b', app_label='app_b',
), ),
] ]
other_objects = [ other_objects = [
ContentType.objects.db_manager('other').create( ContentType.objects.db_manager('other').create(
model='examplemodelb', model='examplemodelb',
name='example model b',
app_label='app_b', app_label='app_b',
), ),
ContentType.objects.db_manager('other').create( ContentType.objects.db_manager('other').create(
model='examplemodela', model='examplemodela',
name='example model a',
app_label='app_a', app_label='app_a',
), ),
] ]

View File

@ -1,7 +1,6 @@
from django.apps import apps from django.apps import apps
from django.db import DEFAULT_DB_ALIAS, router from django.db import DEFAULT_DB_ALIAS, router
from django.db.migrations.loader import is_latest_migration_applied from django.db.migrations.loader import is_latest_migration_applied
from django.utils.encoding import smart_text
from django.utils import six from django.utils import six
from django.utils.six.moves import input from django.utils.six.moves import input
@ -50,7 +49,6 @@ def update_contenttypes(app_config, verbosity=2, interactive=True, using=DEFAULT
cts = [ cts = [
ContentType( ContentType(
name=smart_text(model._meta.verbose_name_raw),
app_label=app_label, app_label=app_label,
model=model_name, model=model_name,
) )

View File

@ -0,0 +1,41 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
def add_legacy_name(apps, schema_editor):
ContentType = apps.get_model('contenttypes', 'ContentType')
for ct in ContentType.objects.all():
try:
ct.name = apps.get_model(ct.app_label, ct.model)._meta.object_name
except:
ct.name = ct.model
ct.save()
class Migration(migrations.Migration):
dependencies = [
('contenttypes', '0001_initial'),
]
operations = [
migrations.AlterModelOptions(
name='contenttype',
options={'verbose_name': 'content type', 'verbose_name_plural': 'content types'},
),
migrations.AlterField(
model_name='contenttype',
name='name',
field=models.CharField(max_length=100, null=True),
),
migrations.RunPython(
migrations.RunPython.noop,
add_legacy_name,
),
migrations.RemoveField(
model_name='contenttype',
name='name',
),
]

View File

@ -1,11 +1,13 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import warnings
from django.apps import apps from django.apps import apps
from django.db import models from django.db import models
from django.db.utils import OperationalError, ProgrammingError from django.db.utils import OperationalError, ProgrammingError
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django.utils.encoding import force_text from django.utils.deprecation import RemovedInDjango20Warning
from django.utils.encoding import python_2_unicode_compatible from django.utils.encoding import force_text, python_2_unicode_compatible
class ContentTypeManager(models.Manager): class ContentTypeManager(models.Manager):
@ -34,6 +36,14 @@ class ContentTypeManager(models.Manager):
key = (opts.app_label, opts.model_name) key = (opts.app_label, opts.model_name)
return self.__class__._cache[self.db][key] return self.__class__._cache[self.db][key]
def create(self, **kwargs):
if 'name' in kwargs:
del kwargs['name']
warnings.warn(
"ContentType.name field doesn't exist any longer. Please remove it from your code.",
RemovedInDjango20Warning, stacklevel=2)
return super(ContentTypeManager, self).create(**kwargs)
def get_for_model(self, model, for_concrete_model=True): def get_for_model(self, model, for_concrete_model=True):
""" """
Returns the ContentType object for a given model, creating the Returns the ContentType object for a given model, creating the
@ -66,7 +76,6 @@ class ContentTypeManager(models.Manager):
ct, created = self.get_or_create( ct, created = self.get_or_create(
app_label=opts.app_label, app_label=opts.app_label,
model=opts.model_name, model=opts.model_name,
defaults={'name': opts.verbose_name_raw},
) )
self._add_to_cache(self.db, ct) self._add_to_cache(self.db, ct)
return ct return ct
@ -108,7 +117,6 @@ class ContentTypeManager(models.Manager):
ct = self.create( ct = self.create(
app_label=opts.app_label, app_label=opts.app_label,
model=opts.model_name, model=opts.model_name,
name=opts.verbose_name_raw,
) )
self._add_to_cache(self.db, ct) self._add_to_cache(self.db, ct)
results[ct.model_class()] = ct results[ct.model_class()] = ct
@ -148,7 +156,6 @@ class ContentTypeManager(models.Manager):
@python_2_unicode_compatible @python_2_unicode_compatible
class ContentType(models.Model): class ContentType(models.Model):
name = models.CharField(max_length=100)
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()
@ -157,22 +164,17 @@ class ContentType(models.Model):
verbose_name = _('content type') verbose_name = _('content type')
verbose_name_plural = _('content types') verbose_name_plural = _('content types')
db_table = 'django_content_type' db_table = 'django_content_type'
ordering = ('name',)
unique_together = (('app_label', 'model'),) unique_together = (('app_label', 'model'),)
def __str__(self): def __str__(self):
# self.name is deprecated in favor of using model's verbose_name, which return self.name
# can be translated. Formal deprecation is delayed until we have DB
# migration to be able to remove the field from the database along with @property
# the attribute. def name(self):
#
# We return self.name only when users have changed its value from the
# initial verbose_name_raw and might rely on it.
model = self.model_class() model = self.model_class()
if not model or self.name != model._meta.verbose_name_raw: if not model:
return self.name return self.model
else: return force_text(model._meta.verbose_name)
return force_text(model._meta.verbose_name)
def model_class(self): def model_class(self):
"Returns the Python model class for this type of content." "Returns the Python model class for this type of content."

View File

@ -1,5 +1,7 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import warnings
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
from django.contrib.contenttypes.views import shortcut from django.contrib.contenttypes.views import shortcut
from django.contrib.sites.shortcuts import get_current_site from django.contrib.sites.shortcuts import get_current_site
@ -230,11 +232,10 @@ class ContentTypesTests(TestCase):
is defined anymore. is defined anymore.
""" """
ct = ContentType.objects.create( ct = ContentType.objects.create(
name='Old model',
app_label='contenttypes', app_label='contenttypes',
model='OldModel', model='OldModel',
) )
self.assertEqual(six.text_type(ct), 'Old model') self.assertEqual(six.text_type(ct), 'OldModel')
self.assertIsNone(ct.model_class()) self.assertIsNone(ct.model_class())
# Make sure stale ContentTypes can be fetched like any other object. # Make sure stale ContentTypes can be fetched like any other object.
@ -243,9 +244,6 @@ class ContentTypesTests(TestCase):
ct_fetched = ContentType.objects.get_for_id(ct.pk) ct_fetched = ContentType.objects.get_for_id(ct.pk)
self.assertIsNone(ct_fetched.model_class()) self.assertIsNone(ct_fetched.model_class())
class MigrateTests(TestCase):
@skipUnlessDBFeature('can_rollback_ddl') @skipUnlessDBFeature('can_rollback_ddl')
def test_unmigrating_first_migration_post_migrate_signal(self): def test_unmigrating_first_migration_post_migrate_signal(self):
""" """
@ -260,3 +258,22 @@ class MigrateTests(TestCase):
call_command("migrate", "contenttypes", "zero", verbosity=0) call_command("migrate", "contenttypes", "zero", verbosity=0)
finally: finally:
call_command("migrate", verbosity=0) call_command("migrate", verbosity=0)
def test_name_deprecation(self):
"""
ContentType.name has been removed. Test that a warning is emitted when
creating a ContentType with a `name`, but the creation should not fail.
"""
with warnings.catch_warnings(record=True) as warns:
warnings.simplefilter('always')
ContentType.objects.create(
name='Name',
app_label='contenttypes',
model='OldModel',
)
self.assertEqual(len(warns), 1)
self.assertEqual(
str(warns[0].message),
"ContentType.name field doesn't exist any longer. Please remove it from your code."
)
self.assertTrue(ContentType.objects.filter(model='OldModel').exists())

View File

@ -164,6 +164,9 @@ details on these changes.
* ``GeoQuerySet`` aggregate methods ``collect()``, ``extent()``, ``extent3d()``, * ``GeoQuerySet`` aggregate methods ``collect()``, ``extent()``, ``extent3d()``,
``makeline()``, and ``union()`` will be removed. ``makeline()``, and ``union()`` will be removed.
* Ability to specify ``ContentType.name`` when creating a content type instance
will be removed.
.. _deprecation-removed-in-1.9: .. _deprecation-removed-in-1.9:
1.9 1.9

View File

@ -59,7 +59,7 @@ The ``ContentType`` model
.. class:: ContentType .. class:: ContentType
Each instance of :class:`~django.contrib.contenttypes.models.ContentType` Each instance of :class:`~django.contrib.contenttypes.models.ContentType`
has three fields which, taken together, uniquely describe an installed has two fields which, taken together, uniquely describe an installed
model: model:
.. attribute:: app_label .. attribute:: app_label
@ -74,12 +74,19 @@ The ``ContentType`` model
The name of the model class. The name of the model class.
Additionally, the following property is available:
.. attribute:: name .. attribute:: name
The human-readable name of the model. This is taken from the The human-readable name of the content type. This is taken from the
:attr:`verbose_name <django.db.models.Field.verbose_name>` :attr:`verbose_name <django.db.models.Field.verbose_name>`
attribute of the model. attribute of the model.
.. versionchanged:: 1.8
Before Django 1.8, the ``name`` property was a real field on the
``ContentType`` model.
Let's look at an example to see how this works. If you already have Let's look at an example to see how this works. If you already have
the :mod:`~django.contrib.contenttypes` application installed, and then add the :mod:`~django.contrib.contenttypes` application installed, and then add
:mod:`the sites application <django.contrib.sites>` to your :mod:`the sites application <django.contrib.sites>` to your
@ -96,9 +103,6 @@ created with the following values:
* :attr:`~django.contrib.contenttypes.models.ContentType.model` * :attr:`~django.contrib.contenttypes.models.ContentType.model`
will be set to ``'site'``. will be set to ``'site'``.
* :attr:`~django.contrib.contenttypes.models.ContentType.name`
will be set to ``'site'``.
.. _the verbose_name attribute: ../model-api/#verbose_name .. _the verbose_name attribute: ../model-api/#verbose_name
Methods on ``ContentType`` instances Methods on ``ContentType`` instances

View File

@ -1101,6 +1101,10 @@ Miscellaneous
the newly introduced ``hints`` parameter for these operations, but it can the newly introduced ``hints`` parameter for these operations, but it can
also be used to disable migrations from running on a particular database. also be used to disable migrations from running on a particular database.
* The ``name`` field of :class:`django.contrib.contenttypes.models.ContentType`
has been removed by a migration and replaced by a property. That means it's
not possible to query or filter a ``ContentType`` by this field any longer.
.. _deprecated-features-1.8: .. _deprecated-features-1.8:
Features deprecated in 1.8 Features deprecated in 1.8

View File

@ -1974,7 +1974,7 @@ class AdminViewStringPrimaryKeyTest(TestCase):
self.assertContains(response, should_contain) self.assertContains(response, should_contain)
should_contain = "Model with string primary key" # capitalized in Recent Actions should_contain = "Model with string primary key" # capitalized in Recent Actions
self.assertContains(response, should_contain) self.assertContains(response, should_contain)
logentry = LogEntry.objects.get(content_type__name__iexact=should_contain) logentry = LogEntry.objects.get(content_type__model__iexact='modelwithstringprimarykey')
# http://code.djangoproject.com/ticket/10275 # http://code.djangoproject.com/ticket/10275
# if the log entry doesn't have a content type it should still be # if the log entry doesn't have a content type it should still be
# possible to view the Recent Actions part # possible to view the Recent Actions part
@ -1989,8 +1989,8 @@ class AdminViewStringPrimaryKeyTest(TestCase):
def test_logentry_get_admin_url(self): def test_logentry_get_admin_url(self):
"LogEntry.get_admin_url returns a URL to edit the entry's object or None for non-existent (possibly deleted) models" "LogEntry.get_admin_url returns a URL to edit the entry's object or None for non-existent (possibly deleted) models"
log_entry_name = "Model with string primary key" # capitalized in Recent Actions log_entry_model = "modelwithstringprimarykey" # capitalized in Recent Actions
logentry = LogEntry.objects.get(content_type__name__iexact=log_entry_name) logentry = LogEntry.objects.get(content_type__model__iexact=log_entry_model)
model = "modelwithstringprimarykey" model = "modelwithstringprimarykey"
desired_admin_url = "/test_admin/admin/admin_views/%s/%s/" % (model, iri_to_uri(quote(self.pk))) desired_admin_url = "/test_admin/admin/admin_views/%s/%s/" % (model, iri_to_uri(quote(self.pk)))
self.assertEqual(logentry.get_admin_url(), desired_admin_url) self.assertEqual(logentry.get_admin_url(), desired_admin_url)

View File

@ -11,7 +11,7 @@ from django.core import checks
from django.db import connections, models from django.db import connections, models
from django.test import TestCase, override_settings from django.test import TestCase, override_settings
from django.test.utils import captured_stdout from django.test.utils import captured_stdout
from django.utils.encoding import force_str from django.utils.encoding import force_str, force_text
from .models import Author, Article, SchemeIncludedURL from .models import Author, Article, SchemeIncludedURL
@ -87,7 +87,7 @@ class ContentTypesViewsTests(TestCase):
ct = ContentType.objects.get_for_model(ModelCreatedOnTheFly) ct = ContentType.objects.get_for_model(ModelCreatedOnTheFly)
self.assertEqual(ct.app_label, 'my_great_app') self.assertEqual(ct.app_label, 'my_great_app')
self.assertEqual(ct.model, 'modelcreatedonthefly') self.assertEqual(ct.model, 'modelcreatedonthefly')
self.assertEqual(ct.name, 'a model created on the fly') self.assertEqual(force_text(ct), 'modelcreatedonthefly')
class IsolatedModelsTestCase(TestCase): class IsolatedModelsTestCase(TestCase):
@ -364,7 +364,7 @@ class GenericRelationshipTests(IsolatedModelsTestCase):
class UpdateContentTypesTests(TestCase): class UpdateContentTypesTests(TestCase):
def setUp(self): def setUp(self):
self.before_count = ContentType.objects.count() self.before_count = ContentType.objects.count()
ContentType.objects.create(name='fake', app_label='contenttypes_tests', model='Fake') ContentType.objects.create(app_label='contenttypes_tests', model='Fake')
self.app_config = apps.get_app_config('contenttypes_tests') self.app_config = apps.get_app_config('contenttypes_tests')
def test_interactive_true(self): def test_interactive_true(self):

View File

@ -29,7 +29,7 @@ class TaggedItem(models.Model):
content_object = GenericForeignKey() content_object = GenericForeignKey()
class Meta: class Meta:
ordering = ["tag", "content_type__name"] ordering = ["tag", "content_type__model"]
def __str__(self): def __str__(self):
return self.tag return self.tag

View File

@ -28,8 +28,3 @@ class ContentTypeTests(TestCase):
self.assertEqual(six.text_type(company_type), 'Company') self.assertEqual(six.text_type(company_type), 'Company')
with translation.override('fr'): with translation.override('fr'):
self.assertEqual(six.text_type(company_type), 'Société') self.assertEqual(six.text_type(company_type), 'Société')
def test_field_override(self):
company_type = ContentType.objects.get(app_label='i18n', model='company')
company_type.name = 'Other'
self.assertEqual(six.text_type(company_type), 'Other')