Django: перед обновлением модели я хотел бы "посмотреть" на ее предыдущие атрибуты

Когда обновление/создание выполняется в модели Django (.save()), я хотел бы иметь возможность "входить" и сравнивать некоторые конкретные атрибуты с тем, что они были установлены ранее (если они ранее существовали вообще),

Я думаю, что Pre-Save Signals, с исходной моделью, сделавшей .objects.get(instance.id), но это кажется расточительным. Кроме того, проверка уже произошла в pre_save()?

Ответы

Ответ 1

проверка модели:

Обратите внимание, что full_clean() не будет вызываться автоматически, когда вы вызываете метод save() моделей

Затем, о pre-save signal, обратите внимание, что вы получаете экземпляр, который сохраняется, отправленный в качестве параметра с сообщением. Поскольку прежняя версия вашей модели существует только в базе данных, я не вижу, где еще вы могли бы получить предыдущее значение атрибутов...

Вы не говорите, почему вы хотите сделать это, так что трудно сказать, но другие решения, о которых я сейчас думаю:

* defining a custom signal that is sent everytime the attributes you are interested in are modified... This signal would then send two arguments : new value, old value
* perform the check directly when setting the attributes

Если вы дадите более подробную информацию, это может быть проще...

ИЗМЕНИТЬ:

Правильно... Если вы выбрали пользовательский 'foo_has_updated', вы не будете уверены, что эта модификация будет сохранена.

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

* With pre-save, you would be able to pre-process the data, but the saving operation might fail
* With post-save, you would be sure that the data has been saved.

Кэширование ваших переменных может быть выполнено следующим образом:

class CachedModel(models.Model):
    cached_vars = [var1, var2, varN]
    def __init__(self, *args, **kwargs):
        super(CachedModel, self).__init__(*args, **kwargs)
        self.var_cache = {}
        for var in self.cached_vars:
            self.var_cache[var] = copy.copy(getattr(self, var))

Или что-то вроде этого... Затем в вашем обработчике сигнала:

def post_save_handler(sender, **kwargs):
    instance = kwargs["instance"]
    [(instance.var_cache[var], getattr(instance, var)) for var in instance.cached_var]
    #[(<initial value>, <saved value>)

И у вас есть то, что вам нужно (я думаю)!!!

Ответ 2

Здесь моя идея: играть со свойствами.

Скажем, у вас есть этот класс:

class Foo(models.Model):
    name = models.CharField()

Вместо этого переименуйте свое поле (вам не потребуется перенос, если он первый раз вы делаете это) и:

class Foo(models.Model):
    _name = models.CharField()

    @property
    def name(self):
        return self._name

    @name.setter
    def name(self, new_value):
        if not getattr(self, '_initial_name', False):
            self._initial_name = self._name

        if new_value != self._initial_name:
            self._name_changed = True
        else:
            self._name_changed = False

        self._name = new_value

Мы добавили два атрибута в ваши экземпляры Foo: '_initial_name' и '_name_changed' и свойство: 'name'. Это не поля модели, а никогда не сохраняться в базе данных. Кроме того, вам не придется связываться с '_name' поле дольше, пока свойство name будет заботиться обо всем.

Теперь ваш обработчик сигнала 'pre_save' или 'post_save' может проверять, что изменено:

def handle_pre_save(sender, **kwargs):
    foo = kwargs['instance']
    if getattr(foo, '_name_changed', False):
        log.debug("foo changed its name from '%s' to '%s'",
                  foo._initial_name, foo.name)

Ответ 3

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

Ответ 4

Хотя я очень одобряю ответ Sébastien Piquemal, я в конечном итоге закончил использование сигналов pre_save и post_save. Вместо того, чтобы переопределять __init__(), я делаю что-то очень похожее в pre_save, а затем проверяю/сравниваю значения в post_save и издаю оттуда специальный сигнал, если выполняются определенные условия.

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

Ответ 5

Вы можете использовать сигнал pre_save и сравнить запись db (старая версия) с записью экземпляра (обновленная, но не сохраненная в версии db).

Возьмите модель, подобную этой, например:

class Person(models.Model):
    Name = models.CharField(max_length=200)

В функции pre_save вы можете сравнить версию экземпляра с версией db.

def check_person_before_saving(sender, **kwargs):
    person_instance = kwargs['instance']
    if person_instance.id:
        # you are saving a Person that is already on the db
        # now you can get the db old version of Person before the updating

        # you should wrap this code on a try/except (just in case)
        person_db = Person.objects.get(id=person_instance.id)

        # do your compares between person_db and person_instance
        ...

# connect the signal to the function. You can use a decorator if you prefer
pre_save.connect(check_person_before_saving, sender=Person)