1. from datetime import datetime
    
  2. from decimal import Decimal
    
  3. 
    
  4. from django import forms
    
  5. from django.conf import settings
    
  6. from django.contrib import admin
    
  7. from django.contrib.admin import helpers
    
  8. from django.contrib.admin.utils import (
    
  9.     NestedObjects,
    
  10.     display_for_field,
    
  11.     display_for_value,
    
  12.     flatten,
    
  13.     flatten_fieldsets,
    
  14.     help_text_for_field,
    
  15.     label_for_field,
    
  16.     lookup_field,
    
  17.     quote,
    
  18. )
    
  19. from django.db import DEFAULT_DB_ALIAS, models
    
  20. from django.test import SimpleTestCase, TestCase, override_settings
    
  21. from django.utils.formats import localize
    
  22. from django.utils.safestring import mark_safe
    
  23. 
    
  24. from .models import Article, Car, Count, Event, EventGuide, Location, Site, Vehicle
    
  25. 
    
  26. 
    
  27. class NestedObjectsTests(TestCase):
    
  28.     """
    
  29.     Tests for ``NestedObject`` utility collection.
    
  30.     """
    
  31. 
    
  32.     @classmethod
    
  33.     def setUpTestData(cls):
    
  34.         cls.n = NestedObjects(using=DEFAULT_DB_ALIAS)
    
  35.         cls.objs = [Count.objects.create(num=i) for i in range(5)]
    
  36. 
    
  37.     def _check(self, target):
    
  38.         self.assertEqual(self.n.nested(lambda obj: obj.num), target)
    
  39. 
    
  40.     def _connect(self, i, j):
    
  41.         self.objs[i].parent = self.objs[j]
    
  42.         self.objs[i].save()
    
  43. 
    
  44.     def _collect(self, *indices):
    
  45.         self.n.collect([self.objs[i] for i in indices])
    
  46. 
    
  47.     def test_unrelated_roots(self):
    
  48.         self._connect(2, 1)
    
  49.         self._collect(0)
    
  50.         self._collect(1)
    
  51.         self._check([0, 1, [2]])
    
  52. 
    
  53.     def test_siblings(self):
    
  54.         self._connect(1, 0)
    
  55.         self._connect(2, 0)
    
  56.         self._collect(0)
    
  57.         self._check([0, [1, 2]])
    
  58. 
    
  59.     def test_non_added_parent(self):
    
  60.         self._connect(0, 1)
    
  61.         self._collect(0)
    
  62.         self._check([0])
    
  63. 
    
  64.     def test_cyclic(self):
    
  65.         self._connect(0, 2)
    
  66.         self._connect(1, 0)
    
  67.         self._connect(2, 1)
    
  68.         self._collect(0)
    
  69.         self._check([0, [1, [2]]])
    
  70. 
    
  71.     def test_queries(self):
    
  72.         self._connect(1, 0)
    
  73.         self._connect(2, 0)
    
  74.         # 1 query to fetch all children of 0 (1 and 2)
    
  75.         # 1 query to fetch all children of 1 and 2 (none)
    
  76.         # Should not require additional queries to populate the nested graph.
    
  77.         self.assertNumQueries(2, self._collect, 0)
    
  78. 
    
  79.     def test_on_delete_do_nothing(self):
    
  80.         """
    
  81.         The nested collector doesn't query for DO_NOTHING objects.
    
  82.         """
    
  83.         n = NestedObjects(using=DEFAULT_DB_ALIAS)
    
  84.         objs = [Event.objects.create()]
    
  85.         EventGuide.objects.create(event=objs[0])
    
  86.         with self.assertNumQueries(2):
    
  87.             # One for Location, one for Guest, and no query for EventGuide
    
  88.             n.collect(objs)
    
  89. 
    
  90.     def test_relation_on_abstract(self):
    
  91.         """
    
  92.         NestedObjects.collect() doesn't trip (AttributeError) on the special
    
  93.         notation for relations on abstract models (related_name that contains
    
  94.         %(app_label)s and/or %(class)s) (#21846).
    
  95.         """
    
  96.         n = NestedObjects(using=DEFAULT_DB_ALIAS)
    
  97.         Car.objects.create()
    
  98.         n.collect([Vehicle.objects.first()])
    
  99. 
    
  100. 
    
  101. class UtilsTests(SimpleTestCase):
    
  102.     empty_value = "-empty-"
    
  103. 
    
  104.     def test_values_from_lookup_field(self):
    
  105.         """
    
  106.         Regression test for #12654: lookup_field
    
  107.         """
    
  108.         SITE_NAME = "example.com"
    
  109.         TITLE_TEXT = "Some title"
    
  110.         CREATED_DATE = datetime.min
    
  111.         ADMIN_METHOD = "admin method"
    
  112.         SIMPLE_FUNCTION = "function"
    
  113.         INSTANCE_ATTRIBUTE = "attr"
    
  114. 
    
  115.         class MockModelAdmin:
    
  116.             def get_admin_value(self, obj):
    
  117.                 return ADMIN_METHOD
    
  118. 
    
  119.         def simple_function(obj):
    
  120.             return SIMPLE_FUNCTION
    
  121. 
    
  122.         site_obj = Site(domain=SITE_NAME)
    
  123.         article = Article(
    
  124.             site=site_obj,
    
  125.             title=TITLE_TEXT,
    
  126.             created=CREATED_DATE,
    
  127.         )
    
  128.         article.non_field = INSTANCE_ATTRIBUTE
    
  129. 
    
  130.         verifications = (
    
  131.             ("site", SITE_NAME),
    
  132.             ("created", localize(CREATED_DATE)),
    
  133.             ("title", TITLE_TEXT),
    
  134.             ("get_admin_value", ADMIN_METHOD),
    
  135.             (simple_function, SIMPLE_FUNCTION),
    
  136.             ("test_from_model", article.test_from_model()),
    
  137.             ("non_field", INSTANCE_ATTRIBUTE),
    
  138.         )
    
  139. 
    
  140.         mock_admin = MockModelAdmin()
    
  141.         for name, value in verifications:
    
  142.             field, attr, resolved_value = lookup_field(name, article, mock_admin)
    
  143. 
    
  144.             if field is not None:
    
  145.                 resolved_value = display_for_field(
    
  146.                     resolved_value, field, self.empty_value
    
  147.                 )
    
  148. 
    
  149.             self.assertEqual(value, resolved_value)
    
  150. 
    
  151.     def test_null_display_for_field(self):
    
  152.         """
    
  153.         Regression test for #12550: display_for_field should handle None
    
  154.         value.
    
  155.         """
    
  156.         display_value = display_for_field(None, models.CharField(), self.empty_value)
    
  157.         self.assertEqual(display_value, self.empty_value)
    
  158. 
    
  159.         display_value = display_for_field(
    
  160.             None, models.CharField(choices=((None, "test_none"),)), self.empty_value
    
  161.         )
    
  162.         self.assertEqual(display_value, "test_none")
    
  163. 
    
  164.         display_value = display_for_field(None, models.DateField(), self.empty_value)
    
  165.         self.assertEqual(display_value, self.empty_value)
    
  166. 
    
  167.         display_value = display_for_field(None, models.TimeField(), self.empty_value)
    
  168.         self.assertEqual(display_value, self.empty_value)
    
  169. 
    
  170.         display_value = display_for_field(
    
  171.             None, models.BooleanField(null=True), self.empty_value
    
  172.         )
    
  173.         expected = (
    
  174.             '<img src="%sadmin/img/icon-unknown.svg" alt="None" />'
    
  175.             % settings.STATIC_URL
    
  176.         )
    
  177.         self.assertHTMLEqual(display_value, expected)
    
  178. 
    
  179.         display_value = display_for_field(None, models.DecimalField(), self.empty_value)
    
  180.         self.assertEqual(display_value, self.empty_value)
    
  181. 
    
  182.         display_value = display_for_field(None, models.FloatField(), self.empty_value)
    
  183.         self.assertEqual(display_value, self.empty_value)
    
  184. 
    
  185.         display_value = display_for_field(None, models.JSONField(), self.empty_value)
    
  186.         self.assertEqual(display_value, self.empty_value)
    
  187. 
    
  188.     def test_json_display_for_field(self):
    
  189.         tests = [
    
  190.             ({"a": {"b": "c"}}, '{"a": {"b": "c"}}'),
    
  191.             (["a", "b"], '["a", "b"]'),
    
  192.             ("a", '"a"'),
    
  193.             ({"a": "你好 世界"}, '{"a": "你好 世界"}'),
    
  194.             ({("a", "b"): "c"}, "{('a', 'b'): 'c'}"),  # Invalid JSON.
    
  195.         ]
    
  196.         for value, display_value in tests:
    
  197.             with self.subTest(value=value):
    
  198.                 self.assertEqual(
    
  199.                     display_for_field(value, models.JSONField(), self.empty_value),
    
  200.                     display_value,
    
  201.                 )
    
  202. 
    
  203.     def test_number_formats_display_for_field(self):
    
  204.         display_value = display_for_field(
    
  205.             12345.6789, models.FloatField(), self.empty_value
    
  206.         )
    
  207.         self.assertEqual(display_value, "12345.6789")
    
  208. 
    
  209.         display_value = display_for_field(
    
  210.             Decimal("12345.6789"), models.DecimalField(), self.empty_value
    
  211.         )
    
  212.         self.assertEqual(display_value, "12345.6789")
    
  213. 
    
  214.         display_value = display_for_field(
    
  215.             12345, models.IntegerField(), self.empty_value
    
  216.         )
    
  217.         self.assertEqual(display_value, "12345")
    
  218. 
    
  219.     @override_settings(USE_THOUSAND_SEPARATOR=True)
    
  220.     def test_number_formats_with_thousand_separator_display_for_field(self):
    
  221.         display_value = display_for_field(
    
  222.             12345.6789, models.FloatField(), self.empty_value
    
  223.         )
    
  224.         self.assertEqual(display_value, "12,345.6789")
    
  225. 
    
  226.         display_value = display_for_field(
    
  227.             Decimal("12345.6789"), models.DecimalField(), self.empty_value
    
  228.         )
    
  229.         self.assertEqual(display_value, "12,345.6789")
    
  230. 
    
  231.         display_value = display_for_field(
    
  232.             12345, models.IntegerField(), self.empty_value
    
  233.         )
    
  234.         self.assertEqual(display_value, "12,345")
    
  235. 
    
  236.     def test_list_display_for_value(self):
    
  237.         display_value = display_for_value([1, 2, 3], self.empty_value)
    
  238.         self.assertEqual(display_value, "1, 2, 3")
    
  239. 
    
  240.         display_value = display_for_value(
    
  241.             [1, 2, "buckle", "my", "shoe"], self.empty_value
    
  242.         )
    
  243.         self.assertEqual(display_value, "1, 2, buckle, my, shoe")
    
  244. 
    
  245.     @override_settings(USE_THOUSAND_SEPARATOR=True)
    
  246.     def test_list_display_for_value_boolean(self):
    
  247.         self.assertEqual(
    
  248.             display_for_value(True, "", boolean=True),
    
  249.             '<img src="/static/admin/img/icon-yes.svg" alt="True">',
    
  250.         )
    
  251.         self.assertEqual(
    
  252.             display_for_value(False, "", boolean=True),
    
  253.             '<img src="/static/admin/img/icon-no.svg" alt="False">',
    
  254.         )
    
  255.         self.assertEqual(display_for_value(True, ""), "True")
    
  256.         self.assertEqual(display_for_value(False, ""), "False")
    
  257. 
    
  258.     def test_label_for_field(self):
    
  259.         """
    
  260.         Tests for label_for_field
    
  261.         """
    
  262.         self.assertEqual(label_for_field("title", Article), "title")
    
  263.         self.assertEqual(label_for_field("hist", Article), "History")
    
  264.         self.assertEqual(
    
  265.             label_for_field("hist", Article, return_attr=True), ("History", None)
    
  266.         )
    
  267. 
    
  268.         self.assertEqual(label_for_field("__str__", Article), "article")
    
  269. 
    
  270.         with self.assertRaisesMessage(
    
  271.             AttributeError, "Unable to lookup 'unknown' on Article"
    
  272.         ):
    
  273.             label_for_field("unknown", Article)
    
  274. 
    
  275.         def test_callable(obj):
    
  276.             return "nothing"
    
  277. 
    
  278.         self.assertEqual(label_for_field(test_callable, Article), "Test callable")
    
  279.         self.assertEqual(
    
  280.             label_for_field(test_callable, Article, return_attr=True),
    
  281.             ("Test callable", test_callable),
    
  282.         )
    
  283. 
    
  284.         self.assertEqual(label_for_field("test_from_model", Article), "Test from model")
    
  285.         self.assertEqual(
    
  286.             label_for_field("test_from_model", Article, return_attr=True),
    
  287.             ("Test from model", Article.test_from_model),
    
  288.         )
    
  289.         self.assertEqual(
    
  290.             label_for_field("test_from_model_with_override", Article),
    
  291.             "not What you Expect",
    
  292.         )
    
  293. 
    
  294.         self.assertEqual(label_for_field(lambda x: "nothing", Article), "--")
    
  295.         self.assertEqual(label_for_field("site_id", Article), "Site id")
    
  296. 
    
  297.         class MockModelAdmin:
    
  298.             @admin.display(description="not Really the Model")
    
  299.             def test_from_model(self, obj):
    
  300.                 return "nothing"
    
  301. 
    
  302.         self.assertEqual(
    
  303.             label_for_field("test_from_model", Article, model_admin=MockModelAdmin),
    
  304.             "not Really the Model",
    
  305.         )
    
  306.         self.assertEqual(
    
  307.             label_for_field(
    
  308.                 "test_from_model", Article, model_admin=MockModelAdmin, return_attr=True
    
  309.             ),
    
  310.             ("not Really the Model", MockModelAdmin.test_from_model),
    
  311.         )
    
  312. 
    
  313.     def test_label_for_field_form_argument(self):
    
  314.         class ArticleForm(forms.ModelForm):
    
  315.             extra_form_field = forms.BooleanField()
    
  316. 
    
  317.             class Meta:
    
  318.                 fields = "__all__"
    
  319.                 model = Article
    
  320. 
    
  321.         self.assertEqual(
    
  322.             label_for_field("extra_form_field", Article, form=ArticleForm()),
    
  323.             "Extra form field",
    
  324.         )
    
  325.         msg = "Unable to lookup 'nonexistent' on Article or ArticleForm"
    
  326.         with self.assertRaisesMessage(AttributeError, msg):
    
  327.             label_for_field("nonexistent", Article, form=ArticleForm()),
    
  328. 
    
  329.     def test_label_for_property(self):
    
  330.         class MockModelAdmin:
    
  331.             @property
    
  332.             @admin.display(description="property short description")
    
  333.             def test_from_property(self):
    
  334.                 return "this if from property"
    
  335. 
    
  336.         self.assertEqual(
    
  337.             label_for_field("test_from_property", Article, model_admin=MockModelAdmin),
    
  338.             "property short description",
    
  339.         )
    
  340. 
    
  341.     def test_help_text_for_field(self):
    
  342.         tests = [
    
  343.             ("article", ""),
    
  344.             ("unknown", ""),
    
  345.             ("hist", "History help text"),
    
  346.         ]
    
  347.         for name, help_text in tests:
    
  348.             with self.subTest(name=name):
    
  349.                 self.assertEqual(help_text_for_field(name, Article), help_text)
    
  350. 
    
  351.     def test_related_name(self):
    
  352.         """
    
  353.         Regression test for #13963
    
  354.         """
    
  355.         self.assertEqual(
    
  356.             label_for_field("location", Event, return_attr=True),
    
  357.             ("location", None),
    
  358.         )
    
  359.         self.assertEqual(
    
  360.             label_for_field("event", Location, return_attr=True),
    
  361.             ("awesome event", None),
    
  362.         )
    
  363.         self.assertEqual(
    
  364.             label_for_field("guest", Event, return_attr=True),
    
  365.             ("awesome guest", None),
    
  366.         )
    
  367. 
    
  368.     def test_safestring_in_field_label(self):
    
  369.         # safestring should not be escaped
    
  370.         class MyForm(forms.Form):
    
  371.             text = forms.CharField(label=mark_safe("<i>text</i>"))
    
  372.             cb = forms.BooleanField(label=mark_safe("<i>cb</i>"))
    
  373. 
    
  374.         form = MyForm()
    
  375.         self.assertHTMLEqual(
    
  376.             helpers.AdminField(form, "text", is_first=False).label_tag(),
    
  377.             '<label for="id_text" class="required inline"><i>text</i>:</label>',
    
  378.         )
    
  379.         self.assertHTMLEqual(
    
  380.             helpers.AdminField(form, "cb", is_first=False).label_tag(),
    
  381.             '<label for="id_cb" class="vCheckboxLabel required inline">'
    
  382.             "<i>cb</i></label>",
    
  383.         )
    
  384. 
    
  385.         # normal strings needs to be escaped
    
  386.         class MyForm(forms.Form):
    
  387.             text = forms.CharField(label="&text")
    
  388.             cb = forms.BooleanField(label="&cb")
    
  389. 
    
  390.         form = MyForm()
    
  391.         self.assertHTMLEqual(
    
  392.             helpers.AdminField(form, "text", is_first=False).label_tag(),
    
  393.             '<label for="id_text" class="required inline">&amp;text:</label>',
    
  394.         )
    
  395.         self.assertHTMLEqual(
    
  396.             helpers.AdminField(form, "cb", is_first=False).label_tag(),
    
  397.             '<label for="id_cb" class="vCheckboxLabel required inline">&amp;cb</label>',
    
  398.         )
    
  399. 
    
  400.     def test_flatten(self):
    
  401.         flat_all = ["url", "title", "content", "sites"]
    
  402.         inputs = (
    
  403.             ((), []),
    
  404.             (("url", "title", ("content", "sites")), flat_all),
    
  405.             (("url", "title", "content", "sites"), flat_all),
    
  406.             ((("url", "title"), ("content", "sites")), flat_all),
    
  407.         )
    
  408.         for orig, expected in inputs:
    
  409.             self.assertEqual(flatten(orig), expected)
    
  410. 
    
  411.     def test_flatten_fieldsets(self):
    
  412.         """
    
  413.         Regression test for #18051
    
  414.         """
    
  415.         fieldsets = ((None, {"fields": ("url", "title", ("content", "sites"))}),)
    
  416.         self.assertEqual(
    
  417.             flatten_fieldsets(fieldsets), ["url", "title", "content", "sites"]
    
  418.         )
    
  419. 
    
  420.         fieldsets = ((None, {"fields": ("url", "title", ["content", "sites"])}),)
    
  421.         self.assertEqual(
    
  422.             flatten_fieldsets(fieldsets), ["url", "title", "content", "sites"]
    
  423.         )
    
  424. 
    
  425.     def test_quote(self):
    
  426.         self.assertEqual(quote("something\nor\nother"), "something_0Aor_0Aother")