From eb8600a65673649ea15ed18d17127f741807ac8b Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Mon, 16 Jun 2014 13:28:03 -0400 Subject: Added database migration for contrib.contenttypes. Moved contenttypes tests to allow them to run correctly in the presence of migrations. refs #22170. --- .../contenttypes/migrations/0001_initial.py | 33 +++ django/contrib/contenttypes/migrations/__init__.py | 0 django/contrib/contenttypes/models.py | 2 + django/contrib/contenttypes/tests.py | 283 --------------------- django/contrib/contenttypes/tests/__init__.py | 0 django/contrib/contenttypes/tests/models.py | 43 ++++ django/contrib/contenttypes/tests/tests.py | 243 ++++++++++++++++++ tests/migrations/test_loader.py | 2 +- tests/runtests.py | 5 + 9 files changed, 327 insertions(+), 284 deletions(-) create mode 100644 django/contrib/contenttypes/migrations/0001_initial.py create mode 100644 django/contrib/contenttypes/migrations/__init__.py delete mode 100644 django/contrib/contenttypes/tests.py create mode 100644 django/contrib/contenttypes/tests/__init__.py create mode 100644 django/contrib/contenttypes/tests/models.py create mode 100644 django/contrib/contenttypes/tests/tests.py diff --git a/django/contrib/contenttypes/migrations/0001_initial.py b/django/contrib/contenttypes/migrations/0001_initial.py new file mode 100644 index 0000000000..08e1119376 --- /dev/null +++ b/django/contrib/contenttypes/migrations/0001_initial.py @@ -0,0 +1,33 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import models, migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='ContentType', + fields=[ + ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), + ('name', models.CharField(max_length=100)), + ('app_label', models.CharField(max_length=100)), + ('model', models.CharField(max_length=100, verbose_name='python model class name')), + ], + options={ + 'ordering': ('name',), + 'db_table': 'django_content_type', + 'verbose_name': 'content type', + 'verbose_name_plural': 'content types', + }, + bases=(models.Model,), + ), + migrations.AlterUniqueTogether( + name='contenttype', + unique_together=set([('app_label', 'model')]), + ), + ] diff --git a/django/contrib/contenttypes/migrations/__init__.py b/django/contrib/contenttypes/migrations/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/django/contrib/contenttypes/models.py b/django/contrib/contenttypes/models.py index 634576f4d0..c54defb459 100644 --- a/django/contrib/contenttypes/models.py +++ b/django/contrib/contenttypes/models.py @@ -1,3 +1,5 @@ +from __future__ import unicode_literals + from django.apps import apps from django.db import models from django.utils.translation import ugettext_lazy as _ diff --git a/django/contrib/contenttypes/tests.py b/django/contrib/contenttypes/tests.py deleted file mode 100644 index 6ee34adb4a..0000000000 --- a/django/contrib/contenttypes/tests.py +++ /dev/null @@ -1,283 +0,0 @@ -from __future__ import unicode_literals - -from django.contrib.contenttypes.models import ContentType -from django.contrib.contenttypes.views import shortcut -from django.contrib.sites.shortcuts import get_current_site -from django.db import models -from django.http import HttpRequest, Http404 -from django.test import TestCase, override_settings -from django.utils.http import urlquote -from django.utils import six -from django.utils.encoding import python_2_unicode_compatible - - -class ConcreteModel(models.Model): - name = models.CharField(max_length=10) - - -class ProxyModel(ConcreteModel): - class Meta: - proxy = True - - -@python_2_unicode_compatible -class FooWithoutUrl(models.Model): - """ - Fake model not defining ``get_absolute_url`` for - :meth:`ContentTypesTests.test_shortcut_view_without_get_absolute_url`""" - name = models.CharField(max_length=30, unique=True) - - def __str__(self): - return self.name - - -class FooWithUrl(FooWithoutUrl): - """ - Fake model defining ``get_absolute_url`` for - :meth:`ContentTypesTests.test_shortcut_view` - """ - - def get_absolute_url(self): - return "/users/%s/" % urlquote(self.name) - - -class FooWithBrokenAbsoluteUrl(FooWithoutUrl): - """ - Fake model defining a ``get_absolute_url`` method containing an error - """ - - def get_absolute_url(self): - return "/users/%s/" % self.unknown_field - - -class ContentTypesTests(TestCase): - - def setUp(self): - ContentType.objects.clear_cache() - - def tearDown(self): - ContentType.objects.clear_cache() - - def test_lookup_cache(self): - """ - Make sure that the content type cache (see ContentTypeManager) - works correctly. Lookups for a particular content type -- by model, ID - or natural key -- should hit the database only on the first lookup. - """ - - # At this point, a lookup for a ContentType should hit the DB - with self.assertNumQueries(1): - ContentType.objects.get_for_model(ContentType) - - # A second hit, though, won't hit the DB, nor will a lookup by ID - # or natural key - with self.assertNumQueries(0): - ct = ContentType.objects.get_for_model(ContentType) - with self.assertNumQueries(0): - ContentType.objects.get_for_id(ct.id) - with self.assertNumQueries(0): - ContentType.objects.get_by_natural_key('contenttypes', - 'contenttype') - - # Once we clear the cache, another lookup will again hit the DB - ContentType.objects.clear_cache() - with self.assertNumQueries(1): - ContentType.objects.get_for_model(ContentType) - - # The same should happen with a lookup by natural key - ContentType.objects.clear_cache() - with self.assertNumQueries(1): - ContentType.objects.get_by_natural_key('contenttypes', - 'contenttype') - # And a second hit shouldn't hit the DB - with self.assertNumQueries(0): - ContentType.objects.get_by_natural_key('contenttypes', - 'contenttype') - - def test_get_for_models_empty_cache(self): - # Empty cache. - with self.assertNumQueries(1): - cts = ContentType.objects.get_for_models(ContentType, FooWithUrl) - self.assertEqual(cts, { - ContentType: ContentType.objects.get_for_model(ContentType), - FooWithUrl: ContentType.objects.get_for_model(FooWithUrl), - }) - - def test_get_for_models_partial_cache(self): - # Partial cache - ContentType.objects.get_for_model(ContentType) - with self.assertNumQueries(1): - cts = ContentType.objects.get_for_models(ContentType, FooWithUrl) - self.assertEqual(cts, { - ContentType: ContentType.objects.get_for_model(ContentType), - FooWithUrl: ContentType.objects.get_for_model(FooWithUrl), - }) - - def test_get_for_models_full_cache(self): - # Full cache - ContentType.objects.get_for_model(ContentType) - ContentType.objects.get_for_model(FooWithUrl) - with self.assertNumQueries(0): - cts = ContentType.objects.get_for_models(ContentType, FooWithUrl) - self.assertEqual(cts, { - ContentType: ContentType.objects.get_for_model(ContentType), - FooWithUrl: ContentType.objects.get_for_model(FooWithUrl), - }) - - def test_get_for_concrete_model(self): - """ - Make sure the `for_concrete_model` kwarg correctly works - with concrete, proxy and deferred models - """ - concrete_model_ct = ContentType.objects.get_for_model(ConcreteModel) - - self.assertEqual(concrete_model_ct, - ContentType.objects.get_for_model(ProxyModel)) - - self.assertEqual(concrete_model_ct, - ContentType.objects.get_for_model(ConcreteModel, - for_concrete_model=False)) - - proxy_model_ct = ContentType.objects.get_for_model(ProxyModel, - for_concrete_model=False) - - self.assertNotEqual(concrete_model_ct, proxy_model_ct) - - # Make sure deferred model are correctly handled - ConcreteModel.objects.create(name="Concrete") - DeferredConcreteModel = ConcreteModel.objects.only('pk').get().__class__ - DeferredProxyModel = ProxyModel.objects.only('pk').get().__class__ - - self.assertEqual(concrete_model_ct, - ContentType.objects.get_for_model(DeferredConcreteModel)) - - self.assertEqual(concrete_model_ct, - ContentType.objects.get_for_model(DeferredConcreteModel, - for_concrete_model=False)) - - self.assertEqual(concrete_model_ct, - ContentType.objects.get_for_model(DeferredProxyModel)) - - self.assertEqual(proxy_model_ct, - ContentType.objects.get_for_model(DeferredProxyModel, - for_concrete_model=False)) - - def test_get_for_concrete_models(self): - """ - Make sure the `for_concrete_models` kwarg correctly works - with concrete, proxy and deferred models. - """ - concrete_model_ct = ContentType.objects.get_for_model(ConcreteModel) - - cts = ContentType.objects.get_for_models(ConcreteModel, ProxyModel) - self.assertEqual(cts, { - ConcreteModel: concrete_model_ct, - ProxyModel: concrete_model_ct, - }) - - proxy_model_ct = ContentType.objects.get_for_model(ProxyModel, - for_concrete_model=False) - cts = ContentType.objects.get_for_models(ConcreteModel, ProxyModel, - for_concrete_models=False) - self.assertEqual(cts, { - ConcreteModel: concrete_model_ct, - ProxyModel: proxy_model_ct, - }) - - # Make sure deferred model are correctly handled - ConcreteModel.objects.create(name="Concrete") - DeferredConcreteModel = ConcreteModel.objects.only('pk').get().__class__ - DeferredProxyModel = ProxyModel.objects.only('pk').get().__class__ - - cts = ContentType.objects.get_for_models(DeferredConcreteModel, - DeferredProxyModel) - self.assertEqual(cts, { - DeferredConcreteModel: concrete_model_ct, - DeferredProxyModel: concrete_model_ct, - }) - - cts = ContentType.objects.get_for_models(DeferredConcreteModel, - DeferredProxyModel, - for_concrete_models=False) - self.assertEqual(cts, { - DeferredConcreteModel: concrete_model_ct, - DeferredProxyModel: proxy_model_ct, - }) - - @override_settings(ALLOWED_HOSTS=['example.com']) - def test_shortcut_view(self): - """ - Check that the shortcut view (used for the admin "view on site" - functionality) returns a complete URL regardless of whether the sites - framework is installed - """ - - request = HttpRequest() - request.META = { - "SERVER_NAME": "Example.com", - "SERVER_PORT": "80", - } - user_ct = ContentType.objects.get_for_model(FooWithUrl) - obj = FooWithUrl.objects.create(name="john") - - with self.modify_settings(INSTALLED_APPS={'append': 'django.contrib.sites'}): - response = shortcut(request, user_ct.id, obj.id) - self.assertEqual("http://%s/users/john/" % get_current_site(request).domain, - response._headers.get("location")[1]) - - with self.modify_settings(INSTALLED_APPS={'remove': 'django.contrib.sites'}): - response = shortcut(request, user_ct.id, obj.id) - self.assertEqual("http://Example.com/users/john/", - response._headers.get("location")[1]) - - def test_shortcut_view_without_get_absolute_url(self): - """ - Check that the shortcut view (used for the admin "view on site" - functionality) returns 404 when get_absolute_url is not defined. - """ - - request = HttpRequest() - request.META = { - "SERVER_NAME": "Example.com", - "SERVER_PORT": "80", - } - user_ct = ContentType.objects.get_for_model(FooWithoutUrl) - obj = FooWithoutUrl.objects.create(name="john") - - self.assertRaises(Http404, shortcut, request, user_ct.id, obj.id) - - def test_shortcut_view_with_broken_get_absolute_url(self): - """ - Check that the shortcut view does not catch an AttributeError raised - by the model's get_absolute_url method. - Refs #8997. - """ - request = HttpRequest() - request.META = { - "SERVER_NAME": "Example.com", - "SERVER_PORT": "80", - } - user_ct = ContentType.objects.get_for_model(FooWithBrokenAbsoluteUrl) - obj = FooWithBrokenAbsoluteUrl.objects.create(name="john") - - self.assertRaises(AttributeError, shortcut, request, user_ct.id, obj.id) - - def test_missing_model(self): - """ - Ensures that displaying content types in admin (or anywhere) doesn't - break on leftover content type records in the DB for which no model - is defined anymore. - """ - ct = ContentType.objects.create( - name='Old model', - app_label='contenttypes', - model='OldModel', - ) - self.assertEqual(six.text_type(ct), 'Old model') - self.assertIsNone(ct.model_class()) - - # Make sure stale ContentTypes can be fetched like any other object. - # Before Django 1.6 this caused a NoneType error in the caching mechanism. - # Instead, just return the ContentType object and let the app detect stale states. - ct_fetched = ContentType.objects.get_for_id(ct.pk) - self.assertIsNone(ct_fetched.model_class()) diff --git a/django/contrib/contenttypes/tests/__init__.py b/django/contrib/contenttypes/tests/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/django/contrib/contenttypes/tests/models.py b/django/contrib/contenttypes/tests/models.py new file mode 100644 index 0000000000..a173f091bd --- /dev/null +++ b/django/contrib/contenttypes/tests/models.py @@ -0,0 +1,43 @@ +from django.db import models +from django.utils.encoding import python_2_unicode_compatible +from django.utils.http import urlquote + + +class ConcreteModel(models.Model): + name = models.CharField(max_length=10) + + +class ProxyModel(ConcreteModel): + class Meta: + proxy = True + + +@python_2_unicode_compatible +class FooWithoutUrl(models.Model): + """ + Fake model not defining ``get_absolute_url`` for + ContentTypesTests.test_shortcut_view_without_get_absolute_url() + """ + name = models.CharField(max_length=30, unique=True) + + def __str__(self): + return self.name + + +class FooWithUrl(FooWithoutUrl): + """ + Fake model defining ``get_absolute_url`` for + ContentTypesTests.test_shortcut_view(). + """ + + def get_absolute_url(self): + return "/users/%s/" % urlquote(self.name) + + +class FooWithBrokenAbsoluteUrl(FooWithoutUrl): + """ + Fake model defining a ``get_absolute_url`` method containing an error + """ + + def get_absolute_url(self): + return "/users/%s/" % self.unknown_field diff --git a/django/contrib/contenttypes/tests/tests.py b/django/contrib/contenttypes/tests/tests.py new file mode 100644 index 0000000000..924ffdca46 --- /dev/null +++ b/django/contrib/contenttypes/tests/tests.py @@ -0,0 +1,243 @@ +from __future__ import unicode_literals + +from django.contrib.contenttypes.models import ContentType +from django.contrib.contenttypes.views import shortcut +from django.contrib.sites.shortcuts import get_current_site +from django.http import HttpRequest, Http404 +from django.test import TestCase, override_settings +from django.utils import six + +from .models import ConcreteModel, ProxyModel, FooWithoutUrl, FooWithUrl, FooWithBrokenAbsoluteUrl + + +class ContentTypesTests(TestCase): + + def setUp(self): + ContentType.objects.clear_cache() + + def tearDown(self): + ContentType.objects.clear_cache() + + def test_lookup_cache(self): + """ + Make sure that the content type cache (see ContentTypeManager) + works correctly. Lookups for a particular content type -- by model, ID + or natural key -- should hit the database only on the first lookup. + """ + + # At this point, a lookup for a ContentType should hit the DB + with self.assertNumQueries(1): + ContentType.objects.get_for_model(ContentType) + + # A second hit, though, won't hit the DB, nor will a lookup by ID + # or natural key + with self.assertNumQueries(0): + ct = ContentType.objects.get_for_model(ContentType) + with self.assertNumQueries(0): + ContentType.objects.get_for_id(ct.id) + with self.assertNumQueries(0): + ContentType.objects.get_by_natural_key('contenttypes', + 'contenttype') + + # Once we clear the cache, another lookup will again hit the DB + ContentType.objects.clear_cache() + with self.assertNumQueries(1): + ContentType.objects.get_for_model(ContentType) + + # The same should happen with a lookup by natural key + ContentType.objects.clear_cache() + with self.assertNumQueries(1): + ContentType.objects.get_by_natural_key('contenttypes', + 'contenttype') + # And a second hit shouldn't hit the DB + with self.assertNumQueries(0): + ContentType.objects.get_by_natural_key('contenttypes', + 'contenttype') + + def test_get_for_models_empty_cache(self): + # Empty cache. + with self.assertNumQueries(1): + cts = ContentType.objects.get_for_models(ContentType, FooWithUrl) + self.assertEqual(cts, { + ContentType: ContentType.objects.get_for_model(ContentType), + FooWithUrl: ContentType.objects.get_for_model(FooWithUrl), + }) + + def test_get_for_models_partial_cache(self): + # Partial cache + ContentType.objects.get_for_model(ContentType) + with self.assertNumQueries(1): + cts = ContentType.objects.get_for_models(ContentType, FooWithUrl) + self.assertEqual(cts, { + ContentType: ContentType.objects.get_for_model(ContentType), + FooWithUrl: ContentType.objects.get_for_model(FooWithUrl), + }) + + def test_get_for_models_full_cache(self): + # Full cache + ContentType.objects.get_for_model(ContentType) + ContentType.objects.get_for_model(FooWithUrl) + with self.assertNumQueries(0): + cts = ContentType.objects.get_for_models(ContentType, FooWithUrl) + self.assertEqual(cts, { + ContentType: ContentType.objects.get_for_model(ContentType), + FooWithUrl: ContentType.objects.get_for_model(FooWithUrl), + }) + + def test_get_for_concrete_model(self): + """ + Make sure the `for_concrete_model` kwarg correctly works + with concrete, proxy and deferred models + """ + concrete_model_ct = ContentType.objects.get_for_model(ConcreteModel) + + self.assertEqual(concrete_model_ct, + ContentType.objects.get_for_model(ProxyModel)) + + self.assertEqual(concrete_model_ct, + ContentType.objects.get_for_model(ConcreteModel, + for_concrete_model=False)) + + proxy_model_ct = ContentType.objects.get_for_model(ProxyModel, + for_concrete_model=False) + + self.assertNotEqual(concrete_model_ct, proxy_model_ct) + + # Make sure deferred model are correctly handled + ConcreteModel.objects.create(name="Concrete") + DeferredConcreteModel = ConcreteModel.objects.only('pk').get().__class__ + DeferredProxyModel = ProxyModel.objects.only('pk').get().__class__ + + self.assertEqual(concrete_model_ct, + ContentType.objects.get_for_model(DeferredConcreteModel)) + + self.assertEqual(concrete_model_ct, + ContentType.objects.get_for_model(DeferredConcreteModel, + for_concrete_model=False)) + + self.assertEqual(concrete_model_ct, + ContentType.objects.get_for_model(DeferredProxyModel)) + + self.assertEqual(proxy_model_ct, + ContentType.objects.get_for_model(DeferredProxyModel, + for_concrete_model=False)) + + def test_get_for_concrete_models(self): + """ + Make sure the `for_concrete_models` kwarg correctly works + with concrete, proxy and deferred models. + """ + concrete_model_ct = ContentType.objects.get_for_model(ConcreteModel) + + cts = ContentType.objects.get_for_models(ConcreteModel, ProxyModel) + self.assertEqual(cts, { + ConcreteModel: concrete_model_ct, + ProxyModel: concrete_model_ct, + }) + + proxy_model_ct = ContentType.objects.get_for_model(ProxyModel, + for_concrete_model=False) + cts = ContentType.objects.get_for_models(ConcreteModel, ProxyModel, + for_concrete_models=False) + self.assertEqual(cts, { + ConcreteModel: concrete_model_ct, + ProxyModel: proxy_model_ct, + }) + + # Make sure deferred model are correctly handled + ConcreteModel.objects.create(name="Concrete") + DeferredConcreteModel = ConcreteModel.objects.only('pk').get().__class__ + DeferredProxyModel = ProxyModel.objects.only('pk').get().__class__ + + cts = ContentType.objects.get_for_models(DeferredConcreteModel, + DeferredProxyModel) + self.assertEqual(cts, { + DeferredConcreteModel: concrete_model_ct, + DeferredProxyModel: concrete_model_ct, + }) + + cts = ContentType.objects.get_for_models(DeferredConcreteModel, + DeferredProxyModel, + for_concrete_models=False) + self.assertEqual(cts, { + DeferredConcreteModel: concrete_model_ct, + DeferredProxyModel: proxy_model_ct, + }) + + @override_settings(ALLOWED_HOSTS=['example.com']) + def test_shortcut_view(self): + """ + Check that the shortcut view (used for the admin "view on site" + functionality) returns a complete URL regardless of whether the sites + framework is installed + """ + + request = HttpRequest() + request.META = { + "SERVER_NAME": "Example.com", + "SERVER_PORT": "80", + } + user_ct = ContentType.objects.get_for_model(FooWithUrl) + obj = FooWithUrl.objects.create(name="john") + + with self.modify_settings(INSTALLED_APPS={'append': 'django.contrib.sites'}): + response = shortcut(request, user_ct.id, obj.id) + self.assertEqual("http://%s/users/john/" % get_current_site(request).domain, + response._headers.get("location")[1]) + + with self.modify_settings(INSTALLED_APPS={'remove': 'django.contrib.sites'}): + response = shortcut(request, user_ct.id, obj.id) + self.assertEqual("http://Example.com/users/john/", + response._headers.get("location")[1]) + + def test_shortcut_view_without_get_absolute_url(self): + """ + Check that the shortcut view (used for the admin "view on site" + functionality) returns 404 when get_absolute_url is not defined. + """ + + request = HttpRequest() + request.META = { + "SERVER_NAME": "Example.com", + "SERVER_PORT": "80", + } + user_ct = ContentType.objects.get_for_model(FooWithoutUrl) + obj = FooWithoutUrl.objects.create(name="john") + + self.assertRaises(Http404, shortcut, request, user_ct.id, obj.id) + + def test_shortcut_view_with_broken_get_absolute_url(self): + """ + Check that the shortcut view does not catch an AttributeError raised + by the model's get_absolute_url method. + Refs #8997. + """ + request = HttpRequest() + request.META = { + "SERVER_NAME": "Example.com", + "SERVER_PORT": "80", + } + user_ct = ContentType.objects.get_for_model(FooWithBrokenAbsoluteUrl) + obj = FooWithBrokenAbsoluteUrl.objects.create(name="john") + + self.assertRaises(AttributeError, shortcut, request, user_ct.id, obj.id) + + def test_missing_model(self): + """ + Ensures that displaying content types in admin (or anywhere) doesn't + break on leftover content type records in the DB for which no model + is defined anymore. + """ + ct = ContentType.objects.create( + name='Old model', + app_label='contenttypes', + model='OldModel', + ) + self.assertEqual(six.text_type(ct), 'Old model') + self.assertIsNone(ct.model_class()) + + # Make sure stale ContentTypes can be fetched like any other object. + # Before Django 1.6 this caused a NoneType error in the caching mechanism. + # Instead, just return the ContentType object and let the app detect stale states. + ct_fetched = ContentType.objects.get_for_id(ct.pk) + self.assertIsNone(ct_fetched.model_class()) diff --git a/tests/migrations/test_loader.py b/tests/migrations/test_loader.py index 4c1e2d239c..e03873a1ea 100644 --- a/tests/migrations/test_loader.py +++ b/tests/migrations/test_loader.py @@ -77,7 +77,7 @@ class LoaderTests(TestCase): ) # Ensure we've included unmigrated apps in there too - self.assertIn("contenttypes", project_state.real_apps) + self.assertIn("auth", project_state.real_apps) @override_settings(MIGRATION_MODULES={"migrations": "migrations.test_migrations_unmigdep"}) def test_load_unmigrated_dependency(self): diff --git a/tests/runtests.py b/tests/runtests.py index c084f2590b..4c63415abf 100755 --- a/tests/runtests.py +++ b/tests/runtests.py @@ -128,6 +128,11 @@ def setup(verbosity, test_labels): settings.MIDDLEWARE_CLASSES = ALWAYS_MIDDLEWARE_CLASSES # Ensure the middleware classes are seen as overridden otherwise we get a compatibility warning. settings._explicit_settings.add('MIDDLEWARE_CLASSES') + settings.MIGRATION_MODULES = { + # this module doesn't actually exist, but this lets us skip creating + # migrations for the test modules for some reason. + 'contenttypes': 'django.contrib.contenttypes.tests.migrations', + } if verbosity > 0: # Ensure any warnings captured to logging are piped through a verbose -- cgit v1.3