LINUX.ORG.RU

[django] как лучше ловить изменение значения в модели?

 


0

0

Нужно при изменении значения в имеющемся объекте или создании нового объекта записывать полученное значение в отдельную таблицу истории изменений. Если при сохранении значение не менялось - то записывать не нужно.

Как это сделать наиболее корректно и просто?

★★★★★

Можно ли в Django на поле класса повесить сеттер, проверяющий изменение значения?

KRoN73 ★★★★★
() автор топика
Ответ на: комментарий от hizel

Сейчас гляну.

..

А где в документации Django можно почтитать про параметры методов update_<field_name>? Что-то не нашёл нигде :-/

Нашёл пример на http://softwaremaniacs.org/forum/django/20516/ (update_comment_count), но непонятно, как контролировать изменения.

KRoN73 ★★★★★
() автор топика
Ответ на: комментарий от KRoN73

Тьфу, блин, тормоз. Там этот метод при save вызывают.

...

Но, как я понимаю, из save уже не узнать, менялось поле или нет.

В голову приходит только костыль в виде original_value и проверки value с original_value при save(). Неужели красивее в Django никак?

KRoN73 ★★★★★
() автор топика
Ответ на: комментарий от KRoN73

там нет в модели никакой магии типа update_foo, просто в топиках переопределен метод save(), а он дергает дополнительно метод из категорий, как-то так

hizel ★★★★★
()

Там же есть вроде встроенная в админку история изменений, не? Правда как она работает я не вникал да и сами измененные значения она не фиксирует, только сам факт выполненного юзером действия.

Думаю, можно посмотреть, как там у них во внутрях это приготовлено.

YogSagot ★★
()
Ответ на: комментарий от YogSagot

>Там же есть вроде встроенная в админку история изменений, не?

Мне просто нужно вести историю изменений параметров с сайта. Понятно, что можно тупо вручную всё реализовать, но хочется по уму. В моём PHP-фреймворке такое делается без всяких хитростей простой перегрузкой метода в классе объекта и без последующей заботы о «нестандартном» поведении объекта.

Хочется также и в Django.

KRoN73 ★★★★★
() автор топика

>Нужно при изменении значения в имеющемся объекте или создании нового объекта записывать полученное значение в отдельную таблицу истории изменений.

Если я тебя правильно понял, сделай геттер через property. А там уже считай, было изменение, или нет, после чего втыкай свой save(). А может и save() хватит.

anonymous
()

1-й способ:

class MyModel(models.Model):
    name = models.CharField(max_length=100)

    def save(self, *args, **kwargs):
        name_changed = False
        if not self.pk: # new object
            name_changed = True
        else:
            orig_obj = MyModel.objects.get(pk=self.pk)
            if orig_obj.name != self.name:
                name_changed = True
        if name_changed:
            # do something
        super(MyModel, self).save(*args, **kwargs)

2-й:

class ModelWithOriginal(models.Model):
    def __init__(self, *args, **kwargs):
        super(ModelWithOriginal, self).__init__(*args, **kwargs)
        # Store initial field values into self._original
        self._original = {}
        for field in self._meta.fields:
            try:
                # for foreign keys save in _original field_id instead of field name
                # this is for reducing database hits
                if hasattr(self, field.name + '_id'):
                    fname = field.name + '_id'
                else:
                    fname = field.name
                self._original[fname] = getattr(self, fname)
            except: # DoesNotExist
                self._original[field.name] = None
    
    def _get_changed_fields(self):
        changed = []
        for field in self._meta.fields:
            if hasattr(self, field.name + '_id'):
                fname = field.name + '_id'
            else:
                fname = field.name
            if self._original[fname] != getattr(self, fname):
                changed.append(fname)
        return changed
    
    def _get_original(self):
        return self._original
    
    changed_fields = property(_get_changed_fields)
    original = property(_get_original)
    
    class Meta:
        abstract = True

class MyModel(ModelWithOriginal):
    name = models.CharField(max_length=100)

    def save(self, *args, **kwargs):
        if 'name' in self.changed_fields or not self.pk:
            # do something
        super(MyModel, self).save(*args, **kwargs)

Я предпочитаю 2-й способ.

anti_social
()
Ответ на: комментарий от anti_social

о_ООООООО

ты что, серьезно? сигналы предоставляют тебе оба объекта - оригинальный и измененный, зачем перегружать save, зачем городить костыли???

подавился печеньем.

volh ★★
()
Ответ на: комментарий от volh

В том то и дело, что ты плохо знаешь джангу. Где тут хоть что-то сказано про оригинальный объект?

django.db.models.signals.post_save

Like pre_save, but sent at the end of the save() method.

Arguments sent with this signal:

sender
    The model class.
instance
    The actual instance being saved.
created
    A boolean; True if a new record was create. 

Если я не прав, приведи рабочий кусок кода.

anti_social
()
Ответ на: комментарий от anti_social

Да, проглядел, я почему-то увидел, что sender - это объект, а не класс.. Значит всё вот так грустно, ни инфы об изменениях, ни даже метода, позволяющего узнать, новая это запись или нет :/

Тогда что-то вроде твоего первого варианта, но опять же через сигналы где-нибудь в signals.py - потому что история изменений к самим описываемым моделям не относится, она должна происходить unobtrusive, ну и насколько я понимаю сигнал можно определить сразу для всех sender, наследовать каждую модель от ModelWithOriginal как-то очень уж коряво.

Т.е. что-то вроде

from django.dispatch import dispatcher
from django.db.models import signals

def myuberaction(hsh): 
    pass

def diff(original,new):
    pass

def track_history(sender, instance, signal, *args, **kwargs):
    orig = None if !instance.id else sender.objects.get(pk=instance.id)
    myuberaction(diff(orig,instance)) 

dispatcher.connect(track_history, sender = Somemodel, signal = signals.pre_save)

нэ?

Походу, я с рельсами слишком расслабился:

> record = Todo.first
=> #<Todo id: 1, title: "sometitle", body: "somebody", published: false, created_at: "2010-03-21 14:41:50", updated_at: "2010-03-21 14:41:50">
> record.title = "someothertitle"
=> "someothertitle"
> record.changes
=> {"title"=>["sometitle", "someothertitle"]}
>> record.title_changed?
=> true
>> record.body_changed?
=> false

volh ★★
()

Подсказали такой вариант: грузим БД в левые поля, не те, что используем при работе с объектом, а на рабочие вешаем геттер/сеттер через property().

Ну и патч для указания геттеров/сеттеров property() в модели поля, вроде, протолкнули в девелоперскую ветку... Как я понял, три года проталкивали.

KRoN73 ★★★★★
() автор топика
Ответ на: комментарий от volh

> наследовать каждую модель от ModelWithOriginal как-то очень уж коряво

Зато позволяет избежать лишнего запроса к базе, а это бывает очень полезно, если сохраняешь сразу несколько десятков объектов.

Ну и фактически твой способ - это мой первый способ, только через сигналы.

anti_social
()
Ответ на: комментарий от Binary

А мне одно только поле и нужно отлавливать :)

KRoN73 ★★★★★
() автор топика
Ответ на: комментарий от KRoN73

а если так?

class MyModel(models.Model):
    a = models.CharField(max_length=15)

    @property
    def a(self):
        return self._a

    @a.setter
    def a(self, value):
        self._a = value

А вообще зачем делать save(), если модель не менялась?

Binary ★★★★★
()
Ответ на: комментарий от Binary

чорт. сорри, но перепечатывать сил нет, идея, наверно, и так ясна

Binary ★★★★★
()
Ответ на: комментарий от Binary

Вот тоже подумал зачем! но если спроектировать так что сэйвить только измененные данные то django.db.models.signals.post_save должно хватить.

anonymous
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.