В Django, как я могу предотвратить «Сохранение с update_fields не повлияло ни на какие строки». ошибка?

Я использую 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(...) таким образом, чтобы, если объект больше не существует, я мог игнорировать любую возникающую ошибку?

Посмотрите docs.djangoproject.com/en/2.1/ref/models/querysets/…

gachdavit 22.05.2019 16:27

Вы можете перехватить ошибку DatabaseError и проверить, выбрасывает ли article.refresh_from_db() Article.DoesNotExist или нет, чтобы убедиться, что объект был удален.

philoj 27.05.2019 08:06

Вы уверены, что article.label != label ? кажется, что данные до и после сохранения одинаковы

Ryabchenko Alexander 31.05.2019 23:11
3 метода стилизации элементов HTML
3 метода стилизации элементов HTML
Когда дело доходит до применения какого-либо стиля к нашему HTML, существует три подхода: встроенный, внутренний и внешний. Предпочтительным обычно...
Формы c голосовым вводом в React с помощью Speechly
Формы c голосовым вводом в React с помощью Speechly
Пытались ли вы когда-нибудь заполнить веб-форму в области электронной коммерции, которая требует много кликов и выбора? Вас попросят заполнить дату,...
Стилизация и валидация html-формы без использования JavaScript (только HTML/CSS)
Стилизация и валидация html-формы без использования JavaScript (только HTML/CSS)
Будучи разработчиком веб-приложений, легко впасть в заблуждение, считая, что приложение без JavaScript не имеет права на жизнь. Нам становится удобно...
Flatpickr: простой модуль календаря для вашего приложения на React
Flatpickr: простой модуль календаря для вашего приложения на React
Если вы ищете пакет для быстрой интеграции календаря с выбором даты в ваше приложения, то библиотека Flatpickr отлично справится с этой задачей....
В чем разница между Promise и Observable?
В чем разница между Promise и Observable?
Разберитесь в этом вопросе, и вы значительно повысите уровень своей компетенции.
Что такое cURL в PHP? Встроенные функции и пример GET запроса
Что такое cURL в PHP? Встроенные функции и пример GET запроса
Клиент для URL-адресов, cURL, позволяет взаимодействовать с множеством различных серверов по множеству различных протоколов с синтаксисом URL.
13
3
5 225
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Я не знаю никакого специального способа справиться с этим, кроме как проверить, изменились ли значения.

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

Редактировать: Другой альтернативой может быть перехват и обработка исключения.

Эй, я пытался вставить вашу функцию, но обнаружил, что все еще получаю ту же ошибку.

Dave 24.05.2019 16:26

Я не думаю, что в этом есть ошибка. Вы пытались отладить то, что работает, чтобы увидеть, каковы значения?

schillingt 29.05.2019 01:56
Ответ принят как подходящий

комментарий от 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')

Другие вопросы по теме