Я использую Django и Python 3.7. у меня есть этот код
article = get_article(id)
...
article.label = label
article.save(update_fields=["label"])
Иногда я получаю следующую ошибку в строке «сохранить» …
raise DatabaseError("Save with update_fields did not affect any rows.")
django.db.utils.DatabaseError: Save with update_fields did not affect any rows.
Очевидно, в "..." другая ветка может удалять мою статью. Есть ли другой способ переписать мой оператор article.save(...) таким образом, чтобы, если объект больше не существует, я мог игнорировать любую возникающую ошибку?
Вы можете перехватить ошибку DatabaseError и проверить, выбрасывает ли article.refresh_from_db() Article.DoesNotExist или нет, чтобы убедиться, что объект был удален.
Вы уверены, что article.label != label ? кажется, что данные до и после сохранения одинаковы
Я не знаю никакого специального способа справиться с этим, кроме как проверить, изменились ли значения.
article = update_model(article, {'label': label})
def update_model(instance, updates):
update_fields = {
field: value
for field, value in updates.items()
if getattr(instance, field) != value
}
if update_fields:
for field, value in update_fields.items():
setattr(instance, field, value)
instance.save(update_fields=update_fields.keys())
return instance
Редактировать: Другой альтернативой может быть перехват и обработка исключения.
Эй, я пытался вставить вашу функцию, но обнаружил, что все еще получаю ту же ошибку.
Я не думаю, что в этом есть ошибка. Вы пытались отладить то, что работает, чтобы увидеть, каковы значения?
комментарий от gachdavit предложил использовать select_for_update
. Вы можете изменить свою функцию get_article
, чтобы она вызывала select_for_update
перед получением статьи. При этом строка базы данных, содержащая статью, будет заблокирована до тех пор, пока текущая транзакция не зафиксируется или не откатится. Если другой поток попытается одновременно удалить статью, этот поток будет заблокирован до тех пор, пока блокировка не будет снята. По сути, статья не будет удалена до тех пор, пока вы не вызовете функцию save
.
Если у вас нет особых требований, я бы выбрал именно этот подход.
Это хакерство, но вы можете переопределить _do_update
в своей модели и просто вернуть True
. Сам Django делает что-то вроде взлома на строка 893 из _do_update
, чтобы подавить то же самое исключение, когда update_fields
содержит имена столбцов, которые не отображаются в модели.
Возвращаемое значение из _do_update
вызывает исключение, которое вы видите из этот блок.
Я протестировал переопределение ниже, и оно, похоже, сработало. Я чувствую себя несколько грязным из-за переопределения частного метода, но я думаю, что справлюсь с этим.
def _do_update(self, base_qs, using, pk_val, values, update_fields, forced_update):
updated = super(Article, self)._do_update(base_qs, using, pk_val, values, update_fields, forced_update)
if not updated and Article.objects.filter(id=pk_val).count() == 0:
return True
return updated
Это решение можно обобщить и перенести в базовый класс примеси, если вам нужно обрабатывать это для более чем одной модели.
Я использовал этот команда управления джанго для тестирования
from django.core.management.base import BaseCommand
from foo.models import Article
class Command(BaseCommand):
def handle(self, *args, **kwargs):
Article.objects.update_or_create(id=1, defaults=dict(label='zulu'))
print('Testing _do_update hack')
article1 = Article.objects.get(id=1)
article1.label = 'yankee'
article2 = Article.objects.get(id=1)
article2.delete()
article1.save(update_fields=['label'])
print('Done. No exception raised')
Посмотрите docs.djangoproject.com/en/2.1/ref/models/querysets/…