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>
This commit is contained in:
Nick Pope 2023-12-30 07:24:30 +00:00 committed by GitHub
parent 561e16d6a7
commit 94b6f101f7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 256 additions and 4 deletions

View file

@ -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(