Vinculando-se à história do modelo do Django Admin

93

A configuração:

  • Estou trabalhando em um aplicativo Django que permite aos usuários criar um objeto no banco de dados e depois voltar e editá-lo tanto quanto desejarem.
  • O site de administração do Django mantém um histórico das mudanças feitas nos objetos através do site de administração.

A questão:

  • Como conectar meu aplicativo ao histórico de alterações do site de administração para que eu possa ver o histórico de alterações que os usuários fazem em seu "conteúdo"?
Akdom
fonte

Respostas:

133

O histórico de administração é apenas um aplicativo como qualquer outro aplicativo Django, com exceção de um posicionamento especial no site de administração.

O modelo está em django.contrib.admin.models.LogEntry.

Quando um usuário faz uma alteração, adicione ao log desta forma (roubado descaradamente de contrib / admin / options.py:

from django.contrib.admin.models import LogEntry, ADDITION
LogEntry.objects.log_action(
    user_id         = request.user.pk, 
    content_type_id = ContentType.objects.get_for_model(object).pk,
    object_id       = object.pk,
    object_repr     = force_unicode(object), 
    action_flag     = ADDITION
)

onde objectestá o objeto que foi alterado de curso.

Agora eu vejo a resposta de Daniel e concordo com ele, é bastante limitada.

Em minha opinião, uma abordagem mais forte é usar o código de Marty Alchin em seu livro Pro Django (consulte Mantendo registros históricos a partir da página 263). Existe um aplicativo django-simple-history que implementa e estende essa abordagem ( docs aqui ).

Van Gale
fonte
7
Não se esqueça: de django.contrib.contenttypes.models import ContentType. Além disso, force_unicode também é sua própria função.
Sakabako
10
from django.utils.encoding import force_unicodepara 'force_unicode'
mmrs151
17
Desde que esta pergunta foi respondida, a abordagem de Marty Alchin foi aberta e ampliada em um aplicativo chamado django-simple-history .
Trey Hunner
5
O novo lar de django-simple-history parece ser: github.com/treyhunner/django-simple-history Mais informações sobre RTD django-simple-history.readthedocs.org/en/latest
Brutus
3
Uma boa abordagem também pode verificar a grade de comparação em djangopackages.com, onde django-simple-history e outras soluções (como CleanerVersion ou django-reversion) são comparadas.
maennel de
21

O registro do histórico de alterações do administrador é definido em django.contrib.admin.modelse há um history_viewmétodo na ModelAdminclasse padrão .

No entanto, eles não são particularmente inteligentes e estão intimamente ligados ao administrador, então talvez seja melhor usá-los apenas para ter ideias e criar sua própria versão para seu aplicativo.

Daniel Roseman
fonte
4
Isso ainda é verdade?
clique aqui
11

Eu sei que esta questão é antiga, mas a partir de hoje (Django 1.9), os itens de história do Django são mais robustos do que eram na data desta questão. Em um projeto atual, precisei obter os itens de histórico recentes e colocá-los em uma lista suspensa da barra de navegação. Foi assim que fiz e foi muito direto:

*views.py*    

from django.contrib.admin.models import LogEntry, ADDITION, CHANGE, DELETION

def main(request, template):

    logs = LogEntry.objects.exclude(change_message="No fields changed.").order_by('-action_time')[:20]
    logCount = LogEntry.objects.exclude(change_message="No fields changed.").order_by('-action_time')[:20].count()

    return render(request, template, {"logs":logs, "logCount":logCount})

Como visto no trecho de código acima, estou criando um queryset básico do modelo LogEntry (django.contrib.admin.models.py é onde ele está localizado no django 1.9) e excluindo os itens onde nenhuma mudança está envolvida, ordenando-o por o tempo de ação e mostrando apenas os últimos 20 registros. Também estou recebendo outro item apenas com a contagem. Se você olhar o modelo LogEntry, poderá ver os nomes dos campos que o Django usou para recuperar os dados de que você precisa. Para o meu caso específico, aqui está o que usei no meu modelo:

Link para a imagem do produto final

*template.html*

<ul class="dropdown-menu">
    <li class="external">
        <h3><span class="bold">{{ logCount }}</span> Notification(s) </h3>
        <a href="{% url 'index' %}"> View All </a>
    </li>
        {% if logs %}
            <ul class="dropdown-menu-list scroller actionlist" data-handle-color="#637283" style="height: 250px;">
                {% for log in logs %}
                    <li>
                        <a href="javascript:;">
                            <span class="time">{{ log.action_time|date:"m/d/Y - g:ia" }} </span>
                            <span class="details">
                                {% if log.action_flag == 1 %}
                                    <span class="label label-sm label-icon label-success">
                                        <i class="fa fa-plus"></i>
                                    </span>
                                {% elif log.action_flag == 2 %}
                                    <span class="label label-sm label-icon label-info">
                                        <i class="fa fa-edit"></i>
                                    </span>
                                {% elif log.action_flag == 3 %}
                                    <span class="label label-sm label-icon label-danger">
                                        <i class="fa fa-minus"></i>
                                    </span>
                                {% endif %}
                                {{ log.content_type|capfirst }}: {{ log }}
                            </span>
                        </a>
                    </li>
                 {% endfor %}
            </ul>
        {% else %}
            <p>{% trans "This object doesn't have a change history. It probably wasn't added via this admin site." %}</p>
        {% endif %}
    </li>
</ul>
dave4jr
fonte
7

Para complementar o que já foi dito, aqui estão alguns outros recursos para você:

(1) Tenho trabalhado com um aplicativo chamado django-reversion que 'se conecta' ao histórico de administração e realmente adiciona a ele. Se você quisesse algum código de amostra, seria um bom lugar para procurar.

(2) Se você decidiu lançar sua própria funcionalidade de histórico, o django fornece sinais que você pode assinar para ter seu aplicativo manipulado, por exemplo, post_save para cada objeto de histórico. Seu código seria executado sempre que uma entrada de registro de histórico fosse salva. Doc: sinais do Django

T. Stone
fonte
3
Eu recomendaria fortemente contra a reversão do django. Em conceito, é uma ótima ideia, mas a implementação é terrível. Usei isso em um local de produção e foi um pesadelo. Funcionou muito bem no início, mas acabei descobrindo que o aplicativo não é nem um pouco escalonável, portanto, para qualquer modelo com alterações semirregentes, seu administrador ficará inutilizável em alguns meses porque as consultas que ele usa são terrivelmente ineficientes.
Cerin de
@Cerin e outros: isso ainda é verdade? Estou tentando ver se consigo usar o django-reversion para um site com muito conteúdo. django-reversion parece ser bem avaliado em postagens djangopackages.org e SO, mas ser capaz de escalar é uma prioridade importante para meu aplicativo, portanto perguntando
Anupam
1
@Anupam, não o uso desde que tive que desabilitá-lo no meu site de produção. Eu relatei os problemas como um bug, mas o desenvolvedor me surpreendeu e disse que não era um problema, então não reavaliei o projeto.
Cerin de
Entendo - você se importa em compartilhar o link do problema, por favor? Será muito útil para mim, já que estou pensando seriamente em usá-lo ou não para meu aplicativo Django
Anupam
3

Código de exemplo

Olá,

Recentemente, hackeei alguns registros para uma visualização de "atualização" do nosso banco de dados de inventário do servidor. Achei que deveria compartilhar meu código de "exemplo". A função a seguir pega um de nossos objetos "Servidor", uma lista de coisas que foram alteradas e um action_flag de ADDITION ou CHANGE. Isso simplifica um pouco as coisas, onde ADIÇÃO significa "adicionado um novo servidor". Uma abordagem mais flexível permitiria adicionar um atributo a um servidor. Claro, foi suficientemente desafiador auditar nossas funções existentes para determinar se uma mudança realmente ocorreu, então estou feliz o suficiente para registrar novos atributos como uma "mudança".

from django.contrib.admin.models import LogEntry, User, ADDITION, CHANGE
from django.contrib.contenttypes.models import ContentType

def update_server_admin_log(server, updated_list, action_flag):
    """Log changes to Admin log."""
    if updated_list or action_flag == ADDITION:
        if action_flag == ADDITION:
            change_message = "Added server %s with hostname %s." % (server.serial, server.name)
        # http://dannyman.toldme.com/2010/06/30/python-list-comma-comma-and/
        elif len(updated_list) > 1:
            change_message = "Changed " + ", ".join(map(str, updated_list[:-1])) + " and " + updated_list[-1] + "."
        else:
            change_message = "Changed " + updated_list[0] + "."
        # http://stackoverflow.com/questions/987669/tying-in-to-django-admins-model-history
        try:
            LogEntry.objects.log_action(
                # The "update" user added just for this purpose -- you probably want request.user.id
                user_id = User.objects.get(username='update').id,
                content_type_id = ContentType.objects.get_for_model(server).id,
                object_id = server.id,
                # HW serial number of our local "Server" object -- definitely change when adapting ;)
                object_repr = server.serial,
                change_message = change_message,
                action_flag = action_flag,
                )
        except:
            print "Failed to log action."
Dannyman
fonte