1. from django.contrib.contenttypes.models import ContentType, ContentTypeManager
    
  2. from django.db import models
    
  3. from django.test import TestCase, override_settings
    
  4. from django.test.utils import isolate_apps
    
  5. 
    
  6. from .models import Author, ConcreteModel, FooWithUrl, ProxyModel
    
  7. 
    
  8. 
    
  9. class ContentTypesTests(TestCase):
    
  10.     def setUp(self):
    
  11.         ContentType.objects.clear_cache()
    
  12. 
    
  13.     def tearDown(self):
    
  14.         ContentType.objects.clear_cache()
    
  15. 
    
  16.     def test_lookup_cache(self):
    
  17.         """
    
  18.         The content type cache (see ContentTypeManager) works correctly.
    
  19.         Lookups for a particular content type -- by model, ID, or natural key
    
  20.         -- should hit the database only on the first lookup.
    
  21.         """
    
  22.         # At this point, a lookup for a ContentType should hit the DB
    
  23.         with self.assertNumQueries(1):
    
  24.             ContentType.objects.get_for_model(ContentType)
    
  25. 
    
  26.         # A second hit, though, won't hit the DB, nor will a lookup by ID
    
  27.         # or natural key
    
  28.         with self.assertNumQueries(0):
    
  29.             ct = ContentType.objects.get_for_model(ContentType)
    
  30.         with self.assertNumQueries(0):
    
  31.             ContentType.objects.get_for_id(ct.id)
    
  32.         with self.assertNumQueries(0):
    
  33.             ContentType.objects.get_by_natural_key("contenttypes", "contenttype")
    
  34. 
    
  35.         # Once we clear the cache, another lookup will again hit the DB
    
  36.         ContentType.objects.clear_cache()
    
  37.         with self.assertNumQueries(1):
    
  38.             ContentType.objects.get_for_model(ContentType)
    
  39. 
    
  40.         # The same should happen with a lookup by natural key
    
  41.         ContentType.objects.clear_cache()
    
  42.         with self.assertNumQueries(1):
    
  43.             ContentType.objects.get_by_natural_key("contenttypes", "contenttype")
    
  44.         # And a second hit shouldn't hit the DB
    
  45.         with self.assertNumQueries(0):
    
  46.             ContentType.objects.get_by_natural_key("contenttypes", "contenttype")
    
  47. 
    
  48.     def test_get_for_models_creation(self):
    
  49.         ContentType.objects.all().delete()
    
  50.         with self.assertNumQueries(4):
    
  51.             cts = ContentType.objects.get_for_models(
    
  52.                 ContentType, FooWithUrl, ProxyModel, ConcreteModel
    
  53.             )
    
  54.         self.assertEqual(
    
  55.             cts,
    
  56.             {
    
  57.                 ContentType: ContentType.objects.get_for_model(ContentType),
    
  58.                 FooWithUrl: ContentType.objects.get_for_model(FooWithUrl),
    
  59.                 ProxyModel: ContentType.objects.get_for_model(ProxyModel),
    
  60.                 ConcreteModel: ContentType.objects.get_for_model(ConcreteModel),
    
  61.             },
    
  62.         )
    
  63. 
    
  64.     def test_get_for_models_empty_cache(self):
    
  65.         # Empty cache.
    
  66.         with self.assertNumQueries(1):
    
  67.             cts = ContentType.objects.get_for_models(
    
  68.                 ContentType, FooWithUrl, ProxyModel, ConcreteModel
    
  69.             )
    
  70.         self.assertEqual(
    
  71.             cts,
    
  72.             {
    
  73.                 ContentType: ContentType.objects.get_for_model(ContentType),
    
  74.                 FooWithUrl: ContentType.objects.get_for_model(FooWithUrl),
    
  75.                 ProxyModel: ContentType.objects.get_for_model(ProxyModel),
    
  76.                 ConcreteModel: ContentType.objects.get_for_model(ConcreteModel),
    
  77.             },
    
  78.         )
    
  79. 
    
  80.     def test_get_for_models_partial_cache(self):
    
  81.         # Partial cache
    
  82.         ContentType.objects.get_for_model(ContentType)
    
  83.         with self.assertNumQueries(1):
    
  84.             cts = ContentType.objects.get_for_models(ContentType, FooWithUrl)
    
  85.         self.assertEqual(
    
  86.             cts,
    
  87.             {
    
  88.                 ContentType: ContentType.objects.get_for_model(ContentType),
    
  89.                 FooWithUrl: ContentType.objects.get_for_model(FooWithUrl),
    
  90.             },
    
  91.         )
    
  92. 
    
  93.     def test_get_for_models_full_cache(self):
    
  94.         # Full cache
    
  95.         ContentType.objects.get_for_model(ContentType)
    
  96.         ContentType.objects.get_for_model(FooWithUrl)
    
  97.         with self.assertNumQueries(0):
    
  98.             cts = ContentType.objects.get_for_models(ContentType, FooWithUrl)
    
  99.         self.assertEqual(
    
  100.             cts,
    
  101.             {
    
  102.                 ContentType: ContentType.objects.get_for_model(ContentType),
    
  103.                 FooWithUrl: ContentType.objects.get_for_model(FooWithUrl),
    
  104.             },
    
  105.         )
    
  106. 
    
  107.     @isolate_apps("contenttypes_tests")
    
  108.     def test_get_for_model_create_contenttype(self):
    
  109.         """
    
  110.         ContentTypeManager.get_for_model() creates the corresponding content
    
  111.         type if it doesn't exist in the database.
    
  112.         """
    
  113. 
    
  114.         class ModelCreatedOnTheFly(models.Model):
    
  115.             name = models.CharField()
    
  116. 
    
  117.         ct = ContentType.objects.get_for_model(ModelCreatedOnTheFly)
    
  118.         self.assertEqual(ct.app_label, "contenttypes_tests")
    
  119.         self.assertEqual(ct.model, "modelcreatedonthefly")
    
  120.         self.assertEqual(str(ct), "modelcreatedonthefly")
    
  121. 
    
  122.     def test_get_for_concrete_model(self):
    
  123.         """
    
  124.         Make sure the `for_concrete_model` kwarg correctly works
    
  125.         with concrete, proxy and deferred models
    
  126.         """
    
  127.         concrete_model_ct = ContentType.objects.get_for_model(ConcreteModel)
    
  128.         self.assertEqual(
    
  129.             concrete_model_ct, ContentType.objects.get_for_model(ProxyModel)
    
  130.         )
    
  131.         self.assertEqual(
    
  132.             concrete_model_ct,
    
  133.             ContentType.objects.get_for_model(ConcreteModel, for_concrete_model=False),
    
  134.         )
    
  135. 
    
  136.         proxy_model_ct = ContentType.objects.get_for_model(
    
  137.             ProxyModel, for_concrete_model=False
    
  138.         )
    
  139.         self.assertNotEqual(concrete_model_ct, proxy_model_ct)
    
  140. 
    
  141.         # Make sure deferred model are correctly handled
    
  142.         ConcreteModel.objects.create(name="Concrete")
    
  143.         DeferredConcreteModel = ConcreteModel.objects.only("pk").get().__class__
    
  144.         DeferredProxyModel = ProxyModel.objects.only("pk").get().__class__
    
  145. 
    
  146.         self.assertEqual(
    
  147.             concrete_model_ct, ContentType.objects.get_for_model(DeferredConcreteModel)
    
  148.         )
    
  149.         self.assertEqual(
    
  150.             concrete_model_ct,
    
  151.             ContentType.objects.get_for_model(
    
  152.                 DeferredConcreteModel, for_concrete_model=False
    
  153.             ),
    
  154.         )
    
  155.         self.assertEqual(
    
  156.             concrete_model_ct, ContentType.objects.get_for_model(DeferredProxyModel)
    
  157.         )
    
  158.         self.assertEqual(
    
  159.             proxy_model_ct,
    
  160.             ContentType.objects.get_for_model(
    
  161.                 DeferredProxyModel, for_concrete_model=False
    
  162.             ),
    
  163.         )
    
  164. 
    
  165.     def test_get_for_concrete_models(self):
    
  166.         """
    
  167.         Make sure the `for_concrete_models` kwarg correctly works
    
  168.         with concrete, proxy and deferred models.
    
  169.         """
    
  170.         concrete_model_ct = ContentType.objects.get_for_model(ConcreteModel)
    
  171. 
    
  172.         cts = ContentType.objects.get_for_models(ConcreteModel, ProxyModel)
    
  173.         self.assertEqual(
    
  174.             cts,
    
  175.             {
    
  176.                 ConcreteModel: concrete_model_ct,
    
  177.                 ProxyModel: concrete_model_ct,
    
  178.             },
    
  179.         )
    
  180. 
    
  181.         proxy_model_ct = ContentType.objects.get_for_model(
    
  182.             ProxyModel, for_concrete_model=False
    
  183.         )
    
  184.         cts = ContentType.objects.get_for_models(
    
  185.             ConcreteModel, ProxyModel, for_concrete_models=False
    
  186.         )
    
  187.         self.assertEqual(
    
  188.             cts,
    
  189.             {
    
  190.                 ConcreteModel: concrete_model_ct,
    
  191.                 ProxyModel: proxy_model_ct,
    
  192.             },
    
  193.         )
    
  194. 
    
  195.         # Make sure deferred model are correctly handled
    
  196.         ConcreteModel.objects.create(name="Concrete")
    
  197.         DeferredConcreteModel = ConcreteModel.objects.only("pk").get().__class__
    
  198.         DeferredProxyModel = ProxyModel.objects.only("pk").get().__class__
    
  199. 
    
  200.         cts = ContentType.objects.get_for_models(
    
  201.             DeferredConcreteModel, DeferredProxyModel
    
  202.         )
    
  203.         self.assertEqual(
    
  204.             cts,
    
  205.             {
    
  206.                 DeferredConcreteModel: concrete_model_ct,
    
  207.                 DeferredProxyModel: concrete_model_ct,
    
  208.             },
    
  209.         )
    
  210. 
    
  211.         cts = ContentType.objects.get_for_models(
    
  212.             DeferredConcreteModel, DeferredProxyModel, for_concrete_models=False
    
  213.         )
    
  214.         self.assertEqual(
    
  215.             cts,
    
  216.             {
    
  217.                 DeferredConcreteModel: concrete_model_ct,
    
  218.                 DeferredProxyModel: proxy_model_ct,
    
  219.             },
    
  220.         )
    
  221. 
    
  222.     def test_cache_not_shared_between_managers(self):
    
  223.         with self.assertNumQueries(1):
    
  224.             ContentType.objects.get_for_model(ContentType)
    
  225.         with self.assertNumQueries(0):
    
  226.             ContentType.objects.get_for_model(ContentType)
    
  227.         other_manager = ContentTypeManager()
    
  228.         other_manager.model = ContentType
    
  229.         with self.assertNumQueries(1):
    
  230.             other_manager.get_for_model(ContentType)
    
  231.         with self.assertNumQueries(0):
    
  232.             other_manager.get_for_model(ContentType)
    
  233. 
    
  234.     def test_missing_model(self):
    
  235.         """
    
  236.         Displaying content types in admin (or anywhere) doesn't break on
    
  237.         leftover content type records in the DB for which no model is defined
    
  238.         anymore.
    
  239.         """
    
  240.         ct = ContentType.objects.create(
    
  241.             app_label="contenttypes",
    
  242.             model="OldModel",
    
  243.         )
    
  244.         self.assertEqual(str(ct), "OldModel")
    
  245.         self.assertIsNone(ct.model_class())
    
  246. 
    
  247.         # Stale ContentTypes can be fetched like any other object.
    
  248.         ct_fetched = ContentType.objects.get_for_id(ct.pk)
    
  249.         self.assertIsNone(ct_fetched.model_class())
    
  250. 
    
  251.     def test_missing_model_with_existing_model_name(self):
    
  252.         """
    
  253.         Displaying content types in admin (or anywhere) doesn't break on
    
  254.         leftover content type records in the DB for which no model is defined
    
  255.         anymore, even if a model with the same name exists in another app.
    
  256.         """
    
  257.         # Create a stale ContentType that matches the name of an existing
    
  258.         # model.
    
  259.         ContentType.objects.create(app_label="contenttypes", model="author")
    
  260.         ContentType.objects.clear_cache()
    
  261.         # get_for_models() should work as expected for existing models.
    
  262.         cts = ContentType.objects.get_for_models(ContentType, Author)
    
  263.         self.assertEqual(
    
  264.             cts,
    
  265.             {
    
  266.                 ContentType: ContentType.objects.get_for_model(ContentType),
    
  267.                 Author: ContentType.objects.get_for_model(Author),
    
  268.             },
    
  269.         )
    
  270. 
    
  271.     def test_str(self):
    
  272.         ct = ContentType.objects.get(app_label="contenttypes_tests", model="site")
    
  273.         self.assertEqual(str(ct), "contenttypes_tests | site")
    
  274. 
    
  275.     def test_app_labeled_name(self):
    
  276.         ct = ContentType.objects.get(app_label="contenttypes_tests", model="site")
    
  277.         self.assertEqual(ct.app_labeled_name, "contenttypes_tests | site")
    
  278. 
    
  279.     def test_app_labeled_name_unknown_model(self):
    
  280.         ct = ContentType(app_label="contenttypes_tests", model="unknown")
    
  281.         self.assertEqual(ct.app_labeled_name, "unknown")
    
  282. 
    
  283. 
    
  284. class TestRouter:
    
  285.     def db_for_read(self, model, **hints):
    
  286.         return "other"
    
  287. 
    
  288.     def db_for_write(self, model, **hints):
    
  289.         return "default"
    
  290. 
    
  291.     def allow_relation(self, obj1, obj2, **hints):
    
  292.         return True
    
  293. 
    
  294. 
    
  295. @override_settings(DATABASE_ROUTERS=[TestRouter()])
    
  296. class ContentTypesMultidbTests(TestCase):
    
  297.     databases = {"default", "other"}
    
  298. 
    
  299.     def test_multidb(self):
    
  300.         """
    
  301.         When using multiple databases, ContentType.objects.get_for_model() uses
    
  302.         db_for_read().
    
  303.         """
    
  304.         ContentType.objects.clear_cache()
    
  305.         with self.assertNumQueries(0, using="default"), self.assertNumQueries(
    
  306.             1, using="other"
    
  307.         ):
    
  308.             ContentType.objects.get_for_model(Author)