From aa14528910df0d35ad43a6f8191e3bd2d0f1746e Mon Sep 17 00:00:00 2001
From: Tim Graham <timograham@gmail.com>
Date: Thu, 2 Feb 2017 14:11:09 -0500
Subject: [PATCH] Split up contenttypes_tests.

---
 tests/contenttypes_tests/test_checks.py     | 218 ++++++++
 tests/contenttypes_tests/test_fields.py     |  13 +
 tests/contenttypes_tests/test_management.py |  65 +++
 tests/contenttypes_tests/test_models.py     |  43 +-
 tests/contenttypes_tests/test_operations.py |  66 +++
 tests/contenttypes_tests/test_views.py      | 121 +++++
 tests/contenttypes_tests/tests.py           | 530 --------------------
 7 files changed, 518 insertions(+), 538 deletions(-)
 create mode 100644 tests/contenttypes_tests/test_checks.py
 create mode 100644 tests/contenttypes_tests/test_fields.py
 create mode 100644 tests/contenttypes_tests/test_management.py
 create mode 100644 tests/contenttypes_tests/test_operations.py
 create mode 100644 tests/contenttypes_tests/test_views.py
 delete mode 100644 tests/contenttypes_tests/tests.py

diff --git a/tests/contenttypes_tests/test_checks.py b/tests/contenttypes_tests/test_checks.py
new file mode 100644
index 0000000000..6b33107de2
--- /dev/null
+++ b/tests/contenttypes_tests/test_checks.py
@@ -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',
+            )
+        ])
diff --git a/tests/contenttypes_tests/test_fields.py b/tests/contenttypes_tests/test_fields.py
new file mode 100644
index 0000000000..88de0ec7f2
--- /dev/null
+++ b/tests/contenttypes_tests/test_fields.py
@@ -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')
diff --git a/tests/contenttypes_tests/test_management.py b/tests/contenttypes_tests/test_management.py
new file mode 100644
index 0000000000..110d183633
--- /dev/null
+++ b/tests/contenttypes_tests/test_management.py
@@ -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)
diff --git a/tests/contenttypes_tests/test_models.py b/tests/contenttypes_tests/test_models.py
index 4c16e0b4aa..a9361c262f 100644
--- a/tests/contenttypes_tests/test_models.py
+++ b/tests/contenttypes_tests/test_models.py
@@ -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)
diff --git a/tests/contenttypes_tests/test_operations.py b/tests/contenttypes_tests/test_operations.py
new file mode 100644
index 0000000000..bdf492fe2a
--- /dev/null
+++ b/tests/contenttypes_tests/test_operations.py
@@ -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())
diff --git a/tests/contenttypes_tests/test_views.py b/tests/contenttypes_tests/test_views.py
new file mode 100644
index 0000000000..d1165c6818
--- /dev/null
+++ b/tests/contenttypes_tests/test_views.py
@@ -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')
diff --git a/tests/contenttypes_tests/tests.py b/tests/contenttypes_tests/tests.py
deleted file mode 100644
index 14e14e0913..0000000000
--- a/tests/contenttypes_tests/tests.py
+++ /dev/null
@@ -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())