Split up contenttypes_tests.

This commit is contained in:
Tim Graham 2017-02-02 14:11:09 -05:00
parent 29f607927f
commit aa14528910
7 changed files with 518 additions and 538 deletions

View File

@ -0,0 +1,218 @@
from unittest import mock
from django.contrib.contenttypes.fields import (
GenericForeignKey, GenericRelation,
)
from django.contrib.contenttypes.models import ContentType
from django.core import checks
from django.db import models
from django.test import SimpleTestCase, override_settings
from django.test.utils import isolate_apps
@isolate_apps('contenttypes_tests', attr_name='apps')
class GenericForeignKeyTests(SimpleTestCase):
def test_missing_content_type_field(self):
class TaggedItem(models.Model):
# no content_type field
object_id = models.PositiveIntegerField()
content_object = GenericForeignKey()
expected = [
checks.Error(
"The GenericForeignKey content type references the nonexistent "
"field 'TaggedItem.content_type'.",
obj=TaggedItem.content_object,
id='contenttypes.E002',
)
]
self.assertEqual(TaggedItem.content_object.check(), expected)
def test_invalid_content_type_field(self):
class Model(models.Model):
content_type = models.IntegerField() # should be ForeignKey
object_id = models.PositiveIntegerField()
content_object = GenericForeignKey('content_type', 'object_id')
self.assertEqual(Model.content_object.check(), [
checks.Error(
"'Model.content_type' is not a ForeignKey.",
hint=(
"GenericForeignKeys must use a ForeignKey to "
"'contenttypes.ContentType' as the 'content_type' field."
),
obj=Model.content_object,
id='contenttypes.E003',
)
])
def test_content_type_field_pointing_to_wrong_model(self):
class Model(models.Model):
content_type = models.ForeignKey('self', models.CASCADE) # should point to ContentType
object_id = models.PositiveIntegerField()
content_object = GenericForeignKey('content_type', 'object_id')
self.assertEqual(Model.content_object.check(), [
checks.Error(
"'Model.content_type' is not a ForeignKey to 'contenttypes.ContentType'.",
hint=(
"GenericForeignKeys must use a ForeignKey to "
"'contenttypes.ContentType' as the 'content_type' field."
),
obj=Model.content_object,
id='contenttypes.E004',
)
])
def test_missing_object_id_field(self):
class TaggedItem(models.Model):
content_type = models.ForeignKey(ContentType, models.CASCADE)
# missing object_id field
content_object = GenericForeignKey()
self.assertEqual(TaggedItem.content_object.check(), [
checks.Error(
"The GenericForeignKey object ID references the nonexistent "
"field 'object_id'.",
obj=TaggedItem.content_object,
id='contenttypes.E001',
)
])
def test_field_name_ending_with_underscore(self):
class Model(models.Model):
content_type = models.ForeignKey(ContentType, models.CASCADE)
object_id = models.PositiveIntegerField()
content_object_ = GenericForeignKey('content_type', 'object_id')
self.assertEqual(Model.content_object_.check(), [
checks.Error(
'Field names must not end with an underscore.',
obj=Model.content_object_,
id='fields.E001',
)
])
@override_settings(INSTALLED_APPS=['django.contrib.auth', 'django.contrib.contenttypes', 'contenttypes_tests'])
def test_generic_foreign_key_checks_are_performed(self):
class Model(models.Model):
content_object = GenericForeignKey()
with mock.patch.object(GenericForeignKey, 'check') as check:
checks.run_checks(app_configs=self.apps.get_app_configs())
check.assert_called_once_with()
@isolate_apps('contenttypes_tests')
class GenericRelationTests(SimpleTestCase):
def test_valid_generic_relationship(self):
class TaggedItem(models.Model):
content_type = models.ForeignKey(ContentType, models.CASCADE)
object_id = models.PositiveIntegerField()
content_object = GenericForeignKey()
class Bookmark(models.Model):
tags = GenericRelation('TaggedItem')
self.assertEqual(Bookmark.tags.field.check(), [])
def test_valid_generic_relationship_with_explicit_fields(self):
class TaggedItem(models.Model):
custom_content_type = models.ForeignKey(ContentType, models.CASCADE)
custom_object_id = models.PositiveIntegerField()
content_object = GenericForeignKey('custom_content_type', 'custom_object_id')
class Bookmark(models.Model):
tags = GenericRelation(
'TaggedItem',
content_type_field='custom_content_type',
object_id_field='custom_object_id',
)
self.assertEqual(Bookmark.tags.field.check(), [])
def test_pointing_to_missing_model(self):
class Model(models.Model):
rel = GenericRelation('MissingModel')
self.assertEqual(Model.rel.field.check(), [
checks.Error(
"Field defines a relation with model 'MissingModel', "
"which is either not installed, or is abstract.",
obj=Model.rel.field,
id='fields.E300',
)
])
def test_valid_self_referential_generic_relationship(self):
class Model(models.Model):
rel = GenericRelation('Model')
content_type = models.ForeignKey(ContentType, models.CASCADE)
object_id = models.PositiveIntegerField()
content_object = GenericForeignKey('content_type', 'object_id')
self.assertEqual(Model.rel.field.check(), [])
def test_missing_generic_foreign_key(self):
class TaggedItem(models.Model):
content_type = models.ForeignKey(ContentType, models.CASCADE)
object_id = models.PositiveIntegerField()
class Bookmark(models.Model):
tags = GenericRelation('TaggedItem')
self.assertEqual(Bookmark.tags.field.check(), [
checks.Error(
"The GenericRelation defines a relation with the model "
"'contenttypes_tests.TaggedItem', but that model does not have a "
"GenericForeignKey.",
obj=Bookmark.tags.field,
id='contenttypes.E004',
)
])
@override_settings(TEST_SWAPPED_MODEL='contenttypes_tests.Replacement')
def test_pointing_to_swapped_model(self):
class Replacement(models.Model):
pass
class SwappedModel(models.Model):
content_type = models.ForeignKey(ContentType, models.CASCADE)
object_id = models.PositiveIntegerField()
content_object = GenericForeignKey()
class Meta:
swappable = 'TEST_SWAPPED_MODEL'
class Model(models.Model):
rel = GenericRelation('SwappedModel')
self.assertEqual(Model.rel.field.check(), [
checks.Error(
"Field defines a relation with the model "
"'contenttypes_tests.SwappedModel', "
"which has been swapped out.",
hint="Update the relation to point at 'settings.TEST_SWAPPED_MODEL'.",
obj=Model.rel.field,
id='fields.E301',
)
])
def test_field_name_ending_with_underscore(self):
class TaggedItem(models.Model):
content_type = models.ForeignKey(ContentType, models.CASCADE)
object_id = models.PositiveIntegerField()
content_object = GenericForeignKey()
class InvalidBookmark(models.Model):
tags_ = GenericRelation('TaggedItem')
self.assertEqual(InvalidBookmark.tags_.field.check(), [
checks.Error(
'Field names must not end with an underscore.',
obj=InvalidBookmark.tags_.field,
id='fields.E001',
)
])

View File

@ -0,0 +1,13 @@
from django.contrib.contenttypes.fields import GenericForeignKey
from django.db import models
from django.test import SimpleTestCase
from django.test.utils import isolate_apps
@isolate_apps('contenttypes_tests')
class GenericForeignKeyTests(SimpleTestCase):
def test_str(self):
class Model(models.Model):
field = GenericForeignKey()
self.assertEqual(str(Model.field), 'contenttypes_tests.Model.field')

View File

@ -0,0 +1,65 @@
from unittest import mock
from django.apps.registry import Apps, apps
from django.contrib.contenttypes import management as contenttypes_management
from django.contrib.contenttypes.models import ContentType
from django.core.management import call_command
from django.test import TestCase
from django.test.utils import captured_stdout
from .models import ModelWithNullFKToSite, Post
class UpdateContentTypesTests(TestCase):
def setUp(self):
self.before_count = ContentType.objects.count()
self.content_type = ContentType.objects.create(app_label='contenttypes_tests', model='Fake')
self.app_config = apps.get_app_config('contenttypes_tests')
def test_interactive_true_with_dependent_objects(self):
"""
interactive mode of remove_stale_contenttypes (the default) deletes
stale contenttypes and warn of dependent objects.
"""
post = Post.objects.create(title='post', content_type=self.content_type)
# A related object is needed to show that a custom collector with
# can_fast_delete=False is needed.
ModelWithNullFKToSite.objects.create(post=post)
with mock.patch('builtins.input', return_value='yes'):
with captured_stdout() as stdout:
call_command('remove_stale_contenttypes', verbosity=2, stdout=stdout)
self.assertEqual(Post.objects.count(), 0)
output = stdout.getvalue()
self.assertIn('- Content type for contenttypes_tests.Fake', output)
self.assertIn('- 1 contenttypes_tests.Post object(s)', output)
self.assertIn('- 1 contenttypes_tests.ModelWithNullFKToSite', output)
self.assertIn('Deleting stale content type', output)
self.assertEqual(ContentType.objects.count(), self.before_count)
def test_interactive_true_without_dependent_objects(self):
"""
interactive mode of remove_stale_contenttypes (the default) deletes
stale contenttypes even if there aren't any dependent objects.
"""
with mock.patch('builtins.input', return_value='yes'):
with captured_stdout() as stdout:
call_command('remove_stale_contenttypes', verbosity=2)
self.assertIn("Deleting stale content type", stdout.getvalue())
self.assertEqual(ContentType.objects.count(), self.before_count)
def test_interactive_false(self):
"""
non-interactive mode of remove_stale_contenttypes doesn't delete
stale content types.
"""
with captured_stdout() as stdout:
call_command('remove_stale_contenttypes', interactive=False, verbosity=2)
self.assertIn("Stale content types remain.", stdout.getvalue())
self.assertEqual(ContentType.objects.count(), self.before_count + 1)
def test_unavailable_content_type_model(self):
"""A ContentType isn't created if the model isn't available."""
apps = Apps()
with self.assertNumQueries(0):
contenttypes_management.create_contenttypes(self.app_config, interactive=False, verbosity=0, apps=apps)
self.assertEqual(ContentType.objects.count(), self.before_count + 1)

View File

@ -1,11 +1,12 @@
from django.contrib.contenttypes.models import ContentType, ContentTypeManager
from django.contrib.contenttypes.views import shortcut
from django.contrib.sites.shortcuts import get_current_site
from django.db import connections
from django.http import Http404, HttpRequest
from django.test import TestCase, override_settings
from .models import (
ConcreteModel, FooWithBrokenAbsoluteUrl, FooWithoutUrl, FooWithUrl,
Author, ConcreteModel, FooWithBrokenAbsoluteUrl, FooWithoutUrl, FooWithUrl,
ProxyModel,
)
@ -20,11 +21,10 @@ class ContentTypesTests(TestCase):
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.
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)
@ -244,8 +244,35 @@ class ContentTypesTests(TestCase):
self.assertEqual(str(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.
# Stale ContentTypes can be fetched like any other object.
ct_fetched = ContentType.objects.get_for_id(ct.pk)
self.assertIsNone(ct_fetched.model_class())
class TestRouter:
def db_for_read(self, model, **hints):
return 'other'
def db_for_write(self, model, **hints):
return 'default'
@override_settings(DATABASE_ROUTERS=[TestRouter()])
class ContentTypesMultidbTests(TestCase):
def setUp(self):
# When a test starts executing, only the "default" database is
# connected. Connect to the "other" database here because otherwise it
# will be connected later when it's queried. Some database backends
# perform extra queries upon connecting (MySQL executes
# "SET SQL_AUTO_IS_NULL = 0"), which will affect assertNumQueries().
connections['other'].ensure_connection()
def test_multidb(self):
"""
When using multiple databases, ContentType.objects.get_for_model() uses
db_for_read().
"""
ContentType.objects.clear_cache()
with self.assertNumQueries(0, using='default'), self.assertNumQueries(1, using='other'):
ContentType.objects.get_for_model(Author)

View File

@ -0,0 +1,66 @@
from django.apps.registry import apps
from django.conf import settings
from django.contrib.contenttypes import management as contenttypes_management
from django.contrib.contenttypes.models import ContentType
from django.core.management import call_command
from django.db import migrations, models
from django.test import TransactionTestCase, override_settings
@override_settings(
MIGRATION_MODULES=dict(
settings.MIGRATION_MODULES,
contenttypes_tests='contenttypes_tests.operations_migrations',
),
)
class ContentTypeOperationsTests(TransactionTestCase):
available_apps = [
'contenttypes_tests',
'django.contrib.contenttypes',
]
def setUp(self):
app_config = apps.get_app_config('contenttypes_tests')
models.signals.post_migrate.connect(self.assertOperationsInjected, sender=app_config)
def tearDown(self):
app_config = apps.get_app_config('contenttypes_tests')
models.signals.post_migrate.disconnect(self.assertOperationsInjected, sender=app_config)
def assertOperationsInjected(self, plan, **kwargs):
for migration, _backward in plan:
operations = iter(migration.operations)
for operation in operations:
if isinstance(operation, migrations.RenameModel):
next_operation = next(operations)
self.assertIsInstance(next_operation, contenttypes_management.RenameContentType)
self.assertEqual(next_operation.app_label, migration.app_label)
self.assertEqual(next_operation.old_model, operation.old_name_lower)
self.assertEqual(next_operation.new_model, operation.new_name_lower)
def test_existing_content_type_rename(self):
ContentType.objects.create(app_label='contenttypes_tests', model='foo')
call_command('migrate', 'contenttypes_tests', database='default', interactive=False, verbosity=0,)
self.assertFalse(ContentType.objects.filter(app_label='contenttypes_tests', model='foo').exists())
self.assertTrue(ContentType.objects.filter(app_label='contenttypes_tests', model='renamedfoo').exists())
call_command('migrate', 'contenttypes_tests', 'zero', database='default', interactive=False, verbosity=0)
self.assertTrue(ContentType.objects.filter(app_label='contenttypes_tests', model='foo').exists())
self.assertFalse(ContentType.objects.filter(app_label='contenttypes_tests', model='renamedfoo').exists())
def test_missing_content_type_rename_ignore(self):
call_command('migrate', 'contenttypes_tests', database='default', interactive=False, verbosity=0,)
self.assertFalse(ContentType.objects.filter(app_label='contenttypes_tests', model='foo').exists())
self.assertTrue(ContentType.objects.filter(app_label='contenttypes_tests', model='renamedfoo').exists())
call_command('migrate', 'contenttypes_tests', 'zero', database='default', interactive=False, verbosity=0)
self.assertTrue(ContentType.objects.filter(app_label='contenttypes_tests', model='foo').exists())
self.assertFalse(ContentType.objects.filter(app_label='contenttypes_tests', model='renamedfoo').exists())
def test_content_type_rename_conflict(self):
ContentType.objects.create(app_label='contenttypes_tests', model='foo')
ContentType.objects.create(app_label='contenttypes_tests', model='renamedfoo')
call_command('migrate', 'contenttypes_tests', database='default', interactive=False, verbosity=0)
self.assertTrue(ContentType.objects.filter(app_label='contenttypes_tests', model='foo').exists())
self.assertTrue(ContentType.objects.filter(app_label='contenttypes_tests', model='renamedfoo').exists())
call_command('migrate', 'contenttypes_tests', 'zero', database='default', interactive=False, verbosity=0)
self.assertTrue(ContentType.objects.filter(app_label='contenttypes_tests', model='foo').exists())
self.assertTrue(ContentType.objects.filter(app_label='contenttypes_tests', model='renamedfoo').exists())

View File

@ -0,0 +1,121 @@
import datetime
from unittest import mock
from django.contrib.contenttypes.models import ContentType
from django.contrib.sites.models import Site
from django.db import models
from django.test import TestCase, override_settings
from django.test.utils import isolate_apps
from .models import (
Article, Author, ModelWithNullFKToSite, SchemeIncludedURL,
Site as MockSite,
)
@override_settings(ROOT_URLCONF='contenttypes_tests.urls')
class ContentTypesViewsTests(TestCase):
@classmethod
def setUpTestData(cls):
# Don't use the manager to ensure the site exists with pk=1, regardless
# of whether or not it already exists.
cls.site1 = Site(pk=1, domain='testserver', name='testserver')
cls.site1.save()
cls.author1 = Author.objects.create(name='Boris')
cls.article1 = Article.objects.create(
title='Old Article', slug='old_article', author=cls.author1,
date_created=datetime.datetime(2001, 1, 1, 21, 22, 23),
)
cls.article2 = Article.objects.create(
title='Current Article', slug='current_article', author=cls.author1,
date_created=datetime.datetime(2007, 9, 17, 21, 22, 23),
)
cls.article3 = Article.objects.create(
title='Future Article', slug='future_article', author=cls.author1,
date_created=datetime.datetime(3000, 1, 1, 21, 22, 23),
)
cls.scheme1 = SchemeIncludedURL.objects.create(url='http://test_scheme_included_http/')
cls.scheme2 = SchemeIncludedURL.objects.create(url='https://test_scheme_included_https/')
cls.scheme3 = SchemeIncludedURL.objects.create(url='//test_default_scheme_kept/')
def setUp(self):
Site.objects.clear_cache()
def test_shortcut_with_absolute_url(self):
"Can view a shortcut for an Author object that has a get_absolute_url method"
for obj in Author.objects.all():
short_url = '/shortcut/%s/%s/' % (ContentType.objects.get_for_model(Author).id, obj.pk)
response = self.client.get(short_url)
self.assertRedirects(response, 'http://testserver%s' % obj.get_absolute_url(), target_status_code=404)
def test_shortcut_with_absolute_url_including_scheme(self):
"""
Can view a shortcut when object's get_absolute_url returns a full URL
the tested URLs are: "http://...", "https://..." and "//..."
"""
for obj in SchemeIncludedURL.objects.all():
short_url = '/shortcut/%s/%s/' % (ContentType.objects.get_for_model(SchemeIncludedURL).id, obj.pk)
response = self.client.get(short_url)
self.assertRedirects(response, obj.get_absolute_url(), fetch_redirect_response=False)
def test_shortcut_no_absolute_url(self):
"""
Shortcuts for an object that has no get_absolute_url() method raise
404.
"""
for obj in Article.objects.all():
short_url = '/shortcut/%s/%s/' % (ContentType.objects.get_for_model(Article).id, obj.pk)
response = self.client.get(short_url)
self.assertEqual(response.status_code, 404)
def test_wrong_type_pk(self):
short_url = '/shortcut/%s/%s/' % (ContentType.objects.get_for_model(Author).id, 'nobody/expects')
response = self.client.get(short_url)
self.assertEqual(response.status_code, 404)
def test_shortcut_bad_pk(self):
short_url = '/shortcut/%s/%s/' % (ContentType.objects.get_for_model(Author).id, '42424242')
response = self.client.get(short_url)
self.assertEqual(response.status_code, 404)
def test_nonint_content_type(self):
an_author = Author.objects.all()[0]
short_url = '/shortcut/%s/%s/' % ('spam', an_author.pk)
response = self.client.get(short_url)
self.assertEqual(response.status_code, 404)
def test_bad_content_type(self):
an_author = Author.objects.all()[0]
short_url = '/shortcut/%s/%s/' % (42424242, an_author.pk)
response = self.client.get(short_url)
self.assertEqual(response.status_code, 404)
@mock.patch('django.apps.apps.get_model')
def test_shortcut_view_with_null_site_fk(self, get_model):
"""
The shortcut view works if a model's ForeignKey to site is None.
"""
get_model.side_effect = lambda *args, **kwargs: MockSite if args[0] == 'sites.Site' else ModelWithNullFKToSite
obj = ModelWithNullFKToSite.objects.create(title='title')
url = '/shortcut/%s/%s/' % (ContentType.objects.get_for_model(ModelWithNullFKToSite).id, obj.pk)
response = self.client.get(url)
self.assertRedirects(response, '%s' % obj.get_absolute_url(), fetch_redirect_response=False)
@isolate_apps('contenttypes_tests')
def test_create_contenttype_on_the_spot(self):
"""
ContentTypeManager.get_for_model() creates the corresponding content
type if it doesn't exist in the database.
"""
class ModelCreatedOnTheFly(models.Model):
name = models.CharField()
class Meta:
verbose_name = 'a model created on the fly'
ct = ContentType.objects.get_for_model(ModelCreatedOnTheFly)
self.assertEqual(ct.app_label, 'contenttypes_tests')
self.assertEqual(ct.model, 'modelcreatedonthefly')
self.assertEqual(str(ct), 'modelcreatedonthefly')

View File

@ -1,530 +0,0 @@
import datetime
from unittest import mock
from django.apps.registry import Apps, apps
from django.conf import settings
from django.contrib.contenttypes import management as contenttypes_management
from django.contrib.contenttypes.fields import (
GenericForeignKey, GenericRelation,
)
from django.contrib.contenttypes.models import ContentType
from django.contrib.sites.models import Site
from django.core import checks, management
from django.core.management import call_command
from django.db import connections, migrations, models
from django.test import (
SimpleTestCase, TestCase, TransactionTestCase, override_settings,
)
from django.test.utils import captured_stdout, isolate_apps
from .models import (
Article, Author, ModelWithNullFKToSite, Post, SchemeIncludedURL,
Site as MockSite,
)
@override_settings(ROOT_URLCONF='contenttypes_tests.urls')
class ContentTypesViewsTests(TestCase):
@classmethod
def setUpTestData(cls):
# don't use the manager because we want to ensure the site exists
# with pk=1, regardless of whether or not it already exists.
cls.site1 = Site(pk=1, domain='testserver', name='testserver')
cls.site1.save()
cls.author1 = Author.objects.create(name='Boris')
cls.article1 = Article.objects.create(
title='Old Article', slug='old_article', author=cls.author1,
date_created=datetime.datetime(2001, 1, 1, 21, 22, 23)
)
cls.article2 = Article.objects.create(
title='Current Article', slug='current_article', author=cls.author1,
date_created=datetime.datetime(2007, 9, 17, 21, 22, 23)
)
cls.article3 = Article.objects.create(
title='Future Article', slug='future_article', author=cls.author1,
date_created=datetime.datetime(3000, 1, 1, 21, 22, 23)
)
cls.scheme1 = SchemeIncludedURL.objects.create(url='http://test_scheme_included_http/')
cls.scheme2 = SchemeIncludedURL.objects.create(url='https://test_scheme_included_https/')
cls.scheme3 = SchemeIncludedURL.objects.create(url='//test_default_scheme_kept/')
def setUp(self):
Site.objects.clear_cache()
def test_shortcut_with_absolute_url(self):
"Can view a shortcut for an Author object that has a get_absolute_url method"
for obj in Author.objects.all():
short_url = '/shortcut/%s/%s/' % (ContentType.objects.get_for_model(Author).id, obj.pk)
response = self.client.get(short_url)
self.assertRedirects(response, 'http://testserver%s' % obj.get_absolute_url(),
status_code=302, target_status_code=404)
def test_shortcut_with_absolute_url_including_scheme(self):
"""
Can view a shortcut when object's get_absolute_url returns a full URL
the tested URLs are: "http://...", "https://..." and "//..."
"""
for obj in SchemeIncludedURL.objects.all():
short_url = '/shortcut/%s/%s/' % (ContentType.objects.get_for_model(SchemeIncludedURL).id, obj.pk)
response = self.client.get(short_url)
self.assertRedirects(response, obj.get_absolute_url(),
status_code=302,
fetch_redirect_response=False)
def test_shortcut_no_absolute_url(self):
"Shortcuts for an object that has no get_absolute_url method raises 404"
for obj in Article.objects.all():
short_url = '/shortcut/%s/%s/' % (ContentType.objects.get_for_model(Article).id, obj.pk)
response = self.client.get(short_url)
self.assertEqual(response.status_code, 404)
def test_wrong_type_pk(self):
short_url = '/shortcut/%s/%s/' % (ContentType.objects.get_for_model(Author).id, 'nobody/expects')
response = self.client.get(short_url)
self.assertEqual(response.status_code, 404)
def test_shortcut_bad_pk(self):
short_url = '/shortcut/%s/%s/' % (ContentType.objects.get_for_model(Author).id, '42424242')
response = self.client.get(short_url)
self.assertEqual(response.status_code, 404)
def test_nonint_content_type(self):
an_author = Author.objects.all()[0]
short_url = '/shortcut/%s/%s/' % ('spam', an_author.pk)
response = self.client.get(short_url)
self.assertEqual(response.status_code, 404)
def test_bad_content_type(self):
an_author = Author.objects.all()[0]
short_url = '/shortcut/%s/%s/' % (42424242, an_author.pk)
response = self.client.get(short_url)
self.assertEqual(response.status_code, 404)
@mock.patch('django.apps.apps.get_model')
def test_shortcut_view_with_null_site_fk(self, get_model):
"""
The shortcut view works if a model's ForeignKey to site is None.
"""
get_model.side_effect = lambda *args, **kwargs: MockSite if args[0] == 'sites.Site' else ModelWithNullFKToSite
obj = ModelWithNullFKToSite.objects.create(title='title')
url = '/shortcut/%s/%s/' % (ContentType.objects.get_for_model(ModelWithNullFKToSite).id, obj.pk)
response = self.client.get(url)
self.assertRedirects(
response, '%s' % obj.get_absolute_url(),
fetch_redirect_response=False,
)
def test_create_contenttype_on_the_spot(self):
"""
Make sure ContentTypeManager.get_for_model creates the corresponding
content type if it doesn't exist in the database (for some reason).
"""
class ModelCreatedOnTheFly(models.Model):
name = models.CharField()
class Meta:
verbose_name = 'a model created on the fly'
app_label = 'my_great_app'
apps = Apps()
ct = ContentType.objects.get_for_model(ModelCreatedOnTheFly)
self.assertEqual(ct.app_label, 'my_great_app')
self.assertEqual(ct.model, 'modelcreatedonthefly')
self.assertEqual(str(ct), 'modelcreatedonthefly')
@override_settings(SILENCED_SYSTEM_CHECKS=['fields.W342']) # ForeignKey(unique=True)
@isolate_apps('contenttypes_tests', attr_name='apps')
class GenericForeignKeyTests(SimpleTestCase):
def test_str(self):
class Model(models.Model):
field = GenericForeignKey()
self.assertEqual(str(Model.field), "contenttypes_tests.Model.field")
def test_missing_content_type_field(self):
class TaggedItem(models.Model):
# no content_type field
object_id = models.PositiveIntegerField()
content_object = GenericForeignKey()
errors = TaggedItem.content_object.check()
expected = [
checks.Error(
"The GenericForeignKey content type references the nonexistent field 'TaggedItem.content_type'.",
obj=TaggedItem.content_object,
id='contenttypes.E002',
)
]
self.assertEqual(errors, expected)
def test_invalid_content_type_field(self):
class Model(models.Model):
content_type = models.IntegerField() # should be ForeignKey
object_id = models.PositiveIntegerField()
content_object = GenericForeignKey(
'content_type', 'object_id')
errors = Model.content_object.check()
expected = [
checks.Error(
"'Model.content_type' is not a ForeignKey.",
hint=(
"GenericForeignKeys must use a ForeignKey to "
"'contenttypes.ContentType' as the 'content_type' field."
),
obj=Model.content_object,
id='contenttypes.E003',
)
]
self.assertEqual(errors, expected)
def test_content_type_field_pointing_to_wrong_model(self):
class Model(models.Model):
content_type = models.ForeignKey('self', models.CASCADE) # should point to ContentType
object_id = models.PositiveIntegerField()
content_object = GenericForeignKey(
'content_type', 'object_id')
errors = Model.content_object.check()
expected = [
checks.Error(
"'Model.content_type' is not a ForeignKey to 'contenttypes.ContentType'.",
hint=(
"GenericForeignKeys must use a ForeignKey to "
"'contenttypes.ContentType' as the 'content_type' field."
),
obj=Model.content_object,
id='contenttypes.E004',
)
]
self.assertEqual(errors, expected)
def test_missing_object_id_field(self):
class TaggedItem(models.Model):
content_type = models.ForeignKey(ContentType, models.CASCADE)
# missing object_id field
content_object = GenericForeignKey()
errors = TaggedItem.content_object.check()
expected = [
checks.Error(
"The GenericForeignKey object ID references the nonexistent field 'object_id'.",
obj=TaggedItem.content_object,
id='contenttypes.E001',
)
]
self.assertEqual(errors, expected)
def test_field_name_ending_with_underscore(self):
class Model(models.Model):
content_type = models.ForeignKey(ContentType, models.CASCADE)
object_id = models.PositiveIntegerField()
content_object_ = GenericForeignKey(
'content_type', 'object_id')
errors = Model.content_object_.check()
expected = [
checks.Error(
'Field names must not end with an underscore.',
obj=Model.content_object_,
id='fields.E001',
)
]
self.assertEqual(errors, expected)
@override_settings(INSTALLED_APPS=['django.contrib.auth', 'django.contrib.contenttypes', 'contenttypes_tests'])
def test_generic_foreign_key_checks_are_performed(self):
class Model(models.Model):
content_object = GenericForeignKey()
with mock.patch.object(GenericForeignKey, 'check') as check:
checks.run_checks(app_configs=self.apps.get_app_configs())
check.assert_called_once_with()
@isolate_apps('contenttypes_tests')
class GenericRelationshipTests(SimpleTestCase):
def test_valid_generic_relationship(self):
class TaggedItem(models.Model):
content_type = models.ForeignKey(ContentType, models.CASCADE)
object_id = models.PositiveIntegerField()
content_object = GenericForeignKey()
class Bookmark(models.Model):
tags = GenericRelation('TaggedItem')
errors = Bookmark.tags.field.check()
self.assertEqual(errors, [])
def test_valid_generic_relationship_with_explicit_fields(self):
class TaggedItem(models.Model):
custom_content_type = models.ForeignKey(ContentType, models.CASCADE)
custom_object_id = models.PositiveIntegerField()
content_object = GenericForeignKey(
'custom_content_type', 'custom_object_id')
class Bookmark(models.Model):
tags = GenericRelation(
'TaggedItem',
content_type_field='custom_content_type',
object_id_field='custom_object_id',
)
errors = Bookmark.tags.field.check()
self.assertEqual(errors, [])
def test_pointing_to_missing_model(self):
class Model(models.Model):
rel = GenericRelation('MissingModel')
errors = Model.rel.field.check()
expected = [
checks.Error(
"Field defines a relation with model 'MissingModel', "
"which is either not installed, or is abstract.",
obj=Model.rel.field,
id='fields.E300',
)
]
self.assertEqual(errors, expected)
def test_valid_self_referential_generic_relationship(self):
class Model(models.Model):
rel = GenericRelation('Model')
content_type = models.ForeignKey(ContentType, models.CASCADE)
object_id = models.PositiveIntegerField()
content_object = GenericForeignKey(
'content_type', 'object_id')
errors = Model.rel.field.check()
self.assertEqual(errors, [])
def test_missing_generic_foreign_key(self):
class TaggedItem(models.Model):
content_type = models.ForeignKey(ContentType, models.CASCADE)
object_id = models.PositiveIntegerField()
class Bookmark(models.Model):
tags = GenericRelation('TaggedItem')
errors = Bookmark.tags.field.check()
expected = [
checks.Error(
"The GenericRelation defines a relation with the model "
"'contenttypes_tests.TaggedItem', but that model does not have a "
"GenericForeignKey.",
obj=Bookmark.tags.field,
id='contenttypes.E004',
)
]
self.assertEqual(errors, expected)
@override_settings(TEST_SWAPPED_MODEL='contenttypes_tests.Replacement')
def test_pointing_to_swapped_model(self):
class Replacement(models.Model):
pass
class SwappedModel(models.Model):
content_type = models.ForeignKey(ContentType, models.CASCADE)
object_id = models.PositiveIntegerField()
content_object = GenericForeignKey()
class Meta:
swappable = 'TEST_SWAPPED_MODEL'
class Model(models.Model):
rel = GenericRelation('SwappedModel')
errors = Model.rel.field.check()
expected = [
checks.Error(
"Field defines a relation with the model "
"'contenttypes_tests.SwappedModel', "
"which has been swapped out.",
hint="Update the relation to point at 'settings.TEST_SWAPPED_MODEL'.",
obj=Model.rel.field,
id='fields.E301',
)
]
self.assertEqual(errors, expected)
def test_field_name_ending_with_underscore(self):
class TaggedItem(models.Model):
content_type = models.ForeignKey(ContentType, models.CASCADE)
object_id = models.PositiveIntegerField()
content_object = GenericForeignKey()
class InvalidBookmark(models.Model):
tags_ = GenericRelation('TaggedItem')
errors = InvalidBookmark.tags_.field.check()
expected = [
checks.Error(
'Field names must not end with an underscore.',
obj=InvalidBookmark.tags_.field,
id='fields.E001',
)
]
self.assertEqual(errors, expected)
class UpdateContentTypesTests(TestCase):
def setUp(self):
self.before_count = ContentType.objects.count()
self.content_type = ContentType.objects.create(app_label='contenttypes_tests', model='Fake')
self.app_config = apps.get_app_config('contenttypes_tests')
def test_interactive_true_with_dependent_objects(self):
"""
interactive mode of remove_stale_contenttypes (the default) should
delete stale contenttypes and warn of dependent objects.
"""
post = Post.objects.create(title='post', content_type=self.content_type)
# A related object is needed to show that a custom collector with
# can_fast_delete=False is needed.
ModelWithNullFKToSite.objects.create(post=post)
with mock.patch('builtins.input', return_value='yes'):
with captured_stdout() as stdout:
call_command('remove_stale_contenttypes', verbosity=2, stdout=stdout)
self.assertEqual(Post.objects.count(), 0)
output = stdout.getvalue()
self.assertIn('- Content type for contenttypes_tests.Fake', output)
self.assertIn('- 1 contenttypes_tests.Post object(s)', output)
self.assertIn('- 1 contenttypes_tests.ModelWithNullFKToSite', output)
self.assertIn('Deleting stale content type', output)
self.assertEqual(ContentType.objects.count(), self.before_count)
def test_interactive_true_without_dependent_objects(self):
"""
interactive mode of remove_stale_contenttypes (the default) should
delete stale contenttypes even if there aren't any dependent objects.
"""
with mock.patch('builtins.input', return_value='yes'):
with captured_stdout() as stdout:
call_command('remove_stale_contenttypes', verbosity=2)
self.assertIn("Deleting stale content type", stdout.getvalue())
self.assertEqual(ContentType.objects.count(), self.before_count)
def test_interactive_false(self):
"""
non-interactive mode of remove_stale_contenttypes shouldn't delete
stale content types.
"""
with captured_stdout() as stdout:
call_command('remove_stale_contenttypes', interactive=False, verbosity=2)
self.assertIn("Stale content types remain.", stdout.getvalue())
self.assertEqual(ContentType.objects.count(), self.before_count + 1)
def test_unavailable_content_type_model(self):
"""
A ContentType shouldn't be created if the model isn't available.
"""
apps = Apps()
with self.assertNumQueries(0):
contenttypes_management.create_contenttypes(self.app_config, interactive=False, verbosity=0, apps=apps)
self.assertEqual(ContentType.objects.count(), self.before_count + 1)
class TestRouter:
def db_for_read(self, model, **hints):
return 'other'
def db_for_write(self, model, **hints):
return 'default'
@override_settings(DATABASE_ROUTERS=[TestRouter()])
class ContentTypesMultidbTestCase(TestCase):
def setUp(self):
# Whenever a test starts executing, only the "default" database is
# connected. We explicitly connect to the "other" database here. If we
# don't do it, then it will be implicitly connected later when we query
# it, but in that case some database backends may automatically perform
# extra queries upon connecting (notably mysql executes
# "SET SQL_AUTO_IS_NULL = 0"), which will affect assertNumQueries().
connections['other'].ensure_connection()
def test_multidb(self):
"""
When using multiple databases, ContentType.objects.get_for_model() uses
db_for_read().
"""
ContentType.objects.clear_cache()
with self.assertNumQueries(0, using='default'), \
self.assertNumQueries(1, using='other'):
ContentType.objects.get_for_model(Author)
@override_settings(
MIGRATION_MODULES=dict(settings.MIGRATION_MODULES, contenttypes_tests='contenttypes_tests.operations_migrations'),
)
class ContentTypeOperationsTests(TransactionTestCase):
available_apps = [
'contenttypes_tests',
'django.contrib.contenttypes',
]
def setUp(self):
app_config = apps.get_app_config('contenttypes_tests')
models.signals.post_migrate.connect(self.assertOperationsInjected, sender=app_config)
def tearDown(self):
app_config = apps.get_app_config('contenttypes_tests')
models.signals.post_migrate.disconnect(self.assertOperationsInjected, sender=app_config)
def assertOperationsInjected(self, plan, **kwargs):
for migration, _backward in plan:
operations = iter(migration.operations)
for operation in operations:
if isinstance(operation, migrations.RenameModel):
next_operation = next(operations)
self.assertIsInstance(next_operation, contenttypes_management.RenameContentType)
self.assertEqual(next_operation.app_label, migration.app_label)
self.assertEqual(next_operation.old_model, operation.old_name_lower)
self.assertEqual(next_operation.new_model, operation.new_name_lower)
def test_existing_content_type_rename(self):
ContentType.objects.create(app_label='contenttypes_tests', model='foo')
management.call_command(
'migrate', 'contenttypes_tests', database='default', interactive=False, verbosity=0,
)
self.assertFalse(ContentType.objects.filter(app_label='contenttypes_tests', model='foo').exists())
self.assertTrue(ContentType.objects.filter(app_label='contenttypes_tests', model='renamedfoo').exists())
management.call_command(
'migrate', 'contenttypes_tests', 'zero', database='default', interactive=False, verbosity=0,
)
self.assertTrue(ContentType.objects.filter(app_label='contenttypes_tests', model='foo').exists())
self.assertFalse(ContentType.objects.filter(app_label='contenttypes_tests', model='renamedfoo').exists())
def test_missing_content_type_rename_ignore(self):
management.call_command(
'migrate', 'contenttypes_tests', database='default', interactive=False, verbosity=0,
)
self.assertFalse(ContentType.objects.filter(app_label='contenttypes_tests', model='foo').exists())
self.assertTrue(ContentType.objects.filter(app_label='contenttypes_tests', model='renamedfoo').exists())
management.call_command(
'migrate', 'contenttypes_tests', 'zero', database='default', interactive=False, verbosity=0,
)
self.assertTrue(ContentType.objects.filter(app_label='contenttypes_tests', model='foo').exists())
self.assertFalse(ContentType.objects.filter(app_label='contenttypes_tests', model='renamedfoo').exists())
def test_content_type_rename_conflict(self):
ContentType.objects.create(app_label='contenttypes_tests', model='foo')
ContentType.objects.create(app_label='contenttypes_tests', model='renamedfoo')
management.call_command(
'migrate', 'contenttypes_tests', database='default', interactive=False, verbosity=0,
)
self.assertTrue(ContentType.objects.filter(app_label='contenttypes_tests', model='foo').exists())
self.assertTrue(ContentType.objects.filter(app_label='contenttypes_tests', model='renamedfoo').exists())
management.call_command(
'migrate', 'contenttypes_tests', 'zero', database='default', interactive=False, verbosity=0,
)
self.assertTrue(ContentType.objects.filter(app_label='contenttypes_tests', model='foo').exists())
self.assertTrue(ContentType.objects.filter(app_label='contenttypes_tests', model='renamedfoo').exists())