Readonly Modelle in der Django Admin-Oberfläche?

85

Wie kann ich ein Modell in der Administrationsoberfläche vollständig schreibgeschützt machen? Es handelt sich um eine Art Protokolltabelle, in der ich die Administratorfunktionen zum Suchen, Sortieren, Filtern usw. verwende, das Protokoll jedoch nicht geändert werden muss.

Für den Fall , der wie folgt aussieht ein Duplikat, hier ist nicht , was ich zu tun werde versuchen:

  • Ich suche nicht nach schreibgeschützten Feldern (selbst wenn Sie jedes Feld schreibgeschützt machen, können Sie immer noch neue Datensätze erstellen)
  • Ich möchte keinen schreibgeschützten Benutzer erstellen : Jeder Benutzer sollte schreibgeschützt sein.
Steve Bennett
quelle
2
Diese Funktion sollte bald
Bosco
2
has_view_permissionwurde schließlich in Django 2.1 implementiert. Siehe auch stackoverflow.com/a/51641149 unten.
DJVG

Antworten:

21

Siehe 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 (für 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
quelle
Klingt plausibel. Es ist so lange her, dass ich Django verwendet habe, und ich könnte abwarten, was andere Kommentatoren zu sagen haben.
Steve Bennett
Ist das ein Mixin für die Modeloder für die ModelAdmin?
OrangeDog
Es ist für die ModelAdmin.
Pascal Polleunus
Für Django 1.8 und höher ist get_all_field_names veraltet. Abwärtskompatibler Weg, um sie zu bekommen . Kurzer Weg, um sie zu bekommen .
Fzzylogic
Sie können has_add_permission
rluts
70

Der Administrator dient zum Bearbeiten und nicht nur zum Anzeigen (Sie finden keine Berechtigung zum Anzeigen). Um das zu erreichen, was Sie wollen, müssen Sie das Hinzufügen, Löschen und das Lesen aller Felder verbieten:

class MyAdmin(ModelAdmin):

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

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

(Wenn Sie das Ändern verbieten, werden Sie die Objekte nicht einmal sehen können)

Für einen nicht getesteten Code, der versucht, das Setzen aller schreibgeschützten Felder zu automatisieren, siehe meine Antwort auf gesamte Modell als schreibgeschützt angesehen

EDIT: auch ungetestet aber habe mir gerade meinen LogEntryAdmin angesehen und es hat

readonly_fields = MyModel._meta.get_all_field_names()

Ich weiß nicht, ob das in allen Fällen funktioniert.

BEARBEITEN: QuerySet.delete () kann weiterhin Objekte in großen Mengen löschen. Um dies zu umgehen, stellen Sie Ihren eigenen "Objekt" -Manager und die entsprechende QuerySet-Unterklasse bereit, die nicht gelöscht wird - siehe Überschreiben von QuerySet.delete () in Django

Danny W. Adair
quelle
2
PS: Und ja, wie in der anderen Antwort besteht der Weg wahrscheinlich darin, diese drei Dinge in einer ReadOnlyAdmin-Klasse zu definieren und dann von dort, wo Sie dieses Verhalten benötigen, eine Unterklasse zu erstellen. Könnte sogar ausgefallen werden und die Definition von Gruppen / Berechtigungen zulassen, die bearbeitet werden dürfen, und dann entsprechend True zurückgeben (und get_readonly_fields () verwenden, das Zugriff auf die Anforderung und damit den aktuellen Benutzer hat).
Danny W. Adair
fast perfekt. Könnte ich gierig fragen, ob es eine Möglichkeit gibt, Zeilen nicht mit einer Bearbeitungsseite zu verknüpfen? (Auch hier müssen Sie keine Zeile vergrößern und nichts bearbeiten.)
Steve Bennett
1
Wenn Sie die list_display_links Ihres ModelAdmin auf etwas setzen, das als False ausgewertet wird (wie eine leere Liste / ein leeres Tupel), setzt ModelAdmin .__ init __ () list_display_links auf alle Spalten (außer dem Kontrollkästchen action) - siehe options.py. Ich denke, das ist getan, um sicherzustellen, dass es Links gibt. Also würde ich __init __ () in einem ReadOnlyAdmin überschreiben, den übergeordneten aufrufen und list_display_links auf eine leere Liste oder ein leeres Tupel setzen. Da Sie jetzt keine Links zu den schreibgeschützten Änderungsformularen haben, ist es wahrscheinlich am besten, ein Parameter- / Klassenattribut dafür zu erstellen - ich würde nicht denken, dass dies das allgemein gewünschte Verhalten ist. Hth
Danny W. Adair
In Bezug auf readonly_fields, die aus dem Modell festgelegt werden, funktioniert dies wahrscheinlich nicht, wenn Sie das Formular überschreiben und andere Felder hinzufügen. Es ist wahrscheinlich besser , es auf den tatsächlichen Formularfeldern zu basieren .
Danny W. Adair
Dies hat nicht funktioniert: def __init __ (self, * args): super (RegistrationStatusAdmin, self) .__ init __ (* args) self.display_links = []
Steve Bennett
49

Hier sind zwei Klassen, die ich verwende, um ein Modell zu erstellen, und / oder dessen Inlines schreibgeschützt sind.

Für Model Admin:

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

Für Inlines:

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
quelle
Wie Sie beide Klassen auf eine Unterklasse anwenden. ZB wenn ich normale Felder und Inlines in einer Klasse habe? Kann ich beide verlängern?
Timo
@ Timo verwenden diese Klassen als Mixins
MartinM
1
has_add_permissionin ReadOnlyAdminnimmt nur Anfrage als Parameter
MartinM
Die has_change_permission () muss ebenfalls überschrieben werden. def has_change_permission (self, Anfrage, obj = None):
david euler
13

Wenn Sie möchten, dass der Benutzer merkt, dass er es nicht bearbeiten kann, fehlen bei der ersten Lösung 2 Teile. Sie haben die Löschaktion entfernt!

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

Zweitens: Die schreibgeschützte Lösung funktioniert problemlos bei einfachen Modellen. Es funktioniert jedoch NICHT , wenn Sie ein geerbtes Modell mit Fremdschlüsseln haben. Leider kenne ich die Lösung dafür noch nicht. Ein guter Versuch ist:

Ganzes Modell als schreibgeschützt

Aber es funktioniert auch nicht bei mir.

Und ein letzter Hinweis: Wenn Sie über eine umfassende Lösung nachdenken möchten, müssen Sie durchsetzen, dass jede Inline auch schreibgeschützt sein muss.

Josir
quelle
11

Eigentlich können Sie diese einfache Lösung ausprobieren:

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

    def has_add_permission(self, request):
        return False
  • actions = None: Vermeidet das Anzeigen der Dropdown-Liste mit der Option "Ausgewählte löschen ..."
  • list_display_links = None: vermeidet das Klicken in Spalten, um dieses Objekt zu bearbeiten
  • has_add_permission() Durch die Rückgabe von False wird vermieden, dass neue Objekte für dieses Modell erstellt werden
Iván Zugnoni
quelle
1
Dies verbietet das Öffnen einer Instanz zum Anzeigen der Felder. Wenn Sie jedoch nur eine Liste erstellen möchten, funktioniert dies.
Sebastián Vansteenkiste
8

Dies wurde in Django 2.1 hinzugefügt, das am 01.08.18 veröffentlicht wurde!

ModelAdmin.has_view_permission()ist genau wie die vorhandene has_delete_permission, has_change_permission und has_add_permission. Sie können darüber in den Dokumenten hier lesen

Aus den Versionshinweisen:

Auf diese Weise können Benutzer schreibgeschützt auf Modelle im Administrator zugreifen. ModelAdmin.has_view_permission () ist neu. Die Implementierung ist insofern abwärtskompatibel, als die Berechtigung "Anzeigen" nicht zugewiesen werden muss, damit Benutzer mit der Berechtigung "Ändern" Objekte bearbeiten können.

grrrrrr
quelle
Der Superuser kann die Objekte in der Admin-Oberfläche trotzdem ändern, oder?
Flimm
Dies ist korrekt, es sei denn, Sie überschreiben eine dieser Methoden, um das Verhalten so zu ändern, dass der Zugriff von Superusern nicht zulässig ist.
Grrrrrr
6

Wenn die akzeptierte Antwort bei Ihnen nicht funktioniert, versuchen Sie Folgendes:

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
quelle
5

Das Kompilieren der hervorragenden Antworten von @darklow und @josir sowie das Hinzufügen von etwas mehr zum Entfernen der Schaltflächen "Speichern" und "Speichern und fortfahren" führt zu (in Python 3-Syntax):

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

und dann benutzt du gerne

class MyModelAdmin(ReadOnlyAdmin):
    pass

Ich habe dies nur mit Django 1.11 / Python 3 versucht.

Mark Chackerian
quelle
Es ist sehr lange her, seit ich Django benutzt habe. Kann noch jemand dafür bürgen?
Steve Bennett
@SteveBennett ㄹ Es gibt viele Variationen der Anforderungen, die in dieser Adresse behandelt werden. Diese Antwort ist nicht wasserdicht. Schlagen Sie die Erklärung hier vor: stackoverflow.com/a/36019597/2586761 und die Antwort, die Sie auf stackoverflow.com kommentiert haben / a / 33543817/2586761 als vollständiger als die akzeptierte Antwort
ptim
3

Die akzeptierte Antwort sollte funktionieren, dies behält jedoch auch die Anzeigereihenfolge der schreibgeschützten Felder bei. Mit dieser Lösung müssen Sie das Modell auch nicht fest codieren.

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
quelle
3

Mit Django 2.2 mache ich das so:

@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
quelle
mit django 2.2 sind die readonly_fieldsund actions
linien
3

Mit Django 2.2 kann schreibgeschützter Administrator so einfach sein wie:

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

Ich hatte die gleiche Anforderung, als ich alle Felder für bestimmte Benutzer in Django Admin schreibgeschützt machen musste. Am Ende nutzte ich das Django-Modul "Django-Admin-View-Berechtigung", ohne meinen eigenen Code zu rollen. Wenn Sie eine genauere Steuerung benötigen, um explizit zu definieren, welche Felder vorhanden sind, müssen Sie das Modul erweitern. Sie können das Plugin in Aktion hier überprüfen

Timothy Mugayi
quelle
0

schreibgeschützt => Ansichten anzeigen

  1. pipenv install django-admin-view-permission
  2. Fügen Sie 'admin_view_permission' zu INSTALLED_APPS in der settings.py. folgendermaßen hinzu: `INSTALLED_APPS = ['admin_view_permission',
  3. python manage.py migrieren
  4. python manage.py runserver 6666

ok.have Spaß mit der "Ansichten" Erlaubnis

Xianhong Xu
quelle
0

Ich habe eine generische Klasse geschrieben, um die ReadOnly-Ansicht abhängig von den Benutzerberechtigungen, einschließlich Inlines, zu verarbeiten.

In 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

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

Dann können wir unsere Klassen in admin.py normal erben:

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