Определите измененные поля в 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: при сохранении, как вы можете проверить, изменилось ли поле?
Ответы
Ответ 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')
Конечно, я не слишком уверен, будут ли недостатки, но я пока не заметил какого-либо странного поведения.