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


86

Як я можу зробити модель повністю доступною лише для читання в інтерфейсі адміністратора? Це для свого роду таблиці журналу, де я використовую функції адміністратора для пошуку, сортування, фільтрування тощо, але немає потреби в зміні журналу.

У разі , якщо це виглядає як дублікат, ось НЕ то , що я намагаюся зробити:

  • Я не шукаю поля лише для читання (навіть створення кожного поля лише для читання все одно дозволить вам створювати нові записи)
  • Я не шукаю , щоб створити тільки для читання користувача : кожен користувач повинен бути доступний тільки для читання.

2
ця функція має з’явитися найближчим часом: github.com/django/django/pull/5297
Боско

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

templates / 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

Ви можете використовувати has_add_permission
засідання

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 () , який має доступ до запиту і , отже , поточний користувач).
Danny W. Adair

майже ідеально. чи не міг би я жадібно запитати, чи є спосіб, щоб рядки не мали посилання на сторінку редагування? (знову ж таки, немає необхідності збільшувати будь-який рядок і не потрібно нічого редагувати)
Стів Беннетт,

1
Якщо ви встановите для списку Model_admin list_display_links щось, що вважається False (наприклад, порожній список / кортеж), ModelAdmin .__ init __ () встановлює list_display_links для всіх стовпців (крім прапорця дії) - див. Options.py. Думаю, це зроблено для того, щоб існували посилання. Тому я б замінив __init __ () у ReadOnlyAdmin, викликав батьківський, а потім встановив list_display_links до порожнього списку або кортежу. Враховуючи, що тепер у вас не буде посилань на форми зміни лише для читання, можливо, найкраще створити для цього атрибут параметра / класу - я не вважаю, що це загально бажана поведінка. Hth
Danny W. Adair

Щодо readonly_fields, встановлених із моделі, це, мабуть, не спрацює, якщо ви перевизначите форму та додасте інші поля ... базуючись на фактичних полях форми, мабуть, краще.
Danny W. Adair

Це не спрацювало: 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

Як ви застосовуєте обидва класи до одного підкласу. Наприклад, якщо в класі є нормальні поля та рядки? Чи можу я продовжити обидва?
Тимо

@timo використовує ці класи як міксини
MartinM

1
has_add_permissionin ReadOnlyAdminприймає як параметр лише запит
MartinM

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 () нова. Реалізація є зворотно сумісною, оскільки немає необхідності призначати дозвіл на перегляд, щоб дозволити користувачам, які мають дозвіл на зміни, редагувати об’єкти.


Суперкористувач все одно зможе змінювати об'єкти в інтерфейсі адміністратора, правда?
Флімм

Це правильно, якщо ви не заміните один із цих методів, щоб змінити поведінку, щоб заборонити доступ суперкористувачів.
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 як повніший за прийняту відповідь
ptim

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

1

Я натрапив на ту саму вимогу, коли потрібно було зробити всі поля лише для читання для певних користувачів у адміністраторі django, і в кінцевому підсумку використав модуль django "django-admin-view-dozvoла", не прокачуючи власний код. Якщо вам потрібен більш тонкий контроль, щоб чітко визначити, які поля, тоді вам потрібно буде розширити модуль. Ви можете перевірити плагін у дії тут


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 runserver 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, ... )
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.