diff options
| author | Tim Graham <timograham@gmail.com> | 2015-02-16 15:54:00 -0500 |
|---|---|---|
| committer | Tim Graham <timograham@gmail.com> | 2015-02-16 17:45:38 -0500 |
| commit | 664c038f2c5b56d1ce929243d29cd4e11ea5f9ea (patch) | |
| tree | 1415dc3fa702a16d2a8cffbd932ec4ae728e6423 /tests/contenttypes_tests | |
| parent | 35f0cae19de226d9d7771304fc8dd2619e644998 (diff) | |
Moved contrib.contenttypes tests out of contrib.
Diffstat (limited to 'tests/contenttypes_tests')
| -rw-r--r-- | tests/contenttypes_tests/models.py | 41 | ||||
| -rw-r--r-- | tests/contenttypes_tests/test_models.py | 290 |
2 files changed, 331 insertions, 0 deletions
diff --git a/tests/contenttypes_tests/models.py b/tests/contenttypes_tests/models.py index dadc40d3d9..15148cdebd 100644 --- a/tests/contenttypes_tests/models.py +++ b/tests/contenttypes_tests/models.py @@ -2,6 +2,7 @@ from __future__ import unicode_literals from django.db import models from django.utils.encoding import python_2_unicode_compatible +from django.utils.http import urlquote @python_2_unicode_compatible @@ -35,3 +36,43 @@ class SchemeIncludedURL(models.Model): def get_absolute_url(self): return self.url + + +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/tests/contenttypes_tests/test_models.py b/tests/contenttypes_tests/test_models.py new file mode 100644 index 0000000000..eb436b6a64 --- /dev/null +++ b/tests/contenttypes_tests/test_models.py @@ -0,0 +1,290 @@ +from __future__ import unicode_literals + +import warnings + +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.utils import IntegrityError, OperationalError, ProgrammingError +from django.http import Http404, HttpRequest +from django.test import TestCase, mock, override_settings +from django.utils import six + +from .models import ( + ConcreteModel, FooWithBrokenAbsoluteUrl, FooWithoutUrl, FooWithUrl, + ProxyModel, +) + + +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( + app_label='contenttypes', + model='OldModel', + ) + self.assertEqual(six.text_type(ct), 'OldModel') + 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()) + + 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()) + + @mock.patch('django.contrib.contenttypes.models.ContentTypeManager.get_or_create') + @mock.patch('django.contrib.contenttypes.models.ContentTypeManager.get') + def test_message_if_get_for_model_fails(self, mocked_get, mocked_get_or_create): + """ + Check that `RuntimeError` with nice error message is raised if + `get_for_model` fails because of database errors. + """ + + def _test_message(mocked_method): + for ExceptionClass in (IntegrityError, OperationalError, ProgrammingError): + mocked_method.side_effect = ExceptionClass + with self.assertRaisesMessage( + RuntimeError, + "Error creating new content types. Please make sure contenttypes " + "is migrated before trying to migrate apps individually." + ): + ContentType.objects.get_for_model(ContentType) + + _test_message(mocked_get) + + mocked_get.side_effect = ContentType.DoesNotExist + _test_message(mocked_get_or_create) |
