Model hanya baca dalam antarmuka admin Django?

86

Bagaimana cara membuat model sepenuhnya hanya-baca di antarmuka admin? Ini untuk semacam tabel log, di mana saya menggunakan fitur admin untuk mencari, mengurutkan, memfilter, dll., Tetapi tidak perlu mengubah log.

Jika ini terlihat seperti duplikat, ini bukan yang saya coba lakukan:

  • Saya tidak mencari bidang hanya baca (bahkan membuat setiap bidang hanya dapat dibaca tetap memungkinkan Anda membuat catatan baru)
  • Saya tidak ingin membuat pengguna hanya- baca : setiap pengguna harus hanya-baca.
Steve Bennett
sumber
2
fitur ini akan segera hadir: github.com/django/django/pull/5297
Bosco
2
has_view_permissionakhirnya diimplementasikan di Django 2.1. Lihat juga stackoverflow.com/a/51641149 di bawah.
djvg

Jawaban:

21

Lihat 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 (untuk 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 %}
Pascal Polleunus
sumber
Kelihatannya mantap. Sudah lama sekali sejak saya menggunakan Django, mungkin menunggu untuk melihat apa yang akan dikatakan oleh pemberi komentar lainnya.
Steve Bennett
Apakah ini campuran untuk Model, atau untuk ModelAdmin?
OrangeDog
Ini untuk ModelAdmin.
Pascal Polleunus
Untuk Django 1.8 dan yang lebih baru, get_all_field_names tidak digunakan lagi. Cara yang kompatibel mundur untuk mendapatkannya . Cara singkat untuk mendapatkannya .
fzzylogic
Anda dapat menggunakan has_add_permission
rluts
70

Admin untuk mengedit, tidak hanya melihat (Anda tidak akan menemukan izin "melihat"). Untuk mencapai apa yang Anda inginkan, Anda harus melarang penambahan, penghapusan, dan membuat semua bidang hanya-baca:

class MyAdmin(ModelAdmin):

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

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

(jika Anda melarang perubahan, Anda bahkan tidak akan bisa melihat objeknya)

Untuk beberapa kode yang belum teruji yang mencoba mengotomatiskan pengaturan semua bidang hanya-baca, lihat jawaban saya untuk Model utuh sebagai hanya-baca

EDIT: juga belum teruji tetapi baru saja melihat LogEntryAdmin saya dan itu

readonly_fields = MyModel._meta.get_all_field_names()

Tidak tahu apakah itu akan berhasil dalam semua kasus.

EDIT: QuerySet.delete () mungkin masih menghapus objek secara massal. Untuk menyiasati ini, sediakan pengelola "objek" Anda sendiri dan subkelas QuerySet terkait yang tidak menghapus - lihat Mengganti QuerySet.delete () di Django

Danny W. Adair
sumber
2
PS: dan ya, seperti pada jawaban lain, cara yang harus dilakukan adalah dengan mendefinisikan tiga hal tersebut dalam kelas ReadOnlyAdmin, dan kemudian subkelas dari mana pun Anda membutuhkan perilaku itu. Bahkan bisa mendapatkan mewah dan memungkinkan definisi kelompok / izin yang yang diperbolehkan untuk mengedit, dan kemudian kembali Benar Sejalan (dan penggunaan get_readonly_fields () yang memiliki akses ke permintaan dan karena itu pengguna saat).
Danny W. Adair
hampir sempurna. dapatkah saya dengan rakus bertanya apakah ada cara agar baris tidak memiliki tautan ke halaman edit? (sekali lagi, tidak perlu memperbesar baris mana pun, dan tidak perlu mengedit apa pun)
Steve Bennett
1
Jika Anda menyetel list_display_links ModelAdmin Anda ke sesuatu yang dievaluasi sebagai False (seperti daftar kosong / tuple), ModelAdmin .__ init __ () menyetel list_display_links ke semua kolom (kecuali kotak centang tindakan) - lihat options.py. Saya rasa itu dilakukan untuk memastikan ada tautan. Jadi saya akan mengganti __init __ () di ReadOnlyAdmin, memanggil orang tua kemudian mengatur list_display_links ke daftar kosong atau tuple. Mengingat bahwa Anda sekarang tidak akan memiliki tautan ke formulir perubahan hanya-baca, mungkin yang terbaik adalah membuat atribut parameter / kelas untuk ini - Saya tidak akan berpikir itu menjadi perilaku yang secara umum diinginkan. H
Danny W. Adair
Mengenai readonly_fields yang disetel dari model, ini mungkin tidak akan berfungsi jika Anda mengganti formulir dan menambahkan bidang lain ... mendasarkannya pada bidang formulir sebenarnya mungkin lebih baik.
Danny W. Adair
Ini tidak berhasil: def __init __ (self, * args): super (RegistrationStatusAdmin, self) .__ init __ (* args) self.display_links = []
Steve Bennett
50

Berikut adalah dua kelas yang saya gunakan untuk membuat model dan / atau sebaris hanya untuk dibaca.

Untuk admin model:

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

Untuk sebaris:

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
sumber
Bagaimana Anda menerapkan kedua kelas ke satu sub kelas. Misalnya apakah saya memiliki bidang normal dan sebaris di kelas? Bisakah saya memperpanjang keduanya?
Timo
@timo menggunakan kelas ini sebagai mixin
MartinM
1
has_add_permissionin ReadOnlyAdminhanya menerima permintaan sebagai parameter
MartinM
has_change_permission () juga perlu diganti. def has_change_permission (self, request, obj = None):
david euler
13

Jika Anda ingin pengguna menyadari bahwa dia tidak dapat mengeditnya, 2 bagian hilang pada solusi pertama. Anda telah menghapus tindakan hapus!

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

Kedua: solusi hanya baca berfungsi dengan baik pada model biasa. Tetapi TIDAK berfungsi jika Anda memiliki model yang diwariskan dengan kunci asing. Sayangnya, saya belum tahu solusinya. Upaya yang baik adalah:

Seluruh model sebagai read-only

Tapi itu juga tidak berhasil untuk saya.

Dan catatan terakhir, jika Anda ingin memikirkan solusi yang luas, Anda harus memastikan bahwa setiap inline juga harus hanya dapat dibaca.

Josir
sumber
11

Sebenarnya Anda bisa mencoba solusi sederhana ini:

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

    def has_add_permission(self, request):
        return False
  • actions = None: hindari menampilkan tarik-turun dengan opsi "Hapus yang dipilih ..."
  • list_display_links = None: menghindari mengklik kolom untuk mengedit objek itu
  • has_add_permission() return False menghindari pembuatan objek baru untuk model itu
Iván Zugnoni
sumber
1
Ini melarang membuka contoh apa pun untuk melihat bidang, namun jika seseorang baik-baik saja dengan hanya daftar, maka itu berhasil.
Sebastián Vansteenkiste
8

Ini ditambahkan ke Django 2.1 yang dirilis pada 8/1/18!

ModelAdmin.has_view_permission()seperti izin has_delete_permission, has_change_permission, dan has_add_permission yang ada. Anda dapat membacanya di dokumen di sini

Dari catatan rilis:

Ini memungkinkan memberi pengguna akses hanya baca ke model di admin. ModelAdmin.has_view_permission () baru. Implementasinya kompatibel ke belakang karena tidak perlu menetapkan izin "tampilan" untuk mengizinkan pengguna yang memiliki izin "ubah" untuk mengedit objek.

grrrrrr
sumber
Pengguna super masih dapat memodifikasi objek di antarmuka admin, bukan?
Flimm
Itu benar, kecuali Anda mengganti salah satu metode ini untuk mengubah perilaku untuk melarang akses pengguna super.
grrrrrr
6

Jika jawaban yang diterima tidak berhasil untuk Anda, coba ini:

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
Wouter
sumber
5

Mengompilasi jawaban luar biasa @darklow dan @josir, ditambah menambahkan sedikit lagi untuk menghapus "Simpan" dan tombol "Simpan dan Lanjutkan" mengarah ke (dengan sintaks 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

dan kemudian Anda menggunakan suka

class MyModelAdmin(ReadOnlyAdmin):
    pass

Saya hanya mencoba ini dengan Django 1.11 / Python 3.

Mark Chackerian
sumber
Sudah sangat lama sejak saya menggunakan Django. Adakah yang bisa menjamin ini?
Steve Bennett
@SteveBennett ㄹ ada banyak variasi pada persyaratan yang dialamatkan ini ... jawaban ini tidak kedap air ... sarankan penjelasannya di sini: stackoverflow.com/a/36019597/2586761 dan jawaban yang Anda komentari di stackoverflow.com / a / 33543817/2586761 lebih lengkap daripada jawaban yang diterima
ptim
3

Jawaban yang diterima seharusnya berfungsi, tetapi ini juga akan mempertahankan urutan tampilan bidang hanya baca. Anda juga tidak perlu melakukan hardcode model dengan solusi ini.

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
lastoneisbearfood
sumber
3

Dengan Django 2.2 saya melakukannya seperti ini:

@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
Eerik Sven Puudist
sumber
dengan django 2.2, baris readonly_fieldsdan actionstidak diperlukan
cheng10
3

dengan django 2.2, admin readonly bisa sesederhana:

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
sumber
1

Saya menemukan persyaratan yang sama ketika perlu membuat semua bidang hanya-baca untuk pengguna tertentu di django admin akhirnya memanfaatkan modul django "django-admin-view-izin" tanpa memutar kode saya sendiri. Jika Anda membutuhkan kontrol yang lebih halus untuk secara eksplisit menentukan bidang mana, Anda perlu memperluas modul. Anda dapat melihat plugin beraksi di sini

Timothy Mugayi
sumber
0

read-only => melihat izin

  1. pipenv install django-admin-view-permission
  2. tambahkan 'admin_view_permission' ke INSTALLED_APPS di settings.py. seperti ini: `INSTALLED_APPS = ['admin_view_permission',
  3. python manage.py migrate
  4. python manage.py runserver 6666

ok. bersenang-senanglah dengan izin 'tampilan'

Xianhong Xu
sumber
0

Saya telah menulis kelas generik untuk menangani tampilan ReadOnly tergantung pada izin Pengguna, termasuk inlines;)

Di 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

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

Kemudian, kita bisa mewarisi secara normal kelas kita di admin.py:

class ContactAdmin(ReadOnlyAdmin):
    list_display = ("name","email","whatever")
    readonly_fields = ("updated","created")
    inlines = ( PhoneInline, ... )
Enric Mieza
sumber