В 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
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
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')

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