浏览代码

Refs #28643 -- Added Replace database function.

Mads Jensen 7 年之前
父节点
当前提交
65728550bd

+ 5 - 2
django/db/models/functions/__init__.py

@@ -5,7 +5,9 @@ from .datetime import (
     Now, Trunc, TruncDate, TruncDay, TruncHour, TruncMinute, TruncMonth,
     TruncQuarter, TruncSecond, TruncTime, TruncYear,
 )
-from .text import Concat, ConcatPair, Length, Lower, StrIndex, Substr, Upper
+from .text import (
+    Concat, ConcatPair, Length, Lower, Replace, StrIndex, Substr, Upper,
+)
 from .window import (
     CumeDist, DenseRank, FirstValue, Lag, LastValue, Lead, NthValue, Ntile,
     PercentRank, Rank, RowNumber,
@@ -21,7 +23,8 @@ __all__ = [
     'TruncMinute', 'TruncMonth', 'TruncQuarter', 'TruncSecond', 'TruncTime',
     'TruncYear',
     # text
-    'Concat', 'ConcatPair', 'Length', 'Lower', 'StrIndex', 'Substr', 'Upper',
+    'Concat', 'ConcatPair', 'Length', 'Lower', 'Replace', 'StrIndex', 'Substr',
+    'Upper',
     # window
     'CumeDist', 'DenseRank', 'FirstValue', 'Lag', 'LastValue', 'Lead',
     'NthValue', 'Ntile', 'PercentRank', 'Rank', 'RowNumber',

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

@@ -70,6 +70,13 @@ class Lower(Transform):
     lookup_name = 'lower'
 
 
+class Replace(Func):
+    function = 'REPLACE'
+
+    def __init__(self, expression, text, replacement=Value(''), **extra):
+        super().__init__(expression, text, replacement, **extra)
+
+
 class StrIndex(Func):
     """
     Return a positive integer corresponding to the 1-indexed position of the

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

@@ -751,6 +751,28 @@ Usage example::
     >>> print(author.name_lower)
     margaret smith
 
+``Replace``
+~~~~~~~~~~~
+
+.. class:: Replace(expression, text, replacement=Value(''), **extra)
+
+.. versionadded:: 2.1
+
+Replaces all occurrences of ``text`` with ``replacement`` in ``expression``.
+The default replacement text is the empty string. The arguments to the function
+are case-sensitive.
+
+Usage example::
+
+    >>> from django.db.models import Value
+    >>> from django.db.models.functions import Replace
+    >>> Author.objects.create(name='Margaret Johnson')
+    >>> Author.objects.create(name='Margaret Smith')
+    >>> Author.objects.update(name=Replace('name', Value('Margaret'), Value('Margareth')))
+    2
+    >>> Author.objects.values('name')
+    <QuerySet [{'name': 'Margareth Johnson'}, {'name': 'Margareth Smith'}]>
+
 ``StrIndex``
 ------------
 

+ 3 - 0
docs/releases/2.1.txt

@@ -169,6 +169,9 @@ Models
 * A ``BinaryField`` may now be set to ``editable=True`` if you wish to include
   it in model forms.
 
+* The new :class:`~django.db.models.functions.Replace` database function
+  replaces strings in an expression.
+
 Requests and Responses
 ~~~~~~~~~~~~~~~~~~~~~~
 

+ 55 - 0
tests/db_functions/test_replace.py

@@ -0,0 +1,55 @@
+from django.db.models import F, Value
+from django.db.models.functions import Concat, Replace
+from django.test import TestCase
+
+from .models import Author
+
+
+class ReplaceTests(TestCase):
+
+    @classmethod
+    def setUpTestData(cls):
+        Author.objects.create(name='George R. R. Martin')
+        Author.objects.create(name='J. R. R. Tolkien')
+
+    def test_replace_with_empty_string(self):
+        qs = Author.objects.annotate(
+            without_middlename=Replace(F('name'), Value('R. R. '), Value('')),
+        )
+        self.assertQuerysetEqual(qs, [
+            ('George R. R. Martin', 'George Martin'),
+            ('J. R. R. Tolkien', 'J. Tolkien'),
+        ], transform=lambda x: (x.name, x.without_middlename), ordered=False)
+
+    def test_case_sensitive(self):
+        qs = Author.objects.annotate(same_name=Replace(F('name'), Value('r. r.'), Value('')))
+        self.assertQuerysetEqual(qs, [
+            ('George R. R. Martin', 'George R. R. Martin'),
+            ('J. R. R. Tolkien', 'J. R. R. Tolkien'),
+        ], transform=lambda x: (x.name, x.same_name), ordered=False)
+
+    def test_replace_expression(self):
+        qs = Author.objects.annotate(same_name=Replace(
+            Concat(Value('Author: '), F('name')), Value('Author: '), Value('')),
+        )
+        self.assertQuerysetEqual(qs, [
+            ('George R. R. Martin', 'George R. R. Martin'),
+            ('J. R. R. Tolkien', 'J. R. R. Tolkien'),
+        ], transform=lambda x: (x.name, x.same_name), ordered=False)
+
+    def test_update(self):
+        Author.objects.update(
+            name=Replace(F('name'), Value('R. R. '), Value('')),
+        )
+        self.assertQuerysetEqual(Author.objects.all(), [
+            ('George Martin'),
+            ('J. Tolkien'),
+        ], transform=lambda x: x.name, ordered=False)
+
+    def test_replace_with_default_arg(self):
+        # The default replacement is an empty string.
+        qs = Author.objects.annotate(same_name=Replace(F('name'), Value('R. R. ')))
+        self.assertQuerysetEqual(qs, [
+            ('George R. R. Martin', 'George Martin'),
+            ('J. R. R. Tolkien', 'J. Tolkien'),
+        ], transform=lambda x: (x.name, x.same_name), ordered=False)