summaryrefslogtreecommitdiff
path: root/tests/contenttypes_tests/test_management.py
blob: eb472d80cefe484aa37684541a574074fc32197c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
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, modify_settings
from django.test.utils import captured_stdout

from .models import ModelWithNullFKToSite, Post


@modify_settings(INSTALLED_APPS={"append": ["empty_models", "no_models"]})
class RemoveStaleContentTypesTests(TestCase):
    # Speed up tests by avoiding retrieving ContentTypes for all test apps.
    available_apps = [
        "contenttypes_tests",
        "empty_models",
        "no_models",
        "django.contrib.contenttypes",
    ]

    @classmethod
    def setUpTestData(cls):
        with captured_stdout():
            call_command(
                "remove_stale_contenttypes",
                interactive=False,
                include_stale_apps=True,
                verbosity=2,
            )
        cls.before_count = ContentType.objects.count()
        cls.content_type = ContentType.objects.create(
            app_label="contenttypes_tests", model="Fake"
        )

    def setUp(self):
        self.app_config = apps.get_app_config("contenttypes_tests")

    def test_interactive_true_with_dependent_objects(self):
        """
        interactive mode (the default) deletes stale content types and warns 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 deletes stale content types 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 deletes stale content types."""
        with captured_stdout() as stdout:
            call_command("remove_stale_contenttypes", interactive=False, verbosity=2)
        self.assertIn("Deleting stale content type", stdout.getvalue())
        self.assertEqual(ContentType.objects.count(), self.before_count)

    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)

    @modify_settings(INSTALLED_APPS={"remove": ["empty_models"]})
    def test_contenttypes_removed_in_installed_apps_without_models(self):
        ContentType.objects.create(app_label="empty_models", model="Fake 1")
        ContentType.objects.create(app_label="no_models", model="Fake 2")
        with mock.patch(
            "builtins.input", return_value="yes"
        ), captured_stdout() as stdout:
            call_command("remove_stale_contenttypes", verbosity=2)
        self.assertNotIn(
            "Deleting stale content type 'empty_models | Fake 1'",
            stdout.getvalue(),
        )
        self.assertIn(
            "Deleting stale content type 'no_models | Fake 2'",
            stdout.getvalue(),
        )
        self.assertEqual(ContentType.objects.count(), self.before_count + 1)

    @modify_settings(INSTALLED_APPS={"remove": ["empty_models"]})
    def test_contenttypes_removed_for_apps_not_in_installed_apps(self):
        ContentType.objects.create(app_label="empty_models", model="Fake 1")
        ContentType.objects.create(app_label="no_models", model="Fake 2")
        with mock.patch(
            "builtins.input", return_value="yes"
        ), captured_stdout() as stdout:
            call_command(
                "remove_stale_contenttypes", include_stale_apps=True, verbosity=2
            )
        self.assertIn(
            "Deleting stale content type 'empty_models | Fake 1'",
            stdout.getvalue(),
        )
        self.assertIn(
            "Deleting stale content type 'no_models | Fake 2'",
            stdout.getvalue(),
        )
        self.assertEqual(ContentType.objects.count(), self.before_count)