Tuân theo Lịch sử mô hình của Quản trị viên Django


94

Thiết lập:

  • Tôi đang làm việc trên một ứng dụng Django cho phép người dùng tạo một đối tượng trong cơ sở dữ liệu và sau đó quay lại và chỉnh sửa nó nhiều như họ muốn.
  • Trang quản trị của Django lưu giữ lịch sử về những thay đổi được thực hiện đối với các đối tượng thông qua trang quản trị.

Câu hỏi:

  • Làm cách nào để kết nối ứng dụng của tôi với lịch sử thay đổi của trang web quản trị để tôi có thể xem lịch sử thay đổi mà người dùng thực hiện đối với "nội dung" của họ?

Câu trả lời:


136

Lịch sử quản trị chỉ là một ứng dụng giống như bất kỳ ứng dụng Django nào khác, ngoại trừ vị trí đặc biệt trên trang quản trị.

Mô hình này nằm trong django.contrib.admin.models.LogEntry.

Khi người dùng thực hiện thay đổi, hãy thêm vào nhật ký như thế này (bị đánh cắp một cách đáng xấu hổ từ 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
)

đâu objectlà đối tượng đã được thay đổi tất nhiên.

Bây giờ tôi thấy câu trả lời của Daniel và đồng ý với anh ấy, nó khá hạn chế.

Theo ý kiến ​​của tôi, một cách tiếp cận mạnh mẽ hơn là sử dụng mã từ Marty Alchin trong cuốn sách Pro Django của anh ấy (xem Lưu giữ hồ sơ lịch sử bắt đầu từ trang 263). Có một ứng dụng django-simple-history triển khai và mở rộng cách tiếp cận này ( tài liệu tại đây ).


7
Đừng quên: từ django.contrib.contenttypes.models nhập ContentType. Ngoài ra, force_unicode cũng là chức năng riêng của chúng.
sakabako

10
from django.utils.encoding import force_unicodecho 'force_unicode'
mmrs151

17
Kể từ khi câu hỏi này được trả lời, cách tiếp cận của Marty Alchin đã được mở và mở rộng trong một ứng dụng có tên django-simple-history .
Trey Hunner

5
Ngôi nhà mới của django-đơn giản-lịch sử có vẻ là: github.com/treyhunner/django-simple-history Thông tin thêm về RTD django-simple-history.readthedocs.org/en/latest
Brutus

3
Một phương pháp tốt cũng có thể là kiểm tra lưới so sánh tại djangopackages.com , nơi django-simple-history và các giải pháp khác (như CleanerVersion hoặc django-reverseion) được so sánh.
maennel

22

Nhật ký lịch sử thay đổi của quản trị viên được xác định trong django.contrib.admin.modelsvà có một history_viewphương thức trong ModelAdminlớp tiêu chuẩn .

Mặc dù vậy, chúng không đặc biệt thông minh và được kết hợp khá chặt chẽ với quản trị viên, vì vậy tốt nhất bạn có thể chỉ sử dụng chúng cho các ý tưởng và tạo phiên bản riêng cho ứng dụng của mình.


4
Điều này có còn đúng không?
bấm vào đây

12

Tôi biết câu hỏi này đã cũ, nhưng tính đến ngày nay (Django 1.9), các mục lịch sử của Django mạnh mẽ hơn so với thời điểm của câu hỏi này. Trong dự án hiện tại, tôi cần lấy các mục lịch sử gần đây và đưa chúng vào danh sách thả xuống từ thanh điều hướng. Đây là cách tôi đã làm và rất thẳng thắn:

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

Như đã thấy trong đoạn mã trên, tôi đang tạo một bộ truy vấn cơ bản từ mô hình LogEntry (django.contrib.admin.models.py là nơi đặt nó trong django 1.9) và loại trừ các mục không có thay đổi nào, sắp xếp nó bằng thời gian hành động và chỉ hiển thị 20 nhật ký đã qua. Tôi cũng nhận được một mặt hàng khác chỉ với số lượng. Nếu bạn nhìn vào mô hình LogEntry, bạn có thể thấy tên trường mà Django đã sử dụng để lấy lại các phần dữ liệu mà bạn cần. Đối với trường hợp cụ thể của tôi, đây là những gì tôi đã sử dụng trong mẫu của mình:

Liên kết đến Hình ảnh Sản phẩm Cuối cùng

*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

Để thêm vào những gì đã được nói, đây là một số tài nguyên khác dành cho bạn:

(1) Tôi đang làm việc với một ứng dụng có tên django-reverseion , ứng dụng này 'kết nối' với lịch sử quản trị viên và thực sự thêm vào nó. Nếu bạn muốn một số mã mẫu sẽ là một nơi tốt để xem.

(2) Nếu bạn quyết định sử dụng chức năng lịch sử của riêng mình, django cung cấp các tín hiệu mà bạn có thể đăng ký để có ứng dụng xử lý, ví dụ: post_save cho từng đối tượng lịch sử. Mã của bạn sẽ chạy mỗi khi mục nhập nhật ký lịch sử được lưu. Doc: Django signal


3
Tôi sẽ mạnh mẽ khuyên bạn nên chống django-reversion. Về khái niệm, đó là một ý tưởng tuyệt vời, nhưng việc thực hiện thì thật tồi tệ. Tôi đã sử dụng cái này trong một xưởng sản xuất và đó là một cơn ác mộng. Lúc đầu, nó hoạt động rất tốt, nhưng cuối cùng tôi phát hiện ra rằng ứng dụng hoàn toàn không có khả năng mở rộng, vì vậy đối với bất kỳ mô hình nào có các thay đổi thường xuyên, quản trị viên của bạn sẽ không sử dụng được trong vài tháng vì các truy vấn mà nó sử dụng kém hiệu quả một cách khủng khiếp.
Cerin

@Cerin và những người khác: điều này vẫn đúng chứ? Tôi đang cố gắng xem liệu tôi có thể sử dụng django-reverseion cho một trang web có nhiều nội dung hay không. django-reverseion dường như được xếp hạng cao nhất trên các bài đăng trên djangopackages.org và SO, nhưng khả năng mở rộng quy mô là một ưu tiên quan trọng đối với ứng dụng của tôi, do đó yêu cầu
Anupam

1
@Anupam, tôi đã không sử dụng nó vì tôi phải tắt nó khỏi trang web sản xuất của mình. Tôi đã báo cáo vấn đề là một lỗi, nhưng nhà phát triển đã thổi bay tôi và nói rằng đó không phải là vấn đề, vì vậy tôi chưa đánh giá lại dự án.
Cerin

Tôi hiểu rồi - bạn có phiền chia sẻ liên kết vấn đề không? Sẽ rất hữu ích cho tôi vì tôi đang cân nhắc nghiêm túc xem có nên sử dụng nó hay không cho ứng dụng Django của mình
Anupam

3

Mã mẫu

Xin chào,

Gần đây tôi đã tấn công một số đăng nhập vào chế độ xem "cập nhật" cho cơ sở dữ liệu kiểm kê máy chủ của chúng tôi. Tôi đã nghĩ rằng tôi sẽ chia sẻ mã "ví dụ" của mình. Hàm theo sau nhận một trong các đối tượng "Máy chủ" của chúng tôi, danh sách những thứ đã được thay đổi và một action_flag của ADDITION hoặc CHANGE. Nó đơn giản hóa mọi thứ một chút trong đó ADDITION có nghĩa là "đã thêm một máy chủ mới." Một cách tiếp cận linh hoạt hơn sẽ cho phép thêm một thuộc tính vào máy chủ. Tất nhiên, việc kiểm tra các chức năng hiện có của chúng tôi đủ thách thức để xác định xem thay đổi có thực sự diễn ra hay không, vì vậy tôi đủ vui khi ghi lại các thuộc tính mới dưới dạng "thay đổi".

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."
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.