Объемное обновление Django с заменой строки

Я пытаюсь обновить и изменить строковое поле Django ORM. Эквивалентный SQL для этого:

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

С этим запросом я ожидаю, что old text и old text more text будут заменены на new text и new text more text соответственно, для всех записей в столбце string_field.

Массовое обновление() кажется многообещающим, но не позволяет мне изменять только часть поля, а Выражения F() реализуют только числовые изменения, а не замену строки. Я также рассмотрел использование необработанных запросов для запуска вышеуказанного SQL, но это похоже на боковую ручку (тем более, что F() существует для выполнения такая же функциональность для чисел), и я не мог заставить их фактически выполнить.

Я закончил с этим, но, похоже, стыдно выполнять все лишние запросы, когда я знаю, что для него требуется однострочный оператор SQL.

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

Эта функция еще не существует в Django ORM для строк? Есть ли что-то, что я забыл в документах?

Связанные вопросы SO:

Ответы

Ответ 1

Проверено с 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',
    )
)

ОБНОВЛЕНИЕ 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('name', Value('old text'), Value('new text'))
)

Ответ 2

Вы можете создать собственный F -подобный объект для представления замены строки в SQL. Вот доказательство концепции:

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)

Вам нужно будет немного поработать с классом, если вам нужно хорошо вести себя с другими объектами F, но, опять же, существующие объекты F, похоже, не работают со строками.

Ответ 3

Новое в Django 2.1 - Заменить функцию базы данных

Ваш пример теперь легче всего выразить через:

ExampleModel.objects.update(string_field=Replace('string_field', Value('old_text'), Value('new_text')))