views:

1675

answers:

4

The Setup:

  • I'm working on a Django application which allows users to create an object in the database and then go back and edit it as much as they desire.
  • Django's admin site keeps a history of the changes made to objects through the admin site.

The Question:

  • How do I hook my application in to the admin site's change history so that I can see the history of changes users make to their "content" ?
+3  A: 

The admin's change history log is defined in django.contrib.admin.models, and there's a history_view method in the standard ModelAdmin class.

They're not particularly clever though, and fairly tightly coupled to the admin, so you may be best just using these for ideas and creating your own version for your app.

Daniel Roseman
+12  A: 

The admin history is just an app like any other Django app, with the exception being special placement on the admin site.

The model is in django.contrib.admin.models.LogEntry.

When a user makes a change, add to the log like this (stolen shamelessly from 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
)

where object is the object that was changed of course.

Now I see Daniel's answer and agree with him, it is pretty limited.

In my opinion a stronger approach is to use the code from Marty Alchin in his book Pro Django (see Keeping Historical Records starting at page 263).

Van Gale
Don't forget: from django.contrib.contenttypes.models import ContentType. Also, force_unicode is also their own function.
sakabako
+1  A: 

To add to what's already been said, here are some other resources for you:

(1) I've been working with an app called django-reversion which 'hooks into' the admin history and actually adds to it. If you wanted some sample code that would be a good place to look.

(2) If you decided to roll your own history functionality django provides signals that you could subscribe to to have your app handle, for instance, post_save for each history object. Your code would run each time a history log entry was saved. Doc: Django signals

T. Stone
A: 

Example Code

Hello,

I recently hacked in some logging to an "update" view for our server inventory database. I figured I would share my "example" code. The function which follows takes one of our "Server" objects, a list of things which have been changed, and an action_flag of either ADDITION or CHANGE. It simplifies things a wee bit where ADDITION means "added a new server." A more flexible approach would allow for adding an attribute to a server. Of course, it was sufficiently challenging to audit our existing functions to determine if a changes had actually taken place, so I am happy enough to log new attributes as a "change".

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