mirror of
				https://github.com/django/django.git
				synced 2025-10-31 09:41:08 +00:00 
			
		
		
		
	When Expression.__init__() overrides make use of *args, **kwargs captures their argument values are respectively bound as a tuple and dict instances. These composite values might themselves contain values that require special identity treatments such as Concat(output_field) as it's a Field instance. Refs #30628 which introduced bound Field differentiation but lacked argument captures handling. Thanks erchenstein for the report.
		
			
				
	
	
		
			124 lines
		
	
	
		
			4.6 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			124 lines
		
	
	
		
			4.6 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| from unittest import skipUnless
 | |
| 
 | |
| from django.db import connection
 | |
| from django.db.models import CharField, TextField
 | |
| from django.db.models import Value as V
 | |
| from django.db.models.functions import Concat, ConcatPair, Upper
 | |
| from django.test import TestCase
 | |
| from django.utils import timezone
 | |
| 
 | |
| from ..models import Article, Author
 | |
| 
 | |
| lorem_ipsum = """
 | |
|     Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod
 | |
|     tempor incididunt ut labore et dolore magna aliqua."""
 | |
| 
 | |
| 
 | |
| class ConcatTests(TestCase):
 | |
|     def test_basic(self):
 | |
|         Author.objects.create(name="Jayden")
 | |
|         Author.objects.create(name="John Smith", alias="smithj", goes_by="John")
 | |
|         Author.objects.create(name="Margaret", goes_by="Maggie")
 | |
|         Author.objects.create(name="Rhonda", alias="adnohR")
 | |
|         authors = Author.objects.annotate(joined=Concat("alias", "goes_by"))
 | |
|         self.assertQuerySetEqual(
 | |
|             authors.order_by("name"),
 | |
|             [
 | |
|                 "",
 | |
|                 "smithjJohn",
 | |
|                 "Maggie",
 | |
|                 "adnohR",
 | |
|             ],
 | |
|             lambda a: a.joined,
 | |
|         )
 | |
| 
 | |
|     def test_gt_two_expressions(self):
 | |
|         with self.assertRaisesMessage(
 | |
|             ValueError, "Concat must take at least two expressions"
 | |
|         ):
 | |
|             Author.objects.annotate(joined=Concat("alias"))
 | |
| 
 | |
|     def test_many(self):
 | |
|         Author.objects.create(name="Jayden")
 | |
|         Author.objects.create(name="John Smith", alias="smithj", goes_by="John")
 | |
|         Author.objects.create(name="Margaret", goes_by="Maggie")
 | |
|         Author.objects.create(name="Rhonda", alias="adnohR")
 | |
|         authors = Author.objects.annotate(
 | |
|             joined=Concat("name", V(" ("), "goes_by", V(")"), output_field=CharField()),
 | |
|         )
 | |
|         self.assertQuerySetEqual(
 | |
|             authors.order_by("name"),
 | |
|             [
 | |
|                 "Jayden ()",
 | |
|                 "John Smith (John)",
 | |
|                 "Margaret (Maggie)",
 | |
|                 "Rhonda ()",
 | |
|             ],
 | |
|             lambda a: a.joined,
 | |
|         )
 | |
| 
 | |
|     def test_mixed_char_text(self):
 | |
|         Article.objects.create(
 | |
|             title="The Title", text=lorem_ipsum, written=timezone.now()
 | |
|         )
 | |
|         article = Article.objects.annotate(
 | |
|             title_text=Concat("title", V(" - "), "text", output_field=TextField()),
 | |
|         ).get(title="The Title")
 | |
|         self.assertEqual(article.title + " - " + article.text, article.title_text)
 | |
|         # Wrap the concat in something else to ensure that text is returned
 | |
|         # rather than bytes.
 | |
|         article = Article.objects.annotate(
 | |
|             title_text=Upper(
 | |
|                 Concat("title", V(" - "), "text", output_field=TextField())
 | |
|             ),
 | |
|         ).get(title="The Title")
 | |
|         expected = article.title + " - " + article.text
 | |
|         self.assertEqual(expected.upper(), article.title_text)
 | |
| 
 | |
|     @skipUnless(
 | |
|         connection.vendor in ("sqlite", "postgresql"),
 | |
|         "SQLite and PostgreSQL specific implementation detail.",
 | |
|     )
 | |
|     def test_coalesce_idempotent(self):
 | |
|         pair = ConcatPair(V("a"), V("b"))
 | |
|         # Check nodes counts
 | |
|         self.assertEqual(len(list(pair.flatten())), 3)
 | |
|         self.assertEqual(
 | |
|             len(list(pair.coalesce().flatten())), 7
 | |
|         )  # + 2 Coalesce + 2 Value()
 | |
|         self.assertEqual(len(list(pair.flatten())), 3)
 | |
| 
 | |
|     def test_sql_generation_idempotency(self):
 | |
|         qs = Article.objects.annotate(description=Concat("title", V(": "), "summary"))
 | |
|         # Multiple compilations should not alter the generated query.
 | |
|         self.assertEqual(str(qs.query), str(qs.all().query))
 | |
| 
 | |
|     def test_concat_non_str(self):
 | |
|         Author.objects.create(name="The Name", age=42)
 | |
|         with self.assertNumQueries(1) as ctx:
 | |
|             author = Author.objects.annotate(
 | |
|                 name_text=Concat(
 | |
|                     "name", V(":"), "alias", V(":"), "age", output_field=TextField()
 | |
|                 ),
 | |
|             ).get()
 | |
|         self.assertEqual(author.name_text, "The Name::42")
 | |
|         # Only non-string columns are casted on PostgreSQL.
 | |
|         self.assertEqual(
 | |
|             ctx.captured_queries[0]["sql"].count("::text"),
 | |
|             1 if connection.vendor == "postgresql" else 0,
 | |
|         )
 | |
| 
 | |
|     def test_equal(self):
 | |
|         self.assertEqual(
 | |
|             Concat("foo", "bar", output_field=TextField()),
 | |
|             Concat("foo", "bar", output_field=TextField()),
 | |
|         )
 | |
|         self.assertNotEqual(
 | |
|             Concat("foo", "bar", output_field=TextField()),
 | |
|             Concat("foo", "bar", output_field=CharField()),
 | |
|         )
 | |
|         self.assertNotEqual(
 | |
|             Concat("foo", "bar", output_field=TextField()),
 | |
|             Concat("bar", "foo", output_field=TextField()),
 | |
|         )
 |