Отслеживание изменений с момента последнего сохранения в моделях Django - PullRequest
9 голосов
/ 08 января 2010

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

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

class MyModel(models.Model):

    def save(self, force_insert=False, force_update=False, using=None):
        if self.id is not None:
            unsaved_copy = MyModel.objects.get(id=self.id)
            # Do your comparisons here
        super(MyModel, self).save(force_insert, force_update, using)

Это прекрасно работает, однако, оно попадает в базу данных для каждого экземпляра модели, которую вы сохраняете (может быть довольно неудобно, если вы делаете много таких сохранений).

Очевидно, что если кто-то может «запомнить» старые значения полей в начале времени жизни экземпляра модели (__init__), не должно быть необходимости получать копию модели из базы данных. Итак, я придумал этот маленький взломать:

class MyModel(models.Model):

    def __init__(self, *args, **kwargs):
        super(MyModel, self).__init__(*args, **kwargs)
        self.unsaved = {}
        for field in self._meta.fields:
            self.unsaved[field.name] = getattr(self, field.name, None)

    def save(self, force_insert=False, force_update=False, using=None):
        for name, value in self.unsaved.iteritems():
            print "Field:%s Old:%s New:%s" % (name, value, getattr(self, name, None))
        # old values can be accessed through the self.unsaved member
        super(MyModel, self).save(force_insert, force_update, using)

Кажется, что это работает, но использует закрытый интерфейс django.db.models.Model.

Возможно, кто-то знает более чистый способ сделать это?

Ответы [ 2 ]

2 голосов
/ 08 января 2010

Я думаю, что ваше решение выглядит разумным.

В качестве альтернативы вы можете использовать метод Manager с именем get_and_copy() (или что-то в этом роде), который подвешивает копию исходного объекта к тому, что возвращается. Затем вы можете использовать другой метод диспетчера, save_and_check(), который использует скопированный оригинал.

FWIW: если вы играете с шаблонами contrib / admin, существует переменная контекста с именем original, которая является копией исходного объекта.

Обновление: Я более внимательно посмотрел на то, что делает админ. В class ModelAdmin (находится в django / contrib / admin / options.py) есть метод под названием construct_change_message(). Он управляется formset.changed_data и formset.changed_objects, поэтому django / forms / models.py class BaseModelFormSet находится там, где происходит действие. См. Метод save_existing_objects(). Также посмотрите на метод _existing_object(). Это немного сложнее, чем я упоминал ранее, потому что они имеют дело с возможностью использования нескольких объектов, но в основном они кэшируют результаты набора запросов при первом доступе.

0 голосов
/ 21 марта 2011

Это не будет работать для приборов.loaddata команда использует models.Model.base_save.Вероятно, самым чистым методом было бы использование дескрипторов для полей, но нужно выяснить, как правильно их вставить.

...