campos sujos no django

votos
28

No meu aplicativo eu preciso para salvar valores alterados (velho e novo), quando model ser salvo. Quaisquer exemplos ou código de trabalho?

Eu preciso disso para premoderation de conteúdo. Por exemplo, se o usuário muda alguma coisa no modelo, então administrador pode ver todas as alterações na tabela separada e, em seguida, decidir aplicá-las ou não.

Publicado 21/09/2008 em 12:27
fonte usuário
Em outras línguas...                            


10 respostas

votos
19

Eu achei a idéia de Armin muito útil. Aqui é a minha variação;

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: Eu testei este BTW.

Desculpe sobre as longas filas. A diferença é (além dos nomes) só armazena em cache campos não-relação local. Em outras palavras, ele não armazena em cache campos de um modelo pai se presente.

E há mais uma coisa; você precisa redefinir dict _original_state depois de salvar. Mas eu não queria substituir método save () já que a maioria das vezes nós descartar instâncias do modelo depois de salvar.

def save(self, *args, **kwargs):
    super(Klass, self).save(*args, **kwargs)
    self._original_state = self._as_dict()
Respondeu 01/12/2008 em 22:09
fonte usuário

votos
13

Você não disse muito sobre o seu caso de uso específico ou necessidades. Em particular, seria útil saber o que você precisa fazer com a informação da mudança (quanto tempo você precisa para armazená-lo?). Se você só precisa armazená-lo para fins transitórios, @ solução sessão de S. Lott pode ser melhor. Se você quer uma trilha de auditoria completa de todas as alterações aos seus objetos armazenados no DB, tente esta solução AuditTrail .

ATUALIZAÇÃO : O código AuditTrail eu link acima é o mais próximo que eu vi a uma solução completa que iria trabalhar para o seu caso, embora tenha algumas limitações (não funciona em tudo para campos ManyToMany). Ele irá armazenar todas as versões anteriores de seus objetos no DB, então o administrador poderia reverter para qualquer versão anterior. Você teria que trabalhar com ele um pouco se você deseja que a alteração não terá efeito até que aprovado.

Você também pode criar uma solução personalizada com base em algo como DiffingMixin de @Armin Ronacher. Você iria armazenar o dicionário diff (talvez em conserva?) Em uma tabela para o administrador para rever mais tarde e aplicar, se desejar (você precisa escrever o código para pegar o dicionário diff e aplicá-lo a uma instância).

Respondeu 21/09/2008 em 15:04
fonte usuário

votos
10

Django é atualmente o envio de todas as colunas ao banco de dados, mesmo se você só mudou um. Para mudar isso, algumas mudanças no sistema de banco de dados seria necessário. Isso pode ser facilmente implementado no código existente, adicionando um conjunto de campos sujos para o modelo e adicionar nomes de coluna para que, cada vez que __set__um valor de coluna.

Se você precisar desse recurso, eu sugiro que você olhar para o Django ORM, implementá-lo e colocar um patch no trac Django. Deve ser muito fácil de adicionar isso e ele iria ajudar outros usuários também. Quando você faz isso, adicione um gancho que é chamado sempre que uma coluna está definido.

Se você não quer cortar na própria Django, você pode copiar o dict na criação do objeto e diff-lo.

Talvez com um mixin assim:

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

Este código não foi testado, mas deve funcionar. Quando você liga para model.get_changed_columns()você obter um dicionário de todos os valores alterados. Isto, obviamente, não vai funcionar para objetos mutáveis em colunas porque o estado original é uma cópia simples do dict.

Respondeu 21/09/2008 em 17:33
fonte usuário

votos
6

Eu estendi a solução da Trey Hunner para apoiar relações M2M. Esperemos que isto irá ajudar os outros que procuram uma solução similar.

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

Pode-se também deseja mesclar as duas listas de campo. Para isso, substituir a última linha

return changed_fields, changed_m2m_fields

com

changed_fields.update(changed_m2m_fields)
return changed_fields
Respondeu 13/06/2012 em 09:57
fonte usuário

votos
5

Adicionando uma segunda resposta, porque muita coisa mudou desde o tempo esta pergunta foi originalmente publicado .

Há uma série de aplicativos no mundo Django que resolvem este problema agora. Você pode encontrar uma completa lista de modelo de auditoria e de história aplicativos no local Pacotes do Django.

Eu escrevi um post de blog comparando alguns desses apps. Este post é agora 4 anos de idade e é um pouco antiquado. As abordagens diferentes para resolver este problema parece ser o mesmo embora.

As abordagens:

  1. Armazenar todas as mudanças históricas em um formato serializado (JSON?) Em uma única tabela
  2. Armazenar todas as mudanças históricas em uma tabela que espelham o original para cada modelo
  3. Armazenar todas as mudanças históricas na mesma mesa que o modelo original (eu não recomendo isso)

O django-reversão pacote ainda parece ser a solução mais popular para este problema. Leva a primeira abordagem: serializar mudanças em vez de tabelas de espelhamento.

I reviveu django-simple-história de alguns anos atrás. Leva a segunda abordagem: espelhar cada tabela.

Então, eu recomendo usar um aplicativo para resolver este problema . Há uma dupla popular que funcionam muito bem neste momento.

Ah, e se você está olhando apenas para verificação de campo sujo e não armazenar todas as mudanças históricas, veja FieldTracker de django-modelo-utils .

Respondeu 31/12/2015 em 22:11
fonte usuário

votos
3

Eu estendi as soluções da SMN muhuk e incluir diferença verificando as chaves primárias para chave estrangeira e one-to-one campos:

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]])

A única diferença está em _as_dictque eu mudei a última linha de

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

para

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

Este mixin, como as acima, pode ser usado assim:

class MyModel(DirtyFieldsMixin, models.Model):
    ....
Respondeu 13/01/2011 em 03:03
fonte usuário

votos
3

Continuando a sugestão de Muhuk & adicionando sinais do Django e uma dispatch_uid original você poderia repor o estado em salvar sem substituir 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]])

Que limpar o estado original uma vez salvo, sem ter que substituir save (). O código funciona, mas não sei o que a penalidade de desempenho é de sinais de conexão em __init__

Respondeu 11/11/2009 em 16:43
fonte usuário

votos
1

Se você estiver usando suas próprias transações (e não a aplicação de administrador padrão), você pode salvar o antes e depois de versões do seu objeto. Você pode salvar a versão antes da sessão, ou você pode colocá-lo em campos "escondidos" no formulário. Campos ocultos é um pesadelo de segurança. Portanto, use a sessão para manter a história do que está acontecendo com este usuário.

Além disso, é claro, você tem que buscar o objeto anterior para que você possa fazer alterações nele. Então você tem várias maneiras de monitorar as diferenças.

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()
Respondeu 21/09/2008 em 12:42
fonte usuário

votos
0

Uma solução atualizado com suporte M2M (usando atualizados dirtyfields e nova API _meta e algumas correções de bugs), com base em @Trey e @ Tony acima. Este passou alguns testes de luz básico para mim.

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()
Respondeu 30/12/2015 em 08:26
fonte usuário

votos
-1

para obter informações de todos, a solução da muhuk falha sob python2.6 como ele levanta uma exceção afirmando 'objeto .__ o init __ ()' aceita nenhum argumento ...

edit: ho! aparentemente, ele poderia ter sido me mau uso do do mixin ... eu não prestar atenção e declarou-o como o último pai e por isso a chamada para o init acabou no objeto pai, em vez do próximo pai como noramlly faria com diamante herança diagrama! então por favor desconsidere meu comentário :)

Respondeu 23/06/2009 em 02:03
fonte usuário

Cookies help us deliver our services. By using our services, you agree to our use of cookies. Learn more