Lien avec l'historique des modèles de Django Admin

93

La mise en place:

  • Je travaille sur une application Django qui permet aux utilisateurs de créer un objet dans la base de données, puis de revenir en arrière et de le modifier autant qu'ils le souhaitent.
  • Le site d'administration de Django conserve un historique des modifications apportées aux objets via le site d'administration.

La question:

  • Comment connecter mon application à l'historique des modifications du site d'administration afin de pouvoir consulter l'historique des modifications que les utilisateurs apportent à leur «contenu»?
akdom
la source

Réponses:

135

L'historique d'administration n'est qu'une application comme n'importe quelle autre application Django, à l'exception du placement spécial sur le site d'administration.

Le modèle se trouve dans django.contrib.admin.models.LogEntry.

Lorsqu'un utilisateur apporte une modification, ajoutez au journal comme ceci (volé sans vergogne à 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
)

objectest l'objet qui a été changé bien sûr.

Maintenant, je vois la réponse de Daniel et je suis d'accord avec lui, c'est assez limité.

À mon avis, une approche plus forte consiste à utiliser le code de Marty Alchin dans son livre Pro Django (voir Keeping Historical Records à partir de la page 263). Il existe une application django-simple-history qui implémente et étend cette approche ( docs ici ).

Van Gale
la source
7
N'oubliez pas: depuis django.contrib.contenttypes.models, importez ContentType. En outre, force_unicode est également leur propre fonction.
sakabako
10
from django.utils.encoding import force_unicodepour 'force_unicode'
mmrs151
17
Depuis que cette question a été répondue, l'approche de Marty Alchin a été open source et étendue dans une application appelée django-simple-history .
Trey Hunner le
5
La nouvelle maison de django-simple-history semble être: github.com/treyhunner/django-simple-history Plus d'informations sur RTD django-simple-history.readthedocs.org/en/latest
Brutus
3
Une bonne approche peut aussi vérifier la grille de comparaison sur djangopackages.com où django-simple-history et d'autres solutions (comme CleanerVersion ou django-reversion) sont comparées.
maennel
22

Le journal de l'historique des modifications de l'administrateur est défini dans django.contrib.admin.models, et il existe une history_viewméthode dans la ModelAdminclasse standard .

Cependant, ils ne sont pas particulièrement intelligents et assez étroitement liés à l'administrateur, vous feriez donc mieux de les utiliser simplement pour des idées et de créer votre propre version pour votre application.

Daniel Roseman
la source
4
Est-ce toujours vrai?
cliquez ici le
11

Je sais que cette question est ancienne, mais à partir d'aujourd'hui (Django 1.9), les éléments d'histoire de Django sont plus robustes qu'ils ne l'étaient à la date de cette question. Dans un projet en cours, j'avais besoin d'obtenir les éléments d'historique récents et de les placer dans une liste déroulante à partir de la barre de navigation. Voici comment je l'ai fait et c'était très simple:

*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})

Comme on le voit dans l'extrait de code ci-dessus, je crée un jeu de requêtes de base à partir du modèle LogEntry (django.contrib.admin.models.py est l'endroit où il se trouve dans django 1.9) et exclut les éléments où aucun changement n'est impliqué, en le triant par le temps d'action et ne montrant que les 20 derniers journaux. Je reçois également un autre article avec juste le décompte. Si vous regardez le modèle LogEntry, vous pouvez voir les noms de champs que Django a utilisés pour récupérer les données dont vous avez besoin. Pour mon cas spécifique, voici ce que j'ai utilisé dans mon modèle:

Lien vers l'image du produit 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
la source
7

Pour ajouter à ce qui a déjà été dit, voici quelques autres ressources pour vous:

(1) J'ai travaillé avec une application appelée django-reversion qui `` se connecte '' à l'historique d'administration et y ajoute en fait. Si vous vouliez un exemple de code, ce serait un bon endroit pour regarder.

(2) Si vous décidez de lancer votre propre fonctionnalité d'historique, django fournit des signaux auxquels vous pouvez vous abonner pour que votre application gère, par exemple, post_save pour chaque objet d'historique. Votre code s'exécutait chaque fois qu'une entrée de journal d'historique était enregistrée. Doc: Signaux Django

T. Stone
la source
3
Je déconseille fortement django-reversion. Dans le concept, c'est une excellente idée, mais la mise en œuvre est terrible. Je l'ai utilisé dans un site de production et c'était un cauchemar. Cela a très bien fonctionné au début, mais j'ai finalement découvert que l'application n'était pas du tout évolutive, donc pour tous les modèles avec des changements semi-fréquents, votre administrateur deviendra inutilisable dans quelques mois car les requêtes qu'il utilise sont horriblement inefficaces.
Cerin
@Cerin et autres: est-ce toujours vrai? J'essaie de voir si je peux utiliser django-reversion pour un site avec beaucoup de contenu. django-reversion semble être le mieux noté sur djangopackages.org et les publications SO, mais être capable de faire évoluer est une priorité importante pour mon application, donc demander
Anupam
1
@Anupam, je ne l'ai pas utilisé depuis que j'ai dû le désactiver de mon site de production. J'ai signalé les problèmes comme un bogue, mais le développeur m'a bluffé et m'a dit que ce n'était pas un problème, donc je n'ai pas réévalué le projet.
Cerin
Je vois - pouvez-vous partager le lien du problème, s'il vous plaît? Sera très utile pour moi car je réfléchis sérieusement à l'opportunité de l'utiliser ou non pour mon application Django
Anupam
3

Exemple de code

Bonjour,

J'ai récemment piraté dans une certaine connexion à une vue "mise à jour" pour notre base de données d'inventaire de serveur. J'ai pensé que je partagerais mon code "exemple". La fonction qui suit prend l'un de nos objets "Serveur", une liste de choses qui ont été modifiées et un drapeau d'action ADDITION ou CHANGE. Cela simplifie un peu les choses où ADDITION signifie «a ajouté un nouveau serveur». Une approche plus flexible permettrait d'ajouter un attribut à un serveur. Bien sûr, il était suffisamment difficile de vérifier nos fonctions existantes pour déterminer si un changement avait réellement eu lieu, donc je suis assez heureux d'enregistrer de nouveaux attributs en tant que «changement».

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
la source