绑定Django Admin的模型历史记录


93

设置:

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

问题:

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

Answers:


133

管理员历史记录只是一个与其他Django应用程序一样的应用程序,唯一的例外是管理网站上的特殊位置。

该模型位于django.contrib.admin.models.LogEntry中。

当用户进行更改时,请像下面这样添加到日志中(从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
)

object当然,更改的对象在哪里。

现在我看到了丹尼尔的回答并同意他的意见,这非常有限。

在我看来,一种更有效的方法是使用Marty Alchin在他的《Pro Django》一书中的代码(请参阅第263页的保留历史记录)。有一个django-simple-history应用程序,它实现并扩展了这种方法(docs此处)。


7
不要忘记:从django.contrib.contenttypes.models导入ContentType。同样,force_unicode也是它们自己的功能。
sakabako

10
from django.utils.encoding import force_unicodefor'force_unicode'– mmrs151
2011年

17
自从回答了这个问题以来,Marty Alchin的方法已经开源并在名为django-simple-history的应用程序中进行了扩展。
Trey Hunner 2011年

5
新家的Django简单的历史似乎是:github.com/treyhunner/django-simple-history对RTD更多信息django-simple-history.readthedocs.org/en/latest
布鲁图

3
好的方法还可以在djangopackages.com上检查比较网格,在网格中比较 django-simple-history和其他解决方案(如CleanerVersion或django-reversion)。
maennel

21

管理员的更改历史记录日志在中定义django.contrib.admin.modelshistory_view标准中有一种方法ModelAdmin类中。

它们并不是特别聪明,并且与管理员紧密联系在一起,因此,最好将它们用于构想并为应用创建自己的版本。


4
这仍然是真的吗?
单击此处

11

我知道这个问题很老,但是到今天(Django 1.9)为止,Django的历史记录比这个问题提出时的功能更加强大。在当前项目中,我需要获取最近的历史记录项目并将其从导航栏中放入下拉列表中。这是我的做法,非常简单:

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

如上面的代码片段所示,我正在从LogEntry模型(django 1.9中位于django.contrib.admin.models.py的位置)中创建一个基本的查询集,并排除不涉及任何更改的项目,并按以下顺序排序操作时间,仅显示过去的20条日志。我还拿到了另外一件商品。如果查看LogEntry模型,则可以看到Django为了拉回所需数据而使用的字段名。对于我的特定情况,这是我在模板中使用的内容:

链接到最终产品的图像

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

7

要补充说的话,这里还有一些其他资源供您参考:

(1)我一直在使用一个名为django-reversion的应用程序 “插入”管理历史记录并实际添加到其中。如果您想要一些示例代码,那将是一个不错的地方。

(2)如果您决定使用自己的历史记录功能,则django提供您可以订阅的信号以使您的应用程序具有句柄,例如,每个历史记录对象的post_save。您的代码将在每次保存历史记录条目时运行。Doc: Django信号


3
强烈建议不要使用django-reversion。从概念上讲,这是个好主意,但实现起来很糟糕。我在生产现场使用了它,这是一场噩梦。起初它运行良好,但是我最终发现该应用程序根本无法扩展,因此对于任何半频繁更改的模型,您的管理员将在几个月内无法使用,因为它使用的查询效率极低。
塞林2014年

@Cerin和其他人:这仍然是真的吗?我正在尝试查看是否可以对具有大量内容的网站使用django-reversion。django-reversion似乎在djangopackages.org和SO帖子上排名最高,但是能够按比例缩放是我的应用程序的重要优先事项,因此问
Anupam'5

1
@Anupam,我没有使用过它,因为我不得不从生产站点中禁用它。我将问题报告为错误,但是开发人员让我大吃一惊,并说这不是问题,因此我没有对项目进行重新评估。
塞林

我知道了-您介意分享问题链接吗?将超级利于我,因为我很认真地考虑是否要使用它,或者不适合我的Django应用程序
阿努邦

3

范例程式码

你好,

我最近入侵了一些日志,以获取服务器清单数据库的“更新”视图。我想我会分享我的“示例”代码。后面的函数采用我们的“服务器”对象之一,已更改的内容的列表以及ADDITION或CHANGE的action_flag。它简化了事情,ADDITION意味着“添加了新服务器”。一种更灵活的方法将允许向服务器添加属性。当然,审核我们现有的功能以确定是否确实发生了更改是充满挑战的,因此我很高兴将新属性记录为“更改”。

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."
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.