diff options
| author | Nick Pope <nick@nickpope.me.uk> | 2023-12-30 07:24:30 +0000 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2023-12-30 08:24:30 +0100 |
| commit | 94b6f101f7dc363a8e71593570b17527dbb9f77f (patch) | |
| tree | 431ddcb41b024af3a4fba136b53a210ad390cf4c /tests/expressions | |
| parent | 561e16d6a7f07d8cadbdb6c72c68934dcd087d18 (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.py | 4 | ||||
| -rw-r--r-- | tests/expressions/tests.py | 101 |
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( |
