Skip to content

Commit

Permalink
Refs #28643 -- Added Replace database function.
Browse files Browse the repository at this point in the history
  • Loading branch information
atombrella authored and timgraham committed Jan 18, 2018
1 parent fcd431c commit 6572855
Show file tree
Hide file tree
Showing 5 changed files with 92 additions and 2 deletions.
7 changes: 5 additions & 2 deletions django/db/models/functions/__init__.py
Expand Up @@ -5,7 +5,9 @@
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,
Expand All @@ -21,7 +23,8 @@
'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',
Expand Down
7 changes: 7 additions & 0 deletions django/db/models/functions/text.py
Expand Up @@ -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
Expand Down
22 changes: 22 additions & 0 deletions docs/ref/models/database-functions.txt
Expand Up @@ -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``
------------

Expand Down
3 changes: 3 additions & 0 deletions docs/releases/2.1.txt
Expand Up @@ -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
~~~~~~~~~~~~~~~~~~~~~~

Expand Down
55 changes: 55 additions & 0 deletions 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)

0 comments on commit 6572855

Please sign in to comment.