Грязные поля в джанго

python django

17967 просмотра

10 ответа

23663 Репутация автора

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

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

Автор: Dmitry Shevchenko Источник Размещён: 21.09.2008 11:27

Ответы (10)


2 плюса

312776 Репутация автора

Если вы используете свои собственные транзакции (а не приложение администратора по умолчанию), вы можете сохранить до и после версий вашего объекта. Вы можете сохранить предыдущую версию в сеансе, или вы можете поместить ее в «скрытые» поля в форме. Скрытые поля - кошмар безопасности. Поэтому используйте сеанс, чтобы сохранить историю того, что происходит с этим пользователем.

Кроме того, конечно, вам нужно получить предыдущий объект, чтобы вы могли вносить в него изменения. Таким образом, у вас есть несколько способов контролировать различия.

def updateSomething( request, object_id ):
    object= Model.objects.get( id=object_id )
    if request.method == "GET":
        request.session['before']= object
        form= SomethingForm( instance=object )
    else request.method == "POST"
        form= SomethingForm( request.POST )
        if form.is_valid():
            # You have before in the session
            # You have the old object
            # You have after in the form.cleaned_data
            # Log the changes
            # Apply the changes to the object
            object.save()
Автор: S.Lott Размещён: 21.09.2008 11:42

14 плюса

58077 Репутация автора

Решение

Вы не очень много говорили о своем конкретном случае использования или потребностях. В частности, было бы полезно знать, что вам нужно делать с информацией об изменении (сколько времени вам нужно ее хранить?). Если вам нужно сохранить его только в преходящих целях, решение сессии S.Lott может быть лучше. Если вы хотите получить полный контрольный список всех изменений ваших объектов, хранящихся в БД, попробуйте это решение AuditTrail .

ОБНОВЛЕНИЕ : Код AuditTrail, который я связал с выше, является самым близким, который я видел к полному решению, которое будет работать для вашего дела, хотя оно имеет некоторые ограничения (вообще не работает для полей ManyToMany). Он сохранит все предыдущие версии ваших объектов в БД, чтобы администратор мог вернуться к любой предыдущей версии. Вам придется немного поработать с ним, если вы хотите, чтобы изменения не вступили в силу до одобрения.

Вы также можете создать собственное решение, основанное на чем-то вроде DiffingMixin от @Armin Ronacher. Вы бы сохранили словарь diff (возможно, маринован?) В таблице, чтобы администратор просмотрел его позже и применил, если это необходимо (вам нужно будет написать код, чтобы взять словарь diff и применить его к экземпляру).

Автор: Carl Meyer Размещён: 21.09.2008 02:04

12 плюса

26103 Репутация автора

Django в настоящее время отправляет все столбцы в базу данных, даже если вы только что изменили их. Чтобы изменить это, необходимы некоторые изменения в системе баз данных. Это может быть легко реализовано в существующем коде путем добавления в модель набора грязных полей и добавления имен столбцов к нему каждый раз, когда вы __set__используете значение столбца.

Если вам нужна эта функция, я бы посоветовал вам взглянуть на ORM Django, реализовать ее и поместить патч в трассировку Django. Это должно быть очень легко добавить, и это поможет другим пользователям. Когда вы это сделаете, добавьте крючок, который вызывается каждый раз, когда установлен столбец.

Если вы не хотите взломать Django, вы можете скопировать dict на создание объекта и разбить его.

Может быть, со смесью вот так:

class DiffingMixin(object):

    def __init__(self, *args, **kwargs):
        super(DiffingMixin, self).__init__(*args, **kwargs)
        self._original_state = dict(self.__dict__)

    def get_changed_columns(self):
        missing = object()
        result = {}
        for key, value in self._original_state.iteritems():
            if key != self.__dict__.get(key, missing):
                result[key] = value
        return result

 class MyModel(DiffingMixin, models.Model):
     pass

Этот код не проверен, но должен работать. Когда вы звоните, model.get_changed_columns()вы получаете диктовку всех измененных значений. Это, конечно, не будет работать для изменяемых объектов в столбцах, потому что исходное состояние является плоской копией dict.

Автор: Armin Ronacher Размещён: 21.09.2008 04:33

20 плюса

10889 Репутация автора

Я нашел идею Армина очень полезной. Вот моя вариация;

class DirtyFieldsMixin(object):
    def __init__(self, *args, **kwargs):
        super(DirtyFieldsMixin, self).__init__(*args, **kwargs)
        self._original_state = self._as_dict()

    def _as_dict(self):
        return dict([(f.name, getattr(self, f.name)) for f in self._meta.local_fields if not f.rel])

    def get_dirty_fields(self):
        new_state = self._as_dict()
        return dict([(key, value) for key, value in self._original_state.iteritems() if value != new_state[key]])

Edit: Я протестировал это BTW.

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

И есть еще одна вещь; вам нужно сбросить _original_statedict после сохранения. Но я не хотел переписывать save()метод, так как большую часть времени мы отбрасываем экземпляры модели после сохранения.

def save(self, *args, **kwargs):
    super(Klass, self).save(*args, **kwargs)
    self._original_state = self._as_dict()
Автор: muhuk Размещён: 01.12.2008 09:09

-1 плюса

0 Репутация автора

для информации каждого, решение muhuk выходит из строя под python2.6, поскольку оно вызывает исключение, указывающее «объект .__ init __ ()» не принимает аргументов ...

Редактировать: ho! по-видимому, это могло быть неправильное использование микса ... Я не обращал внимания и объявил его последним родителем, и из-за этого вызов init закончился в родительском объекте, а не в следующем родителе, поскольку он норамли с алмазом Наследование диаграмм! поэтому, пожалуйста, не обращайте внимания на мой комментарий :)

Автор: tarmath Размещён: 23.06.2009 01:03

3 плюса

0 Репутация автора

Продолжая предложение Мухука и добавляя сигналы Django и уникальный dispatch_uid, вы можете сбросить состояние при сохранении без переопределения save ():

from django.db.models.signals import post_save

class DirtyFieldsMixin(object):
    def __init__(self, *args, **kwargs):
        super(DirtyFieldsMixin, self).__init__(*args, **kwargs)
        post_save.connect(self._reset_state, sender=self.__class__, 
                            dispatch_uid='%s-DirtyFieldsMixin-sweeper' % self.__class__.__name__)
        self._reset_state()

    def _reset_state(self, *args, **kwargs):
        self._original_state = self._as_dict()

    def _as_dict(self):
        return dict([(f.name, getattr(self, f.name)) for f in self._meta.local_fields if not f.rel])

    def get_dirty_fields(self):
        new_state = self._as_dict()
        return dict([(key, value) for key, value in self._original_state.iteritems() if value != new_state[key]])

Которая очистит исходное состояние после сохранения без необходимости переопределять save (). Код работает, но не уверен, что такое ограничение производительности связано с подключением сигналов на __init__

Автор: user208782 Размещён: 11.11.2009 03:43

3 плюса

6208 Репутация автора

Я расширил решения muhuk и smn, чтобы включить проверку различий в первичных ключах для внешнего ключа и полей «один-к-одному»:

from django.db.models.signals import post_save

class DirtyFieldsMixin(object):
    def __init__(self, *args, **kwargs):
        super(DirtyFieldsMixin, self).__init__(*args, **kwargs)
        post_save.connect(self._reset_state, sender=self.__class__,
                            dispatch_uid='%s-DirtyFieldsMixin-sweeper' % self.__class__.__name__)
        self._reset_state()

    def _reset_state(self, *args, **kwargs):
        self._original_state = self._as_dict()

    def _as_dict(self):
        return dict([(f.attname, getattr(self, f.attname)) for f in self._meta.local_fields])

    def get_dirty_fields(self):
        new_state = self._as_dict()
        return dict([(key, value) for key, value in self._original_state.iteritems() if value != new_state[key]])

Единственное различие заключается в том, что _as_dictя изменил последнюю строку из

return dict([
    (f.name, getattr(self, f.name)) for f in self._meta.local_fields
    if not f.rel
])

в

return dict([
    (f.attname, getattr(self, f.attname)) for f in self._meta.local_fields
])

Этот mixin, как и выше, можно использовать так:

class MyModel(DirtyFieldsMixin, models.Model):
    ....
Автор: Trey Hunner Размещён: 13.01.2011 02:03

6 плюса

2634 Репутация автора

Я расширил решение Трей Хуннера для поддержки отношений m2m. Надеюсь, это поможет другим, кто ищет аналогичное решение.

from django.db.models.signals import post_save

DirtyFieldsMixin(object):
    def __init__(self, *args, **kwargs):
        super(DirtyFieldsMixin, self).__init__(*args, **kwargs)
        post_save.connect(self._reset_state, sender=self.__class__,
            dispatch_uid='%s._reset_state' % self.__class__.__name__)
        self._reset_state()

    def _as_dict(self):
        fields =  dict([
            (f.attname, getattr(self, f.attname))
            for f in self._meta.local_fields
        ])
        m2m_fields = dict([
            (f.attname, set([
                obj.id for obj in getattr(self, f.attname).all()
            ]))
            for f in self._meta.local_many_to_many
        ])
        return fields, m2m_fields

    def _reset_state(self, *args, **kwargs):
        self._original_state, self._original_m2m_state = self._as_dict()

    def get_dirty_fields(self):
        new_state, new_m2m_state = self._as_dict()
        changed_fields = dict([
            (key, value)
            for key, value in self._original_state.iteritems()
            if value != new_state[key]
        ])
        changed_m2m_fields = dict([
            (key, value)
            for key, value in self._original_m2m_state.iteritems()
            if sorted(value) != sorted(new_m2m_state[key])
        ])
        return changed_fields, changed_m2m_fields

Можно также пожелать объединить два списка полей. Для этого замените последнюю строку

return changed_fields, changed_m2m_fields

с

changed_fields.update(changed_m2m_fields)
return changed_fields
Автор: Tony Abou-Assaleh Размещён: 13.06.2012 08:57

0 плюса

3241 Репутация автора

Обновленное решение с поддержкой m2m (с использованием обновленных грязных полей и нового API-интерфейса и некоторых исправлений ошибок), основанных на приведенных выше @Trey и @ Tony. Это прошло для меня базовое тестирование света.

from dirtyfields import DirtyFieldsMixin
class M2MDirtyFieldsMixin(DirtyFieldsMixin):
    def __init__(self, *args, **kwargs):
        super(M2MDirtyFieldsMixin, self).__init__(*args, **kwargs)
        post_save.connect(
            reset_state, sender=self.__class__,
            dispatch_uid='{name}-DirtyFieldsMixin-sweeper'.format(
                name=self.__class__.__name__))
        reset_state(sender=self.__class__, instance=self)

    def _as_dict_m2m(self):
        if self.pk:
            m2m_fields = dict([
                (f.attname, set([
                    obj.id for obj in getattr(self, f.attname).all()
                ]))
                for f,model in self._meta.get_m2m_with_model()
            ])
            return m2m_fields
        return {}

    def get_dirty_fields(self, check_relationship=False):
        changed_fields = super(M2MDirtyFieldsMixin, self).get_dirty_fields(check_relationship)
        new_m2m_state = self._as_dict_m2m()
        changed_m2m_fields = dict([
            (key, value)
            for key, value in self._original_m2m_state.iteritems()
            if sorted(value) != sorted(new_m2m_state[key])
        ])
        changed_fields.update(changed_m2m_fields)
        return changed_fields

def reset_state(sender, instance, **kwargs):
    # original state should hold all possible dirty fields to avoid
    # getting a `KeyError` when checking if a field is dirty or not
    instance._original_state = instance._as_dict(check_relationship=True)
    instance._original_m2m_state = instance._as_dict_m2m()
Автор: Neil Размещён: 30.12.2015 08:26

5 плюса

6208 Репутация автора

Добавление второго ответа, потому что многое изменилось с тех пор, как эти вопросы были изначально отправлены .

В мире Django существует множество приложений, которые решают эту проблему сейчас. Вы можете найти полный список моделей аудита и истории приложений на сайте Django Packages.

Я написал сообщение в блоге, сравнивающее некоторые из этих приложений. Это сообщение сейчас 4 года, и он немного устарел. Различные подходы к решению этой проблемы, похоже, одинаковы.

Подходы:

  1. Хранить все исторические изменения в сериализованном формате (JSON?) В одной таблице
  2. Храните все исторические изменения в таблице, отражающей оригинал для каждой модели
  3. Храните все исторические изменения в той же таблице, что и исходная модель (я не рекомендую это)

Пакет django-reversion по- прежнему является самым популярным решением этой проблемы. Он использует первый подход: сериализуйте изменения вместо зеркалирования таблиц.

Несколько лет назад я ожил django-simple-history . Это второй подход: зеркало каждой таблицы.

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

О, и если вы просто ищете проверку грязного поля и не сохраняете все исторические изменения, посмотрите FieldTracker из django-model-utils .

Автор: Trey Hunner Размещён: 31.12.2015 10:11
Вопросы из категории :
32x32