绑定 Django Admin 的模型历史

Posted

技术标签:

【中文标题】绑定 Django Admin 的模型历史【英文标题】:Tying in to Django Admin's Model History 【发布时间】:2010-11-02 12:38:40 【问题描述】:

设置:

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

问题:

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

【问题讨论】:

【参考方案1】:

管理历史记录只是一个与任何其他 Django 应用程序一样的应用程序,除了在管理站点上的特殊位置。

模型在 django.contrib.admin.models.LogEntry 中。

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

其中object 是当然被更改的对象。

现在我看到丹尼尔的回答并同意他的观点,这是相当有限的。

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

【讨论】:

别忘了:从 django.contrib.contenttypes.models 导入 ContentType。另外,force_unicode 也是自己的函数。 from django.utils.encoding import force_unicode for 'force_unicode' 自从回答了这个问题后,Marty Alchin 的方法已经开源并在一个名为 django-simple-history 的应用程序中进行了扩展。 django-simple-history 的新家似乎是:github.com/treyhunner/django-simple-history 更多关于 RTD 的信息django-simple-history.readthedocs.org/en/latest 检查comparison grid at djangopackages.com django-simple-history 和其他解决方案(如 CleanerVersion 或 django-reversion)比较的好方法。【参考方案2】:

管理员的更改历史记录在django.contrib.admin.models 中定义,标准ModelAdmin 类中有history_view 方法。

不过,它们并不是特别聪明,而且与管理员的耦合相当紧密,因此您最好将这些用于创意并为您的应用创建自己的版本。

【讨论】:

这仍然是真的吗?【参考方案3】:

我知道这个问题已经过时了,但是截至今天(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)

如上面的代码 sn-p 所示,我正在从 LogEntry 模型(django.contrib.admin.models.py 是它在 django 1.9 中的位置)创建一个基本查询集,并排除没有更改的项目涉及,按动作时间排序,只显示过去的 20 条日志。我还得到了另一个只有计数的项目。如果您查看 LogEntry 模型,您可以看到 Django 使用的字段名称,以便拉回您需要的数据片段。对于我的具体情况,这是我在模板中使用的内容:

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>

【讨论】:

【参考方案4】:

为了补充已经说过的内容,这里有一些其他资源可供您使用:

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

(2) 如果您决定推出自己的历史记录功能,django 会提供您可以订阅的信号来让您的应用处理,例如,每个历史记录对象的 post_save。每次保存历史日志条目时,您的代码都会运行。文档:Django signals

【讨论】:

我会强烈建议不要使用 django-reversion。从概念上讲,这是一个好主意,但实施起来很糟糕。我在生产现场使用它,这是一场噩梦。起初它运行良好,但我最终发现该应用程序根本无法扩展,因此对于任何具有半频繁更改的模型,您的管理员将在几个月后变得无法使用,因为它使用的查询效率非常低。 @Cerin 和其他人:这仍然是真的吗?我正在尝试查看是否可以将 django-reversion 用于具有大量内容的站点。 django-reversion 似乎在 djangopackages.org 和 SO 帖子中评价最高,但能够扩展是我的应用程序的一个重要优先事项,因此询问 @Anupam,我没有使用它,因为我不得不从我的生产站点禁用它。我将这些问题报告为一个错误,但开发人员让我大吃一惊,说这不是问题,所以我没有重新评估这个项目。 我明白了 - 你介意分享问题链接吗?对我很有帮助,因为我正在认真考虑是否将它用于我的 Django 应用程序【参考方案5】:

示例代码

你好,

我最近入侵了我们服务器库存数据库的“更新”视图的一些日志记录。我想我会分享我的“示例”代码。下面的函数采用我们的“服务器”对象之一、已更改的内容列表以及添加或更改的 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://***.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."

【讨论】:

【参考方案6】:

示例代码:

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 是您要在管理站点日志中注册的对象。 您可以在参数 object_repr 中尝试使用 str() 类。

【讨论】:

以上是关于绑定 Django Admin 的模型历史的主要内容,如果未能解决你的问题,请参考以下文章

使用 django-simple-history 跟踪具有外键历史记录的 Django 模型历史记录

在 django 中为模型创造完整历史的最佳方法是啥?

一起使用Django SimpleHistory和TabularInline

历史模型在哪里?

从应用程序更改时,django 简单历史记录不起作用

Django 1.5 扩展 admin/change_form.html 对象工具