summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMads Jensen <mje@inducks.org>2018-01-18 02:46:15 +0100
committerTim Graham <timograham@gmail.com>2018-01-17 20:46:15 -0500
commit65728550bd6296871562da02069ddb86e9dd3482 (patch)
tree55fcab54194ea50d9ae7701b260fed80375e80e4
parentfcd431c6c3159a753f1cc0658cc4b25803ffccc1 (diff)
Refs #28643 -- Added Replace database function.
-rw-r--r--django/db/models/functions/__init__.py7
-rw-r--r--django/db/models/functions/text.py7
-rw-r--r--docs/ref/models/database-functions.txt22
-rw-r--r--docs/releases/2.1.txt3
-rw-r--r--tests/db_functions/test_replace.py55
5 files changed, 92 insertions, 2 deletions
diff --git a/django/db/models/functions/__init__.py b/django/db/models/functions/__init__.py
index dd11dd1772..0680200c7a 100644
--- a/django/db/models/functions/__init__.py
+++ b/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',
diff --git a/django/db/models/functions/text.py b/django/db/models/functions/text.py
index 4ec07be2df..56a2b66ad5 100644
--- a/django/db/models/functions/text.py
+++ b/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
diff --git a/docs/ref/models/database-functions.txt b/docs/ref/models/database-functions.txt
index acb22249c5..db708a6ce0 100644
--- a/docs/ref/models/database-functions.txt
+++ b/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``
------------
diff --git a/docs/releases/2.1.txt b/docs/releases/2.1.txt
index f2141dbb99..b979ba2224 100644
--- a/docs/releases/2.1.txt
+++ b/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
~~~~~~~~~~~~~~~~~~~~~~
diff --git a/tests/db_functions/test_replace.py b/tests/db_functions/test_replace.py
new file mode 100644
index 0000000000..91a1749d70
--- /dev/null
+++ b/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)