Powiązanie z historią modelu Django Admin

95

Ustawić:

  • Pracuję nad aplikacją Django, która pozwala użytkownikom stworzyć obiekt w bazie danych, a następnie wrócić i edytować go tak często, jak chcą.
  • Strona administratora Django przechowuje historię zmian wprowadzonych w obiektach za pośrednictwem strony administratora.

Pytanie:

  • Jak podłączyć moją aplikację do historii zmian witryny administratora, aby zobaczyć historię zmian wprowadzanych przez użytkowników w ich „zawartości”?
akdom
źródło

Odpowiedzi:

137

Historia administratora to po prostu aplikacja, jak każda inna aplikacja Django, z wyjątkiem specjalnego miejsca na stronie administratora.

Model znajduje się w django.contrib.admin.models.LogEntry.

Kiedy użytkownik dokona zmiany, dodaj do dziennika w ten sposób (skradziony bezwstydnie z 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
)

gdzie objectjest przedmiot, który został oczywiście zmieniony.

Teraz widzę odpowiedź Daniela i zgadzam się z nim, jest dość ograniczona.

Moim zdaniem silniejszym podejściem jest użycie kodu Marty'ego Alchina w jego książce Pro Django (patrz Prowadzenie zapisów historycznych od strony 263). Istnieje aplikacja django-simple-history, która implementuje i rozszerza to podejście ( dokumentacja tutaj ).

Van Gale
źródło
8
Nie zapomnij: z django.contrib.contenttypes.models zaimportuj ContentType. Ponadto force_unicode jest również ich własną funkcją.
sakabako
11
from django.utils.encoding import force_unicodedla „force_unicode”
mmrs151
17
Odkąd udzielono odpowiedzi na to pytanie, podejście Marty'ego Alchina zostało otwarte i rozszerzone w aplikacji o nazwie django-simple-history .
Trey Hunner,
5
Wydaje się, że nowym domem django-simple-history jest: github.com/treyhunner/django-simple-history Więcej informacji o RTD django-simple-history.readthedocs.org/en/latest
Brutus
3
Dobrym podejściem może być również sprawdzenie siatki porównawczej na djangopackages.com, gdzie porównuje się django-simple-history i inne rozwiązania (takie jak CleanerVersion lub django-reversion).
maennel
22

Dziennik historii zmian administratora jest zdefiniowany w programie django.contrib.admin.models, aw klasie history_viewstandardowej znajduje się metoda ModelAdmin.

Nie są jednak szczególnie sprytne i dość ściśle powiązane z administratorem, więc najlepiej będzie, jeśli użyjesz ich do pomysłów i stworzenia własnej wersji aplikacji.

Daniel Roseman
źródło
4
Czy to nadal prawda?
kliknij tutaj
12

Wiem, że to pytanie jest stare, ale na dzień dzisiejszy (Django 1.9) elementy historii Django są bardziej solidne niż były w dniu tego pytania. W bieżącym projekcie musiałem pobrać ostatnie elementy historii i umieścić je na liście rozwijanej na pasku nawigacyjnym. Tak to zrobiłem i było bardzo proste:

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

Jak widać na powyższym fragmencie kodu, tworzę podstawowy zestaw zapytań z modelu LogEntry (django.contrib.admin.models.py jest tam, gdzie znajduje się w django 1.9) i wykluczam elementy, w których nie są wprowadzane żadne zmiany, zamawiając go przez czas działania i pokazanie tylko ostatnich 20 dzienników. Otrzymuję też kolejny przedmiot z samą liczbą. Jeśli spojrzysz na model LogEntry, zobaczysz nazwy pól, których Django użył do wycofania potrzebnych fragmentów danych. W moim konkretnym przypadku oto, czego użyłem w moim szablonie:

Link do zdjęcia produktu końcowego

*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
źródło
7

Aby dodać do tego, co już zostało powiedziane, oto kilka innych zasobów dla Ciebie:

(1) Pracowałem z aplikacją o nazwie django-reversion, która „zaczepia się” o historię administratora i dodaje do niej. Jeśli potrzebujesz przykładowego kodu, byłoby to dobre miejsce do obejrzenia.

(2) Jeśli zdecydowałeś się rzucić własną funkcję historii, django dostarcza sygnały, które możesz zasubskrybować, aby twoja aplikacja obsługiwała, na przykład, post_save dla każdego obiektu historii. Twój kod byłby uruchamiany za każdym razem, gdy zapisywana byłaby pozycja dziennika historii. Doc: Sygnały Django

T. Stone
źródło
3
Chciałbym zdecydowanie odradzam Django zmiany kierunku obrotów. W koncepcji to świetny pomysł, ale implementacja jest okropna. Użyłem tego w zakładzie produkcyjnym i to był koszmar. Na początku działało świetnie, ale w końcu okazało się, że aplikacja nie jest wcale skalowalna, więc w przypadku modeli z półczęstymi zmianami administrator stanie się bezużyteczny za kilka miesięcy, ponieważ zapytania, których używa, są strasznie nieefektywne.
Cerin
@Cerin i inni: czy to nadal prawda? Próbuję sprawdzić, czy mogę użyć django-reversion dla witryny z dużą ilością treści. django-reversion wydaje się być najwyżej ocenianym na djangopackages.org i postach SO, ale możliwość skalowania jest ważnym priorytetem dla mojej aplikacji, dlatego pytam
Anupam
1
@Anupam, nie używałem go, ponieważ musiałem go wyłączyć z mojej strony produkcyjnej. Zgłosiłem te problemy jako błąd, ale deweloper mnie zdmuchnął i powiedział, że to nie jest problem, więc nie poddałem ponownej ocenie projektu.
Cerin
Rozumiem - czy możesz udostępnić link do problemu? Będzie dla mnie bardzo pomocny, ponieważ poważnie zastanawiam się, czy użyć go w mojej aplikacji Django
Anupam
3

Przykładowy kod

Witaj,

Niedawno włamałem się do niektórych funkcji logowania do widoku „aktualizacji” naszej bazy danych spisu serwerów. Pomyślałem, że udostępnię mój „przykładowy” kod. Następująca funkcja pobiera jeden z naszych obiektów „Serwer”, listę rzeczy, które zostały zmienione, oraz flagę akcji ADDITION lub CHANGE. Trochę upraszcza to, gdzie DODATEK oznacza „dodano nowy serwer”. Bardziej elastyczne podejście pozwoliłoby na dodanie atrybutu do serwera. Oczywiście wystarczająco trudne było przeprowadzenie audytu naszych istniejących funkcji w celu ustalenia, czy rzeczywiście zaszła zmiana, więc z przyjemnością rejestruję nowe atrybuty jako „zmianę”.

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."
dannyman
źródło