Mô hình chỉ đọc trong giao diện quản trị Django?


86

Làm cách nào để tạo một mô hình hoàn toàn chỉ đọc trong giao diện quản trị? Nó dành cho một loại bảng nhật ký, nơi tôi đang sử dụng các tính năng quản trị để tìm kiếm, sắp xếp, lọc, v.v. nhưng không cần phải sửa đổi nhật ký.

Trong trường hợp điều này trông giống như một bản sao, đây không phải là những gì tôi đang cố gắng làm:

  • Tôi không tìm kiếm các trường chỉ đọc (ngay cả việc tạo mọi trường chỉ đọc vẫn cho phép bạn tạo các bản ghi mới)
  • Tôi không muốn tạo ra một người dùng chỉ đọc : mọi người dùng nên chỉ đọc.

2
tính năng này sẽ sớm ra mắt: github.com/django/django/pull/5297
Bosco

2
has_view_permissioncuối cùng đã được triển khai trong Django 2.1. Cũng xem stackoverflow.com/a/51641149 bên dưới.
djvg

Câu trả lời:


21

Xem https://djangosnippets.org/snippets/10539/

class ReadOnlyAdminMixin(object):
    """Disables all editing capabilities."""
    change_form_template = "admin/view.html"

    def __init__(self, *args, **kwargs):
        super(ReadOnlyAdminMixin, self).__init__(*args, **kwargs)
        self.readonly_fields = self.model._meta.get_all_field_names()

    def get_actions(self, request):
        actions = super(ReadOnlyAdminMixin, self).get_actions(request)
        del_action = "delete_selected"
        if del_action in actions:
            del actions[del_action]
        return actions

    def has_add_permission(self, request):
        return False

    def has_delete_permission(self, request, obj=None):
        return False

    def save_model(self, request, obj, form, change):
        pass

    def delete_model(self, request, obj):
        pass

    def save_related(self, request, form, formsets, change):
        pass

Template / admin / view.html

{% extends "admin/change_form.html" %}
{% load i18n %}

{% block submit_buttons_bottom %}
  <div class="submit-row">
    <a href="../">{% blocktrans %}Back to list{% endblocktrans %}</a>
  </div>
{% endblock %}

Template / admin / view.html (dành cho Grappelli)

{% extends "admin/change_form.html" %}
{% load i18n %}

{% block submit_buttons_bottom %}
  <footer class="grp-module grp-submit-row grp-fixed-footer">
    <header style="display:none"><h1>{% trans "submit options"|capfirst context "heading" %}</h1></header>
    <ul>
       <li><a href="../" class="grp-button grp-default">{% blocktrans %}Back to list{% endblocktrans %}</a></li>
    </ul>
  </footer>
{% endblock %}

Có vẻ hợp pháp. Tuy nhiên, đã quá lâu kể từ khi tôi sử dụng Django, có thể chờ xem những người bình luận khác nói gì.
Steve Bennett

Đây là một mixin cho Model, hay cho ModelAdmin?
OrangeDog

Nó dành cho ModelAdmin.
Pascal Polleunus

Đối với Django 1.8 trở lên, get_all_field_names không được dùng nữa. Cách tương thích ngược để lấy chúng . Cách ngắn để có được chúng .
fzzylogic

Bạn có thể sử dụng has_add_permission
rluts

70

Quản trị viên là để chỉnh sửa, không chỉ xem (bạn sẽ không tìm thấy quyền "xem"). Để đạt được những gì bạn muốn, bạn sẽ phải cấm thêm, xóa và đặt tất cả các trường ở chế độ chỉ đọc:

class MyAdmin(ModelAdmin):

    def has_add_permission(self, request, obj=None):
        return False

    def has_delete_permission(self, request, obj=None):
        return False

(nếu bạn cấm thay đổi, bạn thậm chí sẽ không thể nhìn thấy các đối tượng)

Đối với một số mã chưa được kiểm tra cố gắng tự động hóa cài đặt tất cả các trường ở chế độ chỉ đọc, hãy xem câu trả lời của tôi cho Toàn bộ mô hình là chỉ đọc

CHỈNH SỬA: cũng chưa được kiểm tra nhưng chỉ cần xem qua LogEntryAdmin của tôi và nó đã

readonly_fields = MyModel._meta.get_all_field_names()

Không biết nếu điều đó sẽ hoạt động trong mọi trường hợp.

CHỈNH SỬA: QuerySet.delete () vẫn có thể xóa hàng loạt đối tượng. Để giải quyết vấn đề này, hãy cung cấp trình quản lý "đối tượng" của riêng bạn và lớp con QuerySet tương ứng không xóa - xem Ghi đè QuerySet.delete () trong Django


2
Tái bút: và vâng, như trong câu trả lời khác, cách để đi có lẽ là xác định ba điều đó trong một lớp ReadOnlyAdmin, và sau đó là lớp con từ bất cứ nơi nào bạn cần hành vi đó. Thậm chí có thể trở nên lạ mắt và cho phép định nghĩa các nhóm / quyền được phép chỉnh sửa, sau đó trả về True tương ứng (và sử dụng get_readonly_fields () có quyền truy cập vào yêu cầu và do đó là người dùng hiện tại).
Danny W. Adair

gân như hoan hảo. Tôi có thể hỏi một cách thèm thuồng nếu có cách nào để không có các hàng liên kết đến một trang chỉnh sửa? (một lần nữa, không có nhu cầu để phóng to trên bất kỳ liên tiếp, và không cần phải chỉnh sửa bất cứ điều gì)
Steve Bennett

1
Nếu bạn đặt list_display_links của ModelAdmin thành thứ gì đó được đánh giá là Sai (như danh sách / bộ giá trị trống), ModelAdmin .__ init __ () sẽ đặt list_display_links thành tất cả các cột (ngoại trừ hộp kiểm hành động) - xem option.py. Tôi đoán điều đó được thực hiện để đảm bảo có các liên kết. Vì vậy, tôi sẽ ghi đè __init __ () trong ReadOnlyAdmin, gọi cái mẹ sau đó đặt list_display_links thành một danh sách hoặc bộ giá trị trống. Vì bây giờ bạn sẽ không có liên kết đến các biểu mẫu thay đổi chỉ đọc, có lẽ tốt nhất là tạo một thuộc tính tham số / lớp cho điều này - tôi sẽ không nghĩ rằng đó là hành vi mong muốn chung. Hth
Danny W. Adair

Về việc readonly_fields được đặt từ mô hình, điều này có thể sẽ không hoạt động nếu bạn ghi đè biểu mẫu và thêm các trường khác ... dựa trên các trường biểu mẫu thực tế có lẽ tốt hơn.
Danny W. Adair

Cách này không thành công: def __init __ (self, * args): super (registerStatusAdmin, self) .__ init __ (* args) self.display_links = []
Steve Bennett

50

Đây là hai lớp tôi đang sử dụng để tạo một mô hình và / hoặc nó chỉ được đọc trong dòng.

Đối với quản trị viên mô hình:

from django.contrib import admin

class ReadOnlyAdmin(admin.ModelAdmin):
    readonly_fields = []

    def get_readonly_fields(self, request, obj=None):
        return list(self.readonly_fields) + \
               [field.name for field in obj._meta.fields] + \
               [field.name for field in obj._meta.many_to_many]


    def has_add_permission(self, request):
        return False

    def has_delete_permission(self, request, obj=None):
        return False

class MyModelAdmin(ReadOnlyAdmin):
    pass

Đối với nội tuyến:

class ReadOnlyTabularInline(admin.TabularInline):
    extra = 0
    can_delete = False
    editable_fields = []
    readonly_fields = []
    exclude = []

    def get_readonly_fields(self, request, obj=None):
        return list(self.readonly_fields) + \
               [field.name for field in self.model._meta.fields
                if field.name not in self.editable_fields and
                   field.name not in self.exclude]

    def has_add_permission(self, request):
        return False


class MyInline(ReadOnlyTabularInline):
    pass

Cách bạn áp dụng cả hai lớp cho một lớp con. Ví dụ: nếu tôi có các trường và nội tuyến bình thường trong một lớp? Tôi có thể gia hạn cả hai không?
Timo

@timo sử dụng các lớp này làm mixin
MartinM

1
has_add_permissiontrong ReadOnlyAdminchỉ lấy yêu cầu làm tham số
MartinM

has_change_permission () cũng cần được ghi đè. def has_change_permission (self, request, obj = None):
david euler

13

Nếu bạn muốn người dùng biết rằng họ không thể chỉnh sửa nó, thì giải pháp đầu tiên bị thiếu 2 mảnh. Bạn đã xóa hành động xóa!

class MyAdmin(ModelAdmin)
    def has_add_permission(self, request, obj=None):
        return False
    def has_delete_permission(self, request, obj=None):
        return False

    def get_actions(self, request):
        actions = super(MyAdmin, self).get_actions(request)
        if 'delete_selected' in actions:
            del actions['delete_selected']
        return actions

Thứ hai: giải pháp chỉ đọc hoạt động tốt trên các mô hình đơn giản. Nhưng nó KHÔNG hoạt động nếu bạn có một mô hình kế thừa với các khóa ngoại. Thật không may, tôi chưa biết giải pháp cho điều đó. Một nỗ lực tốt là:

Toàn bộ mô hình dưới dạng chỉ đọc

Nhưng nó cũng không hiệu quả với tôi.

Và lưu ý cuối cùng, nếu bạn muốn suy nghĩ về một giải pháp rộng rãi, bạn phải bắt buộc rằng mỗi nội tuyến cũng phải được đọc.


11

Trên thực tế, bạn có thể thử giải pháp đơn giản này:

class ReadOnlyModelAdmin(admin.ModelAdmin):
    actions = None
    list_display_links = None
    # more stuff here

    def has_add_permission(self, request):
        return False
  • actions = None: tránh hiển thị menu thả xuống với tùy chọn "Xóa đã chọn ..."
  • list_display_links = None: tránh nhấp vào cột để chỉnh sửa đối tượng đó
  • has_add_permission() trả về False tránh tạo đối tượng mới cho mô hình đó

1
Điều này cấm mở bất kỳ trường hợp nào để xem các trường, tuy nhiên nếu một người ổn với việc chỉ liệt kê, thì nó hoạt động.
Sebastián Vansteenkiste

8

Điều này đã được thêm vào Django 2.1 được phát hành vào ngày 18/8!

ModelAdmin.has_view_permission()giống như has_delete_permission hiện có, has_change_permission và has_add_permission. Bạn có thể đọc về nó trong tài liệu ở đây

Từ ghi chú phát hành:

Điều này cho phép cấp cho người dùng quyền truy cập chỉ đọc vào các mô hình trong quản trị viên. ModelAdmin.has_view_permission () là mới. Việc triển khai tương thích ngược ở chỗ không cần chỉ định quyền “xem” để cho phép người dùng có quyền “thay đổi” chỉnh sửa các đối tượng.


Tuy nhiên, superuser sẽ vẫn có thể sửa đổi các đối tượng trong giao diện quản trị, phải không?
Flimm

Điều đó đúng, trừ khi bạn ghi đè một trong những phương pháp này để thay đổi hành vi không cho phép truy cập của người dùng cấp trên.
grrrrrr

6

Nếu câu trả lời được chấp nhận không phù hợp với bạn, hãy thử cách này:

def get_readonly_fields(self, request, obj=None):
    readonly_fields = []
    for field in self.model._meta.fields:
        readonly_fields.append(field.name)

    return readonly_fields

5

Việc biên dịch các câu trả lời xuất sắc của @darklow và @josir, cộng với việc thêm một chút nữa để xóa các nút "Lưu" và "Lưu và Tiếp tục" dẫn đến (theo cú pháp Python 3):

class ReadOnlyAdmin(admin.ModelAdmin):
    """Provides a read-only view of a model in Django admin."""
    readonly_fields = []

    def change_view(self, request, object_id, extra_context=None):
        """ customize add/edit form to remove save / save and continue """
        extra_context = extra_context or {}
        extra_context['show_save_and_continue'] = False
        extra_context['show_save'] = False
        return super().change_view(request, object_id, extra_context=extra_context)

    def get_actions(self, request):
        actions = super().get_actions(request)
        if 'delete_selected' in actions:
            del actions['delete_selected']
        return actions

    def get_readonly_fields(self, request, obj=None):
        return list(self.readonly_fields) + \
           [field.name for field in obj._meta.fields] + \
           [field.name for field in obj._meta.many_to_many]

    def has_add_permission(self, request):
        return False

    def has_delete_permission(self, request, obj=None):
        return False

và sau đó bạn sử dụng like

class MyModelAdmin(ReadOnlyAdmin):
    pass

Tôi chỉ thử điều này với Django 1.11 / Python 3.


Đã rất lâu rồi tôi không sử dụng Django. Bất cứ ai khác có thể xác minh cho điều này?
Steve Bennett

@SteveBennett ㄹ có rất nhiều biến thể về các yêu cầu mà câu trả lời này giải quyết ... câu trả lời này không chặt chẽ ... gợi ý giải thích tại đây: stackoverflow.com/a/36019597/2586761 và câu trả lời bạn đã nhận xét trên stackoverflow.com / a / 33543817/2586761 là đầy đủ hơn câu trả lời được chấp nhận
ptim

3

Câu trả lời được chấp nhận sẽ hoạt động, nhưng điều này cũng sẽ bảo toàn thứ tự hiển thị của các trường chỉ đọc. Bạn cũng không cần phải mã hóa mô hình bằng giải pháp này.

class ReadonlyAdmin(admin.ModelAdmin):
   def __init__(self, model, admin_site):
      super(ReadonlyAdmin, self).__init__(model, admin_site)
      self.readonly_fields = [field.name for field in filter(lambda f: not f.auto_created, model._meta.fields)]

   def has_delete_permission(self, request, obj=None):
       return False
   def has_add_permission(self, request, obj=None):
       return False

3

Với Django 2.2, tôi làm như thế này:

@admin.register(MyModel)
class MyAdmin(admin.ModelAdmin):
    readonly_fields = ('all', 'the', 'necessary', 'fields')
    actions = None # Removes the default delete action in list view

    def has_add_permission(self, request):
        return False

    def has_change_permission(self, request, obj=None):
        return False

    def has_delete_permission(self, request, obj=None):
        return False

với django 2.2, readonly_fieldsactionsdòng này là không cần thiết
cheng10

3

với django 2.2, quản trị viên chỉ đọc có thể đơn giản như:

class ReadOnlyAdminMixin():
    def has_add_permission(self, request):
        return False

    def has_change_permission(self, request, obj=None):
        return False

    def has_delete_permission(self, request, obj=None):
        return False


class LogEntryAdmin(ReadOnlyAdminMixin, admin.ModelAdmin):
    list_display = ('id', 'user', 'action_flag', 'content_type', 'object_repr')

1

Tôi gặp phải yêu cầu tương tự khi cần đặt tất cả các trường chỉ đọc cho một số người dùng nhất định trong quản trị viên django đã tận dụng mô-đun django "django-admin-view-allow" mà không cần cuộn mã của riêng tôi. Nếu bạn cần kiểm soát chi tiết hơn để xác định rõ ràng các trường nào thì bạn sẽ cần mở rộng mô-đun. Bạn có thể kiểm tra plugin đang hoạt động tại đây


0

quyền chỉ đọc => xem

  1. pipenv install django-admin-view-permission
  2. thêm 'admin_view_permission' vào INSTALLED_APPS trong settings.py.như thế này: ʻINSTALLED_APPS = ['admin_view_permission',
  3. python management.py di chuyển
  4. python management.py runningerver 6666

ok. chúc bạn vui vẻ với quyền 'lượt xem'


0

Tôi đã viết một lớp chung để xử lý chế độ xem ReadOnly tùy thuộc vào quyền của Người dùng, bao gồm cả nội tuyến;)

Trong models.py:

class User(AbstractUser):
    ...
    def is_readonly(self):
        if self.is_superuser:
            return False
        # make readonly all users not in "admins" group
        adminGroup = Group.objects.filter(name="admins")
        if adminGroup in self.groups.all():
            return False
        return True

Trong admin.py:

# read-only user filter class for ModelAdmin
class ReadOnlyAdmin(admin.ModelAdmin):
    def __init__(self, *args, **kwargs):
        # keep initial readonly_fields defined in subclass
        self._init_readonly_fields = self.readonly_fields
        # keep also inline readonly_fields
        for inline in self.inlines:
            inline._init_readonly_fields = inline.readonly_fields
        super().__init__(*args,**kwargs)
    # customize change_view to disable edition to readonly_users
    def change_view( self, request, object_id, form_url='', extra_context=None ):
        context = extra_context or {}
        # find whether it is readonly or not 
        if request.user.is_readonly():
            # put all fields in readonly_field list
            self.readonly_fields = [ field.name for field in self.model._meta.get_fields() if not field.auto_created ]
            # readonly mode fer all inlines
            for inline in self.inlines:
                inline.readonly_fields = [field.name for field in inline.model._meta.get_fields() if not field.auto_created]
            # remove edition buttons
            self.save_on_top = False
            context['show_save'] = False
            context['show_save_and_continue'] = False
        else:
            # if not readonly user, reset initial readonly_fields
            self.readonly_fields = self._init_readonly_fields
            # same for inlines
            for inline in self.inlines:
                inline.readonly_fields = self._init_readonly_fields
        return super().change_view(
                    request, object_id, form_url, context )
    def save_model(self, request, obj, form, change):
        # disable saving model for readonly users
        # just in case we have a malicious user...
        if request.user.is_readonly():
            # si és usuari readonly no guardem canvis
            return False
        # if not readonly user, save model
        return super().save_model( request, obj, form, change )

Sau đó, chúng ta có thể kế thừa bình thường các lớp của mình trong admin.py:

class ContactAdmin(ReadOnlyAdmin):
    list_display = ("name","email","whatever")
    readonly_fields = ("updated","created")
    inlines = ( PhoneInline, ... )
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.