summaryrefslogtreecommitdiff
path: root/tests/expressions
diff options
context:
space:
mode:
authorNick Pope <nick@nickpope.me.uk>2023-12-30 07:24:30 +0000
committerGitHub <noreply@github.com>2023-12-30 08:24:30 +0100
commit94b6f101f7dc363a8e71593570b17527dbb9f77f (patch)
tree431ddcb41b024af3a4fba136b53a210ad390cf4c /tests/expressions
parent561e16d6a7f07d8cadbdb6c72c68934dcd087d18 (diff)
Fixed #29049 -- Added slicing notation to F expressions.
Co-authored-by: Priyansh Saxena <askpriyansh@gmail.com> Co-authored-by: Niclas Olofsson <n@niclasolofsson.se> Co-authored-by: David Smith <smithdc@gmail.com> Co-authored-by: Mariusz Felisiak <felisiak.mariusz@gmail.com> Co-authored-by: Abhinav Yadav <abhinav.sny.2002@gmail.com>
Diffstat (limited to 'tests/expressions')
-rw-r--r--tests/expressions/models.py4
-rw-r--r--tests/expressions/tests.py101
2 files changed, 105 insertions, 0 deletions
diff --git a/tests/expressions/models.py b/tests/expressions/models.py
index 0a8a0a6584..bd4db9050e 100644
--- a/tests/expressions/models.py
+++ b/tests/expressions/models.py
@@ -106,3 +106,7 @@ class UUIDPK(models.Model):
class UUID(models.Model):
uuid = models.UUIDField(null=True)
uuid_fk = models.ForeignKey(UUIDPK, models.CASCADE, null=True)
+
+
+class Text(models.Model):
+ name = models.TextField()
diff --git a/tests/expressions/tests.py b/tests/expressions/tests.py
index 073e2e9258..cbb441601c 100644
--- a/tests/expressions/tests.py
+++ b/tests/expressions/tests.py
@@ -84,6 +84,7 @@ from .models import (
RemoteEmployee,
Result,
SimulationRun,
+ Text,
Time,
)
@@ -205,6 +206,100 @@ class BasicExpressionsTests(TestCase):
],
)
+ def _test_slicing_of_f_expressions(self, model):
+ tests = [
+ (F("name")[:], "Example Inc.", "Example Inc."),
+ (F("name")[:7], "Example Inc.", "Example"),
+ (F("name")[:6][:5], "Example", "Examp"), # Nested slicing.
+ (F("name")[0], "Examp", "E"),
+ (F("name")[5], "E", ""),
+ (F("name")[7:], "Foobar Ltd.", "Ltd."),
+ (F("name")[0:10], "Ltd.", "Ltd."),
+ (F("name")[2:7], "Test GmbH", "st Gm"),
+ (F("name")[1:][:3], "st Gm", "t G"),
+ (F("name")[2:2], "t G", ""),
+ ]
+ for expression, name, expected in tests:
+ with self.subTest(expression=expression, name=name, expected=expected):
+ obj = model.objects.get(name=name)
+ obj.name = expression
+ obj.save()
+ obj.refresh_from_db()
+ self.assertEqual(obj.name, expected)
+
+ def test_slicing_of_f_expressions_charfield(self):
+ self._test_slicing_of_f_expressions(Company)
+
+ def test_slicing_of_f_expressions_textfield(self):
+ Text.objects.bulk_create(
+ [Text(name=company.name) for company in Company.objects.all()]
+ )
+ self._test_slicing_of_f_expressions(Text)
+
+ def test_slicing_of_f_expressions_with_annotate(self):
+ qs = Company.objects.annotate(
+ first_three=F("name")[:3],
+ after_three=F("name")[3:],
+ random_four=F("name")[2:5],
+ first_letter_slice=F("name")[:1],
+ first_letter_index=F("name")[0],
+ )
+ tests = [
+ ("first_three", ["Exa", "Foo", "Tes"]),
+ ("after_three", ["mple Inc.", "bar Ltd.", "t GmbH"]),
+ ("random_four", ["amp", "oba", "st "]),
+ ("first_letter_slice", ["E", "F", "T"]),
+ ("first_letter_index", ["E", "F", "T"]),
+ ]
+ for annotation, expected in tests:
+ with self.subTest(annotation):
+ self.assertCountEqual(qs.values_list(annotation, flat=True), expected)
+
+ def test_slicing_of_f_expression_with_annotated_expression(self):
+ qs = Company.objects.annotate(
+ new_name=Case(
+ When(based_in_eu=True, then=Concat(Value("EU:"), F("name"))),
+ default=F("name"),
+ ),
+ first_two=F("new_name")[:3],
+ )
+ self.assertCountEqual(
+ qs.values_list("first_two", flat=True),
+ ["Exa", "EU:", "Tes"],
+ )
+
+ def test_slicing_of_f_expressions_with_negative_index(self):
+ msg = "Negative indexing is not supported."
+ indexes = [slice(0, -4), slice(-4, 0), slice(-4), -5]
+ for i in indexes:
+ with self.subTest(i=i), self.assertRaisesMessage(ValueError, msg):
+ F("name")[i]
+
+ def test_slicing_of_f_expressions_with_slice_stop_less_than_slice_start(self):
+ msg = "Slice stop must be greater than slice start."
+ with self.assertRaisesMessage(ValueError, msg):
+ F("name")[4:2]
+
+ def test_slicing_of_f_expressions_with_invalid_type(self):
+ msg = "Argument to slice must be either int or slice instance."
+ with self.assertRaisesMessage(TypeError, msg):
+ F("name")["error"]
+
+ def test_slicing_of_f_expressions_with_step(self):
+ msg = "Step argument is not supported."
+ with self.assertRaisesMessage(ValueError, msg):
+ F("name")[::4]
+
+ def test_slicing_of_f_unsupported_field(self):
+ msg = "This field does not support slicing."
+ with self.assertRaisesMessage(NotSupportedError, msg):
+ Company.objects.update(num_chairs=F("num_chairs")[:4])
+
+ def test_slicing_of_outerref(self):
+ inner = Company.objects.filter(name__startswith=OuterRef("ceo__firstname")[0])
+ outer = Company.objects.filter(Exists(inner)).values_list("name", flat=True)
+ self.assertSequenceEqual(outer, ["Foobar Ltd."])
+
def test_arithmetic(self):
# We can perform arithmetic operations in expressions
# Make sure we have 2 spare chairs
@@ -2359,6 +2454,12 @@ class ReprTests(SimpleTestCase):
repr(Func("published", function="TO_CHAR")),
"Func(F(published), function=TO_CHAR)",
)
+ self.assertEqual(
+ repr(F("published")[0:2]), "Sliced(F(published), slice(0, 2, None))"
+ )
+ self.assertEqual(
+ repr(OuterRef("name")[1:5]), "Sliced(OuterRef(name), slice(1, 5, None))"
+ )
self.assertEqual(repr(OrderBy(Value(1))), "OrderBy(Value(1), descending=False)")
self.assertEqual(repr(RawSQL("table.col", [])), "RawSQL(table.col, [])")
self.assertEqual(