Грязные поля в джанго
17967 просмотра
10 ответа
В моем приложении мне нужно сохранить измененные значения (старые и новые), когда модель будет сохранена. Любые примеры или рабочий код?
Мне нужно это для предварительного прослушивания контента. Например, если пользователь меняет что-то в модели, администратор может видеть все изменения в отдельной таблице, а затем решает применить их или нет.
Автор: Dmitry Shevchenko Источник Размещён: 17.05.2019 03:35Ответы (10)
14 плюса
Вы не очень много говорили о своем конкретном случае использования или потребностях. В частности, было бы полезно знать, что вам нужно делать с информацией об изменении (сколько времени вам нужно ее хранить?). Если вам нужно сохранить его только в преходящих целях, решение сессии S.Lott может быть лучше. Если вы хотите получить полный контрольный список всех изменений ваших объектов, хранящихся в БД, попробуйте это решение AuditTrail .
ОБНОВЛЕНИЕ : Код AuditTrail, который я связал с выше, является самым близким, который я видел к полному решению, которое будет работать для вашего дела, хотя оно имеет некоторые ограничения (вообще не работает для полей ManyToMany). Он сохранит все предыдущие версии ваших объектов в БД, чтобы администратор мог вернуться к любой предыдущей версии. Вам придется немного поработать с ним, если вы хотите, чтобы изменения не вступили в силу до одобрения.
Вы также можете создать собственное решение, основанное на чем-то вроде DiffingMixin от @Armin Ronacher. Вы бы сохранили словарь diff (возможно, маринован?) В таблице, чтобы администратор просмотрел его позже и применил, если это необходимо (вам нужно будет написать код, чтобы взять словарь diff и применить его к экземпляру).
Автор: Carl Meyer Размещён: 21.09.2008 02:0420 плюса
Я нашел идею Армина очень полезной. Вот моя вариация;
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_state
dict после сохранения. Но я не хотел переписывать save()
метод, так как большую часть времени мы отбрасываем экземпляры модели после сохранения.
def save(self, *args, **kwargs):
super(Klass, self).save(*args, **kwargs)
self._original_state = self._as_dict()
Автор: muhuk
Размещён: 01.12.2008 09:09
12 плюса
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.
6 плюса
Я расширил решение Трей Хуннера для поддержки отношений 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
5 плюса
Добавление второго ответа, потому что многое изменилось с тех пор, как эти вопросы были изначально отправлены .
В мире Django существует множество приложений, которые решают эту проблему сейчас. Вы можете найти полный список моделей аудита и истории приложений на сайте Django Packages.
Я написал сообщение в блоге, сравнивающее некоторые из этих приложений. Это сообщение сейчас 4 года, и он немного устарел. Различные подходы к решению этой проблемы, похоже, одинаковы.
Подходы:
- Хранить все исторические изменения в сериализованном формате (JSON?) В одной таблице
- Храните все исторические изменения в таблице, отражающей оригинал для каждой модели
- Храните все исторические изменения в той же таблице, что и исходная модель (я не рекомендую это)
Пакет django-reversion по- прежнему является самым популярным решением этой проблемы. Он использует первый подход: сериализуйте изменения вместо зеркалирования таблиц.
Несколько лет назад я ожил django-simple-history . Это второй подход: зеркало каждой таблицы.
Поэтому я бы рекомендовал использовать приложение для решения этой проблемы . Есть пара популярных, которые работают очень хорошо на данный момент.
О, и если вы просто ищете проверку грязного поля и не сохраняете все исторические изменения, посмотрите FieldTracker из django-model-utils .
Автор: Trey Hunner Размещён: 31.12.2015 10:113 плюса
Продолжая предложение Мухука и добавляя сигналы 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:433 плюса
Я расширил решения 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
2 плюса
Если вы используете свои собственные транзакции (а не приложение администратора по умолчанию), вы можете сохранить до и после версий вашего объекта. Вы можете сохранить предыдущую версию в сеансе, или вы можете поместить ее в «скрытые» поля в форме. Скрытые поля - кошмар безопасности. Поэтому используйте сеанс, чтобы сохранить историю того, что происходит с этим пользователем.
Кроме того, конечно, вам нужно получить предыдущий объект, чтобы вы могли вносить в него изменения. Таким образом, у вас есть несколько способов контролировать различия.
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
0 плюса
Обновленное решение с поддержкой 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
-1 плюса
для информации каждого, решение muhuk выходит из строя под python2.6, поскольку оно вызывает исключение, указывающее «объект .__ init __ ()» не принимает аргументов ...
Редактировать: ho! по-видимому, это могло быть неправильное использование микса ... Я не обращал внимания и объявил его последним родителем, и из-за этого вызов init закончился в родительском объекте, а не в следующем родителе, поскольку он норамли с алмазом Наследование диаграмм! поэтому, пожалуйста, не обращайте внимания на мой комментарий :)
Автор: tarmath Размещён: 23.06.2009 01:03Вопросы из категории :
- python Обработка XML в Python
- python Как я могу использовать Python itertools.groupby ()?
- python Python: На какой ОС я работаю?
- python Как я могу создать непосредственно исполняемое кроссплатформенное приложение с графическим интерфейсом на Python?
- python Вызов функции модуля с использованием его имени (строки)
- python Звук Питона («Колокол»)
- python Regex и unicode
- python Создать зашифрованный ZIP-файл в Python
- python Создайте базовый итератор Python
- python Функция транспонирования / распаковки (обратная сторона zip)?
- django Использование виджетов времени / даты в Django в произвольной форме
- django Django + FCGID на Fedora Core 9 - что мне не хватает?
- django Грязные поля в джанго
- django Есть ли простой способ заполнить SlugField из CharField?
- django Захват параметров URL в запросе. GET
- django Модель limit_choices_to = {'user': user}
- django В интерфейсе администратора Django есть способ дублировать элемент?
- django Ввести ошибки в уже утвержденную форму?
- django How do I perform HTML decoding/encoding using Python/Django?
- django Шаблонные переменные Django и Javascript