与 Django 管理员的模型历史紧密相连

设置:

  • 我正在开发一个 Django 应用程序,它允许用户在数据库中创建一个对象,然后按照自己的需要返回并进行编辑。
  • Django 的管理站点保存了通过管理站点对对象所做的更改的历史记录。

问题:

  • 如何将我的应用程序连接到管理站点的更改历史记录,以便我可以看到用户对其“内容”所做的更改的历史记录?
68629 次浏览

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.

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.utils.encoding import force_unicode
from django.contrib.contenttypes.models import ContentType
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). There is an application django-simple-history which implements and extends this approach (docs here).

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

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."

I know this question is old, but as of today (Django 1.9), Django's history items are more robust than they were at the date of this question. In a current project, I needed to get the recent history items and put them into a dropdown from the navbar. This is how I did it and was very straight forward:

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

As seen in the above code snippet, I'm creating a basic queryset from the LogEntry model (django.contrib.admin.models.py is where it's located in django 1.9) and excluding the items where no changes are involved, ordering it by the action time and only showing the past 20 logs. I'm also getting another item with just the count. If you look at the LogEntry model, you can see the field names that Django has used in order to pull back the pieces of data that you need. For my specific case, here is what I used in my template:

Link to Image Of Final Product

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

Example code:

from django.contrib.contenttypes.models import ContentType
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=str(object),
action_flag=ADDITION,
)

Object is the object you want to register in the admin site log.
You can try with str() class in the parameter object_repr.