Browse Source

Refs #28643 -- Added LTrim, RTrim, and Trim database functions.

Thanks Tim Graham and Mads Jensen for reviews.
Mariusz Felisiak 7 years ago
parent
commit
9421aee35e

+ 4 - 4
django/db/models/functions/__init__.py

@@ -6,8 +6,8 @@ from .datetime import (
     TruncQuarter, TruncSecond, TruncTime, TruncWeek, TruncYear,
 )
 from .text import (
-    Chr, Concat, ConcatPair, Left, Length, Lower, Ord, Replace, Right,
-    StrIndex, Substr, Upper,
+    Chr, Concat, ConcatPair, Left, Length, Lower, LTrim, Ord, Replace, Right,
+    RTrim, StrIndex, Substr, Trim, Upper,
 )
 from .window import (
     CumeDist, DenseRank, FirstValue, Lag, LastValue, Lead, NthValue, Ntile,
@@ -24,8 +24,8 @@ __all__ = [
     'TruncMinute', 'TruncMonth', 'TruncQuarter', 'TruncSecond', 'TruncTime',
     'TruncWeek', 'TruncYear',
     # text
-    'Chr', 'Concat', 'ConcatPair', 'Left', 'Length', 'Lower', 'Ord', 'Replace',
-    'Right', 'StrIndex', 'Substr', 'Upper',
+    'Chr', 'Concat', 'ConcatPair', 'Left', 'Length', 'Lower', 'LTrim', 'Ord',
+    'Replace', 'Right', 'RTrim', 'StrIndex', 'Substr', 'Trim', 'Upper',
     # window
     'CumeDist', 'DenseRank', 'FirstValue', 'Lag', 'LastValue', 'Lead',
     'NthValue', 'Ntile', 'PercentRank', 'Rank', 'RowNumber',

+ 15 - 0
django/db/models/functions/text.py

@@ -110,6 +110,11 @@ class Lower(Transform):
     lookup_name = 'lower'
 
 
+class LTrim(Transform):
+    function = 'LTRIM'
+    lookup_name = 'ltrim'
+
+
 class Ord(Transform):
     function = 'ASCII'
     lookup_name = 'ord'
@@ -136,6 +141,11 @@ class Right(Left):
         return Substr(self.source_expressions[0], self.source_expressions[1] * Value(-1))
 
 
+class RTrim(Transform):
+    function = 'RTRIM'
+    lookup_name = 'rtrim'
+
+
 class StrIndex(Func):
     """
     Return a positive integer corresponding to the 1-indexed position of the
@@ -174,6 +184,11 @@ class Substr(Func):
         return super().as_sql(compiler, connection, function='SUBSTR')
 
 
+class Trim(Transform):
+    function = 'TRIM'
+    lookup_name = 'trim'
+
+
 class Upper(Transform):
     function = 'UPPER'
     lookup_name = 'upper'

+ 39 - 0
docs/ref/models/database-functions.txt

@@ -800,6 +800,16 @@ Usage example::
     >>> print(author.name_lower)
     margaret smith
 
+``LTrim``
+---------
+
+.. class:: LTrim(expression, **extra)
+
+.. versionadded:: 2.1
+
+Similar to :class:`~django.db.models.functions.Trim`, but removes only leading
+spaces.
+
 ``Ord``
 -------
 
@@ -862,6 +872,16 @@ Usage example::
     >>> print(author.last_letter)
     h
 
+``RTrim``
+---------
+
+.. class:: RTrim(expression, **extra)
+
+.. versionadded:: 2.1
+
+Similar to :class:`~django.db.models.functions.Trim`, but removes only trailing
+spaces.
+
 ``StrIndex``
 ------------
 
@@ -915,6 +935,25 @@ Usage example::
     >>> print(Author.objects.get(name='Margaret Smith').alias)
     marga
 
+``Trim``
+--------
+
+.. class:: Trim(expression, **extra)
+
+.. versionadded:: 2.1
+
+Returns the value of the given text field or expression with leading and
+trailing spaces removed.
+
+Usage example::
+
+    >>> from django.db.models.functions import Trim
+    >>> Author.objects.create(name='  John  ', alias='j')
+    >>> Author.objects.update(name=Trim('name'))
+    1
+    >>> print(Author.objects.get(alias='j').name)
+    John
+
 ``Upper``
 ---------
 

+ 6 - 3
docs/releases/2.1.txt

@@ -204,10 +204,13 @@ Models
 
 * A number of new text database functions are added:
   :class:`~django.db.models.functions.Chr`,
-  :class:`~django.db.models.functions.Ord`,
   :class:`~django.db.models.functions.Left`,
-  :class:`~django.db.models.functions.Right`, and
-  :class:`~django.db.models.functions.Replace`.
+  :class:`~django.db.models.functions.LTrim`,
+  :class:`~django.db.models.functions.Ord`,
+  :class:`~django.db.models.functions.Replace`,
+  :class:`~django.db.models.functions.Right`,
+  :class:`~django.db.models.functions.RTrim`, and
+  :class:`~django.db.models.functions.Trim`.
 
 * The new :class:`~django.db.models.functions.TruncWeek` function truncates
   :class:`~django.db.models.DateField` and

+ 40 - 0
tests/db_functions/test_trim.py

@@ -0,0 +1,40 @@
+from django.db.models import CharField
+from django.db.models.functions import LTrim, RTrim, Trim
+from django.test import TestCase
+
+from .models import Author
+
+
+class TrimTests(TestCase):
+    def test_trim(self):
+        Author.objects.create(name='  John ', alias='j')
+        Author.objects.create(name='Rhonda', alias='r')
+        authors = Author.objects.annotate(
+            ltrim=LTrim('name'),
+            rtrim=RTrim('name'),
+            trim=Trim('name'),
+        )
+        self.assertQuerysetEqual(
+            authors.order_by('alias'), [
+                ('John ', '  John', 'John'),
+                ('Rhonda', 'Rhonda', 'Rhonda'),
+            ],
+            lambda a: (a.ltrim, a.rtrim, a.trim)
+        )
+
+    def test_trim_transform(self):
+        Author.objects.create(name=' John  ')
+        Author.objects.create(name='Rhonda')
+        tests = (
+            (LTrim, 'John  '),
+            (RTrim, ' John'),
+            (Trim, 'John'),
+        )
+        for transform, trimmed_name in tests:
+            with self.subTest(transform=transform):
+                try:
+                    CharField.register_lookup(transform)
+                    authors = Author.objects.filter(**{'name__%s' % transform.lookup_name: trimmed_name})
+                    self.assertQuerysetEqual(authors, [' John  '], lambda a: a.name)
+                finally:
+                    CharField._unregister_lookup(transform)