Подтвердить что ты не робот

Django: использование выражения F для текстового поля в вызове обновления

В представлении django мне нужно добавить строковые данные в конец существующего текстового столбца в моей базе данных. Так, например, скажем, у меня есть таблица с именем "ATable", и у нее есть поле с именем "aField". Я хотел бы добавить строку в конец "aField" без пробелов. Первоначально у меня было это:

tableEntry = ATable.objects.get(id=100)
tableEntry.aField += aStringVar
tableEntry.save()

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

Я немного заглянул в это и нашел это, и я надеялся, что это сработает, используя выражение F:

ATable.objects.filter(id=100).update(aField=F('aField') + aStringVar)

Проблема здесь, я получаю ошибку SQL, говоря:

operator does not exist: text + unknown
HINT:  No operator matches the given name and argument type(s). You might need to add explicit type casts.

Попробовал перейти на "str (aStringVar)", хотя его уже строка - не повезло.. Я обнаружил пару отчетов об ошибках django, жалующихся на подобные проблемы, но я не видел исправления или обходного пути. Есть ли способ, которым я могу использовать aStringVar, чтобы его можно было добавить к тексту выражения F? BTW - также попытался "str (F ('aField')) + aStringVar", но преобразовал результат выражения F в строку "(DEFAULT:)".

4b9b3361

Ответ 1

Вы можете переопределить объект F в Django одним простым изменением:

class CF(F):
    ADD = '||'

Затем используйте CF вместо F. Он поместит "||" вместо "+" при генерации SQL. Например, запрос:

User.objects.filter(pk=100).update(email=CF('username') + '@gmail.com')

будет генерировать SQL:

UPDATE "auth_user" SET "email" = "auth_user"."username" || '@gmail.com'
WHERE "auth_user"."id" = 100 

Ответ 2

Вы можете использовать функцию Concat db.

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

ATable.objects.filter(id=100).update(some_field=Concat('some_field', Value('more string')))

В моем случае я добавляю суффикс для URI facebook avatars следующим образом:

FACEBOOK_URI = 'graph.facebook.com'
FACEBOOK_LARGE = '?type=large'
# ...
users = User.objects.filter(Q(avatar_uri__icontains=FACEBOOK_URI) & ~Q(avatar_uri__icontains=FACEBOOK_LARGE))
users.update(avatar_uri=Concat('avatar_uri', Value(FACEBOOK_LARGE)))

и я получаю SQL как это (Django 1.9):

UPDATE `user_user` SET `avatar_uri` = CONCAT(COALESCE(`user_user`.`avatar_uri`, ''), COALESCE('?type=large', ''))
WHERE (`user_user`.`avatar_uri` LIKE '%graph.facebook.com%' AND NOT (`user_user`.`avatar_uri` LIKE '%?type=large%' AND `user_user`.`avatar_uri` IS NOT NULL))

В результате все URI изображений были изменены с http://graph.facebook.com/<fb user id>/picture на http://graph.facebook.com/<fb user id>/picture?type=large

Ответ 3

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

У вас тоже есть блокировка, но не забывайте этот сенарио:

  • Django: m = Model.objects.all() [10]
  • Django: m.field = field
  • Django: прогресс, который занимает некоторое время (time.sleep(100))
  • DB: заблокировать таблицу
  • DB: поле обновления
  • DD: разблокировать таблицу
  • Django: медленный процесс завершен.
  • Django: m.save()

Теперь обновление поля было отменено экземпляром модели в Django (запись Ghost)

Ответ 4

Вы можете выполнить эту функцию с помощью оператора Django select_for_update(): https://docs.djangoproject.com/en/dev/ref/models/querysets/#select-for-update

Что-то вроде этого:

obj = ATable.objects.select_for_update().get(id=100)
obj.aField = obj.aField + aStringVar
obj.save()

Строка таблицы будет заблокирована при вызове .select_for_update(). get(), и блокировка будет выпущена при вызове .save(), что позволит вам выполнить операцию атомарно.

Ответ 5

Кажется, вы не можете этого сделать. однако то, что вы пытаетесь сделать, можно решить с помощью transactions

(похоже, вы используете postgres, поэтому, если вы хотите сделать это в одном запросе и использовать raw sql, как было предложено, || - это оператор конкатенации, который вы хотите)