26

I am trying to update and modify a string field Django's ORM. The equivalent SQL to do this is:

UPDATE example_table SET string_field = REPLACE(string_field, 'old text', 'new text');

With that query, I expect old text and old text more text to be replaced with new text and new text more text respectively, for all the entries in the string_field column.

Bulk update() seems promising, but doesn't allow me to modify only part of the field, and F() expressions only implement numeric changes, not string replace. I also looked at using raw queries to run the above SQL, but that seems like a sideways hack (especially since F() exists to do the same functionality on numbers), and I couldn't get them to actually execute.

I ended up with this, but it seems a shame to execute all the extra queries when I know there's a one line SQL statement to do it.

for entry in ExampleModel.objects.all():
    entry.string_field = entry.string_field.replace('old text', 'new text', 1)
    entry.save()

Does this feature not exist in Django's ORM for strings yet? Is there something I overlooked in the docs?

Related SO questions:

Community
  • 1
  • 1
Holly
  • 1,059
  • 1
  • 13
  • 16
  • I have come with this problem back there. The best way I could found is to do the string processing in Python code. If you do not mind using raw SQL, the replace function is quite consistent between any databases. – Edwin Lunando Feb 22 '15 at 13:27

5 Answers5

38

Tested with django 1.9

from django.db.models import F, Func, Value

ExampleModel.objects.filter(<condition>).update(
    string_field=Func(
        F('string_field'),
        Value('old text'), Value('new text'),
        function='replace',
    )
)

UPDATE Django 2.1 https://docs.djangoproject.com/en/2.2/ref/models/database-functions/#replace

from django.db.models import Value
from django.db.models.functions import Replace

ExampleModel.objects.filter(<condition>).update(
    string_field=Replace('string_field', Value('old text'), Value('new text'))
)
Seonghyeon Cho
  • 171
  • 1
  • 3
  • 11
James
  • 13,571
  • 6
  • 61
  • 83
5

You could make your own F-like object to represent the string replacing in SQL. Here is a proof of concept:

from django.db.models.expressions import ExpressionNode

class StringReplaceF(ExpressionNode):
    def __init__(self, field, replace_from, replace_to):
        self.field = field
        self.replace_from = replace_from
        self.replace_to = replace_to
        super(StringReplaceF, self).__init__()

    def evaluate(self, evaluator, qn, connection):
        return (
            "REPLACE({}, %s, %s)".format(self.field),
            (self.replace_from, self.replace_to)
        )

 >>> f = StringReplaceF('string_field', 'old text', 'new text')
 >>> ExampleModel.objects.update(string_field=f)

You'd need to do a bit more work with the class if you need it to behave nicely with other F objects, but then again, the existing F objects don't seem to work with strings anyway.

Andrew Magee
  • 6,506
  • 4
  • 35
  • 58
  • Any idea what the Django version and DBMS support for this is? – Kevin Brown-Silva Mar 03 '15 at 00:21
  • Not sure exactly. I just tried it on 1.6.6; I expect it would work in (much) earlier versions but not sure of the specifics. SQL-wise, the `REPLACE` function seems to exist in that form in at least Postgres, MySQL and SQLite. – Andrew Magee Mar 03 '15 at 00:25
3

New in Django 2.1 - Replace database function

Your example can now be expressed most easily via:

ExampleModel.objects.update(
    string_field=Replace('string_field', Value('old_text'), Value('new_text'))
)
tripleee
  • 175,061
  • 34
  • 275
  • 318
Dave Lawrence
  • 1,226
  • 9
  • 8
0

Use replace_field_substring(YourModel, "your_field", "value_from", "value_to")

from django.db.models import F, Value, QuerySet
from django.db.models.functions import Replace

def replace_field_substring(model_or_qs, field, value_from, value_to):
    """Generates ORM partial replace, which can be used in Django ORM data migrations,
    for example for partial replace tbl.cont from="twitter.com" to="x.com" will give you
    UPDATE tbl SET cont = REPLACE(cont, 'twitter.com', 'x.com') WHERE cont LIKE '%twitter.com%';
    """
    qs = model_or_qs if isinstance(model_or_qs, QuerySet) else model_or_qs.objects.all()
    replacement_expression = Replace(F(field), Value(value_from), Value(value_to))
    qs.update(**{field: replacement_expression})
pymen
  • 5,737
  • 44
  • 35
-1

Django 2.2 support bulk update, can you using function that one.

Check this one : https://docs.djangoproject.com/en/2.2/ref/models/querysets/#django.db.models.query.QuerySet.bulk_create