Vinculación con el historial del modelo de administración de Django

94

La puesta en marcha:

  • Estoy trabajando en una aplicación Django que permite a los usuarios crear un objeto en la base de datos y luego regresar y editarlo tanto como deseen.
  • El sitio de administración de Django mantiene un historial de los cambios realizados en los objetos a través del sitio de administración.

La pregunta:

  • ¿Cómo conecto mi aplicación al historial de cambios del sitio de administración para poder ver el historial de cambios que los usuarios realizan en su "contenido"?
akdom
fuente

Respuestas:

136

El historial de administración es solo una aplicación como cualquier otra aplicación de Django, con la excepción de una ubicación especial en el sitio de administración.

El modelo está en django.contrib.admin.models.LogEntry.

Cuando un usuario realiza un cambio, agréguelo al registro de esta manera (robado 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
)

dónde objectestá el objeto que se cambió, por supuesto.

Ahora veo la respuesta de Daniel y estoy de acuerdo con él, es bastante limitada.

En mi opinión, un enfoque más sólido es utilizar el código de Marty Alchin en su libro Pro Django (consulte Cómo mantener registros históricos a partir de la página 263). Existe una aplicación django-simple-history que implementa y extiende este enfoque ( docs aquí ).

Van Gale
fuente
7
No lo olvides: desde django.contrib.contenttypes.models importa ContentType. Además, force_unicode también es su propia función.
sakabako
10
from django.utils.encoding import force_unicodepara 'force_unicode'
mmrs151
17
Desde que se respondió esta pregunta, el enfoque de Marty Alchin ha sido de código abierto y extendido en una aplicación llamada django-simple-history .
Trey Hunner
5
El nuevo hogar de django-simple-history parece ser: github.com/treyhunner/django-simple-history Más información sobre RTD django-simple-history.readthedocs.org/en/latest
Brutus
3
Un buen enfoque también puede verificar la cuadrícula de comparación en djangopackages.com donde se comparan django-simple-history y otras soluciones (como CleanerVersion o django-reversion).
maennel
22

El registro del historial de cambios del administrador está definido en django.contrib.admin.models, y hay un history_viewmétodo en la ModelAdminclase estándar .

Sin embargo, no son particularmente inteligentes y están bastante unidos al administrador, por lo que es mejor que los use como ideas y cree su propia versión para su aplicación.

Daniel Roseman
fuente
4
¿Sigue siendo cierto esto?
haga clic aquí
12

Sé que esta pregunta es antigua, pero a partir de hoy (Django 1.9), los elementos del historial de Django son más sólidos de lo que eran en la fecha de esta pregunta. En un proyecto actual, necesitaba obtener los elementos del historial reciente y colocarlos en un menú desplegable de la barra de navegación. Así es como lo hice y fue muy sencillo:

*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 se ve en el fragmento de código anterior, estoy creando un conjunto de consultas básico desde el modelo LogEntry (django.contrib.admin.models.py es donde se encuentra en django 1.9) y excluyendo los elementos donde no hay cambios involucrados, ordenándolos por el tiempo de acción y solo muestra los últimos 20 registros. También obtengo otro artículo con solo el recuento. Si observa el modelo LogEntry, puede ver los nombres de campo que Django ha utilizado para extraer los datos que necesita. Para mi caso específico, esto es lo que usé en mi plantilla:

Enlace a la imagen del producto 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
fuente
7

Para agregar a lo que ya se ha dicho, aquí hay algunos otros recursos para usted:

(1) He estado trabajando con una aplicación llamada django-reversion que se 'engancha' al historial de administración y en realidad lo agrega. Si quisiera algún código de muestra, sería un buen lugar para buscar.

(2) Si decidió lanzar su propia funcionalidad de historial, django proporciona señales a las que puede suscribirse para que su aplicación maneje, por ejemplo, post_save para cada objeto de historial. Su código se ejecutará cada vez que se guarde una entrada de registro del historial. Doc: señales de Django

T. Stone
fuente
3
Yo fuertemente recomiendo contra django-reversión. En concepto, es una gran idea, pero la implementación es terrible. Usé esto en un sitio de producción y fue una pesadilla. Funcionó muy bien al principio, pero finalmente descubrí que la aplicación no es escalable en absoluto, por lo que para cualquier modelo con cambios semi-frecuentes, su administrador quedará inutilizable en unos pocos meses porque las consultas que utiliza son terriblemente ineficientes.
Cerin
@Cerin y otros: ¿esto sigue siendo cierto? Estoy tratando de ver si puedo usar django-reversion para un sitio con mucho contenido. django-reversion parece tener la mejor calificación en las publicaciones de djangopackages.org y SO, pero poder escalar es una prioridad importante para mi aplicación, por lo que pregunto
Anupam
1
@Anupam, no lo he usado desde que tuve que deshabilitarlo desde mi sitio de producción. Informé los problemas como un error, pero el desarrollador me sorprendió y dijo que no era un problema, así que no he vuelto a evaluar el proyecto.
Cerin
Ya veo, ¿te importaría compartir el enlace del problema, por favor? Será muy útil para mí ya que estoy considerando seriamente si usarlo o no para mi aplicación Django
Anupam
3

Código de ejemplo

Hola,

Hace poco pirateé algunos registros a una vista de "actualización" para la base de datos de inventario de nuestro servidor. Pensé que compartiría mi código de "ejemplo". La función que sigue toma uno de nuestros objetos "Servidor", una lista de cosas que han sido cambiadas y un action_flag de ADDITION o CHANGE. Simplifica un poco las cosas donde ADICIÓN significa "agregado un nuevo servidor". Un enfoque más flexible permitiría agregar un atributo a un servidor. Por supuesto, fue lo suficientemente desafiante auditar nuestras funciones existentes para determinar si realmente se habían producido cambios, por lo que estoy lo suficientemente feliz de registrar nuevos atributos como un "cambio".

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
fuente