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

Определите измененные поля в django post_save signal

Я использую django post_save для выполнения некоторых инструкций после сохранения модели.

class Mode(models.Model):
    name = models.CharField(max_length=5)
    mode = models.BooleanField()


from django.db.models.signals import post_save
from django.dispatch import receiver

@receiver(post_save, sender=Mode)
def post_save(sender, instance, created, **kwargs):
        # do some stuff
        pass

Теперь я хочу выполнить оператор на основании того, изменилось ли значение поля mode или нет.

@receiver(post_save, sender=Mode)
def post_save(sender, instance, created, **kwargs):
        # if value of `mode` has changed:
        #  then do this
        # else:
        #  do that
        pass

Я просмотрел несколько потоков SOF и блог, но не смог найти решение. Все они пытались использовать метод или форму pre_save, которые не являются моим прецедентом. https://docs.djangoproject.com/es/1.9/ref/signals/#post-save в django docs не упоминает прямой способ сделать это.

Ответ в приведенной ниже ссылке выглядит многообещающим, но я не знаю, как его использовать. Я не уверен, поддерживает ли последняя версия django или нет, потому что я использовал ipdb для отладки этого и обнаружил, что переменная instance не имеет атрибута has_changed, как указано в следующем ответе.

Django: при сохранении, как вы можете проверить, изменилось ли поле?

4b9b3361

Ответ 1

Настройте его на __init__ вашей модели, чтобы у вас был доступ к нему.

def __init__(self, *args, **kwargs):
    super(YourModel, self).__init__(*args, **kwargs)
    self.__original_mode = self.mode

Теперь вы можете выполнить что-то вроде:

if instance.mode != instance.__original_mode:
    # do something useful

Ответ 2

Обычно лучше переопределить метод сохранения, чем использовать сигналы.

Из Два ложки django: "Использовать сигналы в качестве крайней меры".

Я согласен с ответом @scoopseven о кэшировании исходного значения в init, но переопределяя метод сохранения, если это возможно.

class Mode(models.Model):
    name = models.CharField(max_length=5)
    mode = models.BooleanField()
    __original_mode = None

    def __init__(self, *args, **kwargs):
        super(Mode, self).__init__(*args, **kwargs)
        self.__original_mode = self.mode

    def save(self, force_insert=False, force_update=False, *args, **kwargs):
        if self.mode != self.__original_mode:
            #  then do this
        else:
            #  do that

        super(Mode, self).save(force_insert, force_update, *args, **kwargs)
        self.__original_mode = self.mode

Ответ 3

Это старый вопрос, но я недавно столкнулся с этой ситуацией, и я сделал это следующим образом:

class Mode(models.Model):

    def save(self, *args, **kwargs):
        if self.pk:
            # If self.pk is not None then it an update.
            cls = self.__class__
            old = cls.objects.get(pk=self.pk)
            # This will get the current model state since super().save() isn't called yet.
            new = self  # This gets the newly instantiated Mode object with the new values.
            changed_fields = []
            for field in cls._meta.get_fields():
                field_name = field.name
                try:
                    if getattr(old, field_name) != getattr(new, field_name):
                        changed_fields.append(field_name)
                except Exception as ex:  # Catch field does not exist exception
                    pass
            kwargs['update_fields'] = changed_fields
        super().save(*args, **kwargs)

Это более эффективно, так как ловит все обновления/сохранения из приложений и django-admin.

Ответ 4

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

from django.db.models.signals import post_save, pre_save
from django.dispatch import receiver


@receiver(pre_save, sender=MyModel)
def on_cahnge(sender, instance: MyModel, **kwargs):
    if instance.id is None: # new object will be created
        pass # write your code hier
    else:
        previous = MyModel.objects.get(id=instance.id)
        if previous.field_a != instance.field_a: # fielad will be updated
            pass  # write your code hier

Ответ 5

Супер просто, едва неудобство;)

Нахальный способ сделать это - установить attr в объекте при предварительном сохранении, например, так

from django.db.models.signals import post_save, pre_save
from django.dispatch import receiver


@receiver(pre_save, sender=MyModel)
def my_model_pre_save(instance: MyModel, **kwargs) -> None:
    # Check if the instance has an id, if not, is_new will be true
    instance.__setattr__('is_new', not instance.id)


@receiver(post_save, sender=MyModel)
def my_model_post_save(instance: MyModel, **kwargs) -> None:
    # Check if the current object is new 
    if instance.is_new:
        print('This is a new record')

Конечно, я не слишком уверен, будут ли недостатки, но я пока не заметил какого-либо странного поведения.