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

96

Настройка:

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

Вопрос:

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

Ответы:

137

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

Ван Гейл
источник
9
Не забывайте: из django.contrib.contenttypes.models импортируйте ContentType. Кроме того, force_unicode также является их собственной функцией.
sakabako
12
from django.utils.encoding import force_unicodeдля 'force_unicode'
mmrs151 08
17
После ответа на этот вопрос подход Марти Алчина был открыт и расширен в приложении под названием django-simple-history .
Trey Hunner
5
Новый дом django-simple-history выглядит так: github.com/treyhunner/django-simple-history Дополнительная информация о RTD django-simple-history.readthedocs.org/en/latest
Брут
3
Хорошим подходом может быть также проверка таблицы сравнения на djangopackages.com, где сравниваются django-simple-history и другие решения (например, CleanerVersion или django-reversion).
maennel
22

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

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

Дэниел Розман
источник
4
Это все еще правда?
нажмите здесь
12

Я знаю, что этот вопрос старый, но на сегодняшний день (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>
dave4jr
источник
7

В дополнение к тому, что уже было сказано, вот еще несколько ресурсов для вас:

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

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

Т. Стоун
источник
3
Я настоятельно рекомендую не использовать django-reversion. По идее это отличная идея, но ужасная реализация. Я использовал это на производственной площадке, и это был кошмар. Сначала это работало отлично, но в конце концов я обнаружил, что приложение совсем не масштабируемо, поэтому для любых моделей с нечастыми изменениями ваш администратор станет непригодным для использования через несколько месяцев, потому что используемые им запросы ужасно неэффективны.
Cerin
@Cerin и другие: это все еще правда? Я пытаюсь понять, могу ли я использовать django-reversion для сайта с большим количеством контента. django-reversion, кажется, имеет наивысший рейтинг на djangopackages.org и сообщениях SO, но возможность масштабирования является важным приоритетом для моего приложения, поэтому спрашиваю
Anupam
1
@Anupam, я не использовал его, так как мне пришлось отключить его с моего рабочего сайта. Я сообщил о проблемах как об ошибке, но разработчик меня обманул и сказал, что это не проблема, поэтому я не переоценивал проект.
Cerin
Ясно - не могли бы вы поделиться ссылкой на проблему? Будет очень полезно для меня, так как я серьезно обдумываю, использовать его или нет для моего приложения Django
Анупам
3

Пример кода

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

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