Django: использование выражения F для текстового поля в вызове обновления - PullRequest
21 голосов
/ 16 ноября 2011

В представлении 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?Кстати, также пытался "str (F ('aField')) + aStringVar", но это преобразовало результат выражения F в строку "(DEFAULT:)".

Ответы [ 5 ]

22 голосов
/ 01 августа 2014

Вы можете переопределить 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 
16 голосов
/ 08 мая 2016

Вы можете использовать функцию 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, например:

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 голосов
/ 16 ноября 2011

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

Вы тоже приобрели замок, но не забудьте этот сенарио:

  1. Django: m = Model.objects.all () [10]
  2. Django: m.field = field
  3. Django: прогресс, который занимает некоторое время (time.sleep (100))
  4. DB: таблица блокировок
  5. БД: поле обновления
  6. DD: разблокировать таблицу
  7. Django: медленный процесс закончен
  8. Django: m.save ()

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

3 голосов
/ 06 февраля 2013

Вы можете достичь этой функциональности с помощью оператора 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 (), что позволит вам выполнить операцию атомарно.

2 голосов
/ 16 ноября 2011

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...