Привязка к истории моделей администраторов Django

Настройка:

  • Я работаю над приложением Django, которое позволяет пользователям создавать объект в базе данных, а затем возвращаться и редактировать его столько, сколько они пожелают.
  • Административный сайт Django хранит историю изменений, внесенных в объекты через сайт администратора.

Вопрос:

  • Как подключить мое приложение к истории изменений сайта администратора, чтобы я мог видеть историю изменений, внесенных пользователями в их "контент"?

Ответ 1

История администратора - это просто приложение, подобное любому другому приложению 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 - это объект, который был изменен, конечно.

Теперь я вижу, что Дэниел отвечает и согласен с ним, он довольно ограничен.

По моему мнению, более сильный подход - использовать код Марти Алчин в своей книге Pro Django (см. Сохранение исторических записей, начиная со страницы 263). Существует приложение django-simple-history, которое реализует и расширяет этот подход (docs здесь).

Ответ 2

Журнал истории изменений admin определен в django.contrib.admin.models, а в стандартном классе ModelAdmin есть метод history_view.

Они не особенно умны, хотя и довольно тесно связаны с администратором, поэтому вам может быть лучше всего использовать их для идей и создания собственной версии для вашего приложения.

Ответ 3

Чтобы добавить к сказанному, вот некоторые другие ресурсы для вас:

(1) Я работал с приложением под названием django-reversion, которое "зацепило" историю админов и фактически добавило к ней, Если вам нужен образец кода, который будет хорошим местом для просмотра.

(2) Если вы решили перевернуть свою собственную историческую функциональность, django предоставляет сигналы, на которые вы могли подписаться, чтобы иметь свой дескриптор приложения, например post_save для каждого объекта истории. Ваш код будет запускаться каждый раз, когда запись журнала истории будет сохранена. Doc: Сигналы Django

Ответ 4

Я знаю, что этот вопрос старый, но на сегодняшний день (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.contrib.admin.models.py - это место, где он находится в django 1.9) и исключая элементы, в которых нет изменений, заказывая его по времени действия и отображая только последние 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>

Ответ 5

Пример кода

Здравствуйте,

Недавно я взломал некоторые записи в виде "обновления" для нашей базы данных инвентаризации серверов. Я решил, что поделюсь своим "примерным" кодом. Следующая функция берет один из наших объектов "Сервер", список вещей, которые были изменены, и action_flag либо ADDITION, либо CHANGE. Это упрощает работу с небольшим количеством бит, где 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/info/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."