Модели только для чтения в интерфейсе администратора Django?

86

Как сделать модель полностью доступной только для чтения в интерфейсе администратора? Это своего рода таблица журнала, где я использую функции администратора для поиска, сортировки, фильтрации и т. Д., Но нет необходимости изменять журнал.

В случае , если это выглядит как дубликат, вот не то , что я пытаюсь сделать:

  • Я не ищу поля только для чтения (даже если сделать каждое поле только для чтения, вы все равно сможете создавать новые записи)
  • Я не собираюсь создавать пользователя только для чтения : каждый пользователь должен быть доступен только для чтения.
Стив Беннетт
источник
2
эта функция должна появиться в ближайшее время: github.com/django/django/pull/5297
Bosco
2
has_view_permissionбыл наконец реализован в Django 2.1. Также см. Stackoverflow.com/a/51641149 ниже.
djvg

Ответы:

21

См. 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

шаблоны / 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 %}

templates / admin / view.html (для 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 %}
Паскаль Поллеун
источник
Выглядит вполне законно. Хотя я так давно не использовал Django, могу подождать, чтобы узнать, что скажут другие комментаторы.
Стив Беннетт
Это миксин для Modelили для ModelAdmin?
OrangeDog
Это для ModelAdmin.
Паскаль Поллеун
Для Django 1.8 и новее get_all_field_names устарела. Обратно совместимый способ их получения . Короткий способ их получить .
fzzylogic 09
Вы можете использовать has_add_permission
rluts
70

Администратор предназначен для редактирования, а не только для просмотра (вы не найдете разрешения «просмотр»). Чтобы добиться желаемого, вам нужно запретить добавление, удаление и сделать все поля доступными только для чтения:

class MyAdmin(ModelAdmin):

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

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

(если вы запретите менять, вы даже не увидите объекты)

Для некоторого непроверенного кода, который пытается автоматизировать настройку всех полей только для чтения, см. Мой ответ Целая модель как доступная только для чтения

РЕДАКТИРОВАТЬ: тоже непроверено, но только что взглянул на мой LogEntryAdmin, и у него есть

readonly_fields = MyModel._meta.get_all_field_names()

Не знаю, сработает ли это во всех случаях.

РЕДАКТИРОВАТЬ: QuerySet.delete () все еще может массово удалять объекты. Чтобы обойти это, предоставьте свой собственный менеджер «объектов» и соответствующий подкласс QuerySet, который не удаляется - см. Переопределение QuerySet.delete () в Django.

Дэнни В. Адэр
источник
2
PS: и да, как и в другом ответе, путь, вероятно, состоит в том, чтобы определить эти три вещи в классе ReadOnlyAdmin, а затем создать подкласс из этого, где бы вам ни понадобилось такое поведение. Может даже получить фантазии и дать определение групп / разрешений , которые будут разрешены для редактирования, а затем возвращающие соответственно (и использование get_readonly_fields () , который имеет доступ к запросу и , следовательно , текущий пользователь).
Дэнни В. Адэр
практически идеально. Могу я с жадностью спросить, есть ли способ, чтобы строки не ссылались на страницу редактирования? (опять же, нет необходимости увеличивать масштаб какой-либо строки и ничего редактировать)
Стив Беннетт,
1
Если вы установили в свой ModelAdmin list_display_links что-то, что оценивается как False (например, пустой список / кортеж), ModelAdmin .__ init __ () устанавливает list_display_links для всех столбцов (кроме флажка действия) - см. Options.py. Думаю, это сделано для того, чтобы ссылки были. Поэтому я бы переопределил __init __ () в ReadOnlyAdmin, вызвал родительский, а затем установил list_display_links в пустой список или кортеж. Учитывая, что теперь у вас не будет ссылок на формы изменения только для чтения, возможно, лучше всего создать для этого атрибут параметра / класса - я бы не подумал, что это обычно желаемое поведение. Hth
Дэнни У. Адэр
Что касается readonly_fields, устанавливаемых из модели, это, вероятно, не сработает, если вы переопределите форму и добавите другие поля ... возможно, лучше использовать фактические поля формы.
Дэнни У. Адэр
Это не сработало: def __init __ (self, * args): super (RegistrationStatusAdmin, self) .__ init __ (* args) self.display_links = []
Стив Беннетт
50

Вот два класса, которые я использую, чтобы сделать модель и / или встроенную модель только для чтения.

Для администратора модели:

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

Для встроенных:

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
Darklow
источник
Как вы применяете оба класса к одному подклассу. Например, если у меня в классе есть нормальные поля и строчки? Могу ли я продлить оба?
Timo
@timo использует эти классы как
миксины
1
has_add_permissionin ReadOnlyAdminпринимает в качестве параметра только запрос
MartinM 03
has_change_permission () также необходимо переопределить. def has_change_permission (self, request, obj = None):
Дэвид Эйлер
13

Если вы хотите, чтобы пользователь осознал, что он / она не может его редактировать, в первом решении отсутствуют 2 части. Вы удалили действие удаления!

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

Во-вторых: решение только для чтения отлично работает на простых моделях. Но это НЕ работает, если у вас есть унаследованная модель с внешними ключами. К сожалению, я пока не знаю решения этой проблемы. Хорошая попытка:

Вся модель доступна только для чтения

Но и у меня тоже не работает.

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

Джосир
источник
11

На самом деле вы можете попробовать это простое решение:

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

    def has_add_permission(self, request):
        return False
  • actions = None: избегает отображения раскрывающегося списка с опцией «Удалить выбранное ...»
  • list_display_links = None: избегает нажатия в столбцах для редактирования этого объекта
  • has_add_permission() возврат False позволяет избежать создания новых объектов для этой модели
Иван Зунони
источник
1
Это запрещает открывать любой экземпляр для просмотра полей, однако, если вас устраивает просто листинг, то он работает.
Себастьян Ванстенкисте,
8

Это было добавлено в Django 2.1, выпущенный 01.08.18!

ModelAdmin.has_view_permission()аналогичен существующим has_delete_permission, has_change_permission и has_add_permission. Вы можете прочитать об этом в документации здесь

Из примечаний к выпуску:

Это позволяет предоставить пользователям доступ только для чтения к моделям в админке. ModelAdmin.has_view_permission () новый. Реализация обратно совместима в том смысле, что нет необходимости назначать разрешение «просмотр», чтобы позволить пользователям, имеющим разрешение на «изменение», редактировать объекты.

грррррр
источник
Однако суперпользователь по-прежнему сможет изменять объекты в интерфейсе администратора, верно?
Flimm
Это правильно, если вы не переопределите один из этих методов, чтобы изменить поведение и запретить доступ суперпользователям.
grrrrrr
6

Если принятый ответ вам не подходит, попробуйте следующее:

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

Компиляция отличных ответов @darklow и @josir плюс добавление еще немного для удаления кнопок «Сохранить» и «Сохранить и продолжить» приводит к (в синтаксисе 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

а затем вы используете как

class MyModelAdmin(ReadOnlyAdmin):
    pass

Я пробовал это только с Django 1.11 / Python 3.

Марк Чакериан
источник
Я очень давно не использовал Django. Кто-нибудь еще может за это поручиться?
Стив Беннетт
@SteveBennett ㄹ существует множество вариаций требований, которые это касается ... этот ответ не является водонепроницаемым ... предложите объяснение здесь: stackoverflow.com/a/36019597/2586761 и ответ, который вы прокомментировали на stackoverflow.com / a / 33543817/2586761 как более полный, чем принятый ответ
птим
3

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

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

В Django 2.2 я делаю это так:

@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
Ээрик Свен Пуудист
источник
с Джанго 2.2, readonly_fieldsи actionsлиния не нужно
cheng10
3

с django 2.2 администратор только для чтения может быть таким простым, как:

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')
cheng10
источник
1

Я столкнулся с тем же требованием, когда мне нужно было сделать все поля доступными только для чтения для определенных пользователей в django admin в конечном итоге использовать модуль django «django-admin-view-permission» без прокрутки моего собственного кода. Если вам нужен более мелкозернистый элемент управления, чтобы явно определить, какие поля, вам нужно будет расширить модуль. Вы можете проверить работу плагина здесь

Тимоти Мугайи
источник
0

только для чтения => разрешение на просмотр

  1. pipenv install django-admin-view-permission
  2. добавьте 'admin_view_permission' в INSTALLED_APPS в settings.py. например: `INSTALLED_APPS = ['admin_view_permission',
  3. python manage.py мигрировать
  4. сервер запуска python manage.py 6666

хорошо. развлекайся с разрешением на просмотр

Сяньхун Сюй
источник
0

Я написал общий класс для обработки представления ReadOnly в зависимости от разрешений пользователя, включая встроенные;)

В 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

В 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 )

Затем мы можем просто наследовать наши классы в admin.py:

class ContactAdmin(ReadOnlyAdmin):
    list_display = ("name","email","whatever")
    readonly_fields = ("updated","created")
    inlines = ( PhoneInline, ... )
Энрик Миеза
источник