Modelli di sola lettura nell'interfaccia di amministrazione di Django?


86

Come posso rendere un modello completamente di sola lettura nell'interfaccia di amministrazione? È per una sorta di tabella di registro, in cui sto utilizzando le funzionalità di amministrazione per cercare, ordinare, filtrare ecc., Ma non è necessario modificare il registro.

Nel caso in cui questo appare come un duplicato, ecco , non quello che sto cercando di fare:

  • Non sto cercando campi di sola lettura (anche rendere ogni campo di sola lettura ti consentirebbe comunque di creare nuovi record)
  • Non sto cercando di creare un utente di sola lettura : ogni utente dovrebbe essere di sola lettura.

2
questa funzionalità dovrebbe essere disponibile a breve: github.com/django/django/pull/5297
Bosco

2
has_view_permissionè stato finalmente implementato in Django 2.1. Vedi anche stackoverflow.com/a/51641149 di seguito.
djvg

Risposte:


21

Vedi 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 (per 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 %}

Sembra giusto. È passato così tanto tempo dall'ultima volta che ho usato Django, potrei aspettare di vedere cosa hanno da dire gli altri commentatori.
Steve Bennett

È un mixin per il Model, o per il ModelAdmin?
OrangeDog

È per il ModelAdmin.
Pascal Polleunus

Per Django 1.8 e versioni successive, get_all_field_names è deprecato. Modo compatibile con le versioni precedenti per ottenerli . Breve strada per ottenerli .
fzzylogic

Puoi usare has_add_permission
rluts

70

L'amministratore è per la modifica, non solo per la visualizzazione (non troverai un permesso di "visualizzazione"). Per ottenere ciò che desideri, dovrai vietare l'aggiunta, l'eliminazione e rendere tutti i campi di sola lettura:

class MyAdmin(ModelAdmin):

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

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

(se proibisci di cambiare non vedrai nemmeno gli oggetti)

Per alcuni codici non testati che tentano di automatizzare l'impostazione di tutti i campi di sola lettura, vedere la mia risposta a Modello intero come di sola lettura

EDIT: anche non testato ma ho appena dato un'occhiata al mio LogEntryAdmin e lo ha fatto

readonly_fields = MyModel._meta.get_all_field_names()

Non so se funzionerà in tutti i casi.

EDIT: QuerySet.delete () può ancora eliminare in blocco gli oggetti. Per aggirare questo problema, fornisci il tuo gestore di "oggetti" e la corrispondente sottoclasse QuerySet che non cancella - vedi Sostituzione di QuerySet.delete () in Django


2
PS: e sì, come nell'altra risposta, la strada da percorrere è probabilmente definire queste tre cose in una classe ReadOnlyAdmin, e quindi sottoclasse da quella in cui hai bisogno di quel comportamento. Potrebbe anche ottenere l'immaginazione e consentire la definizione di gruppi / permessi, che sono autorizzati a modificare, e poi tornare Vero corrispondentemente (e sull'uso get_readonly_fields (), che ha accesso alla richiesta e quindi l'utente corrente).
Danny W. Adair

quasi perfetto. potrei chiedere avidamente se c'è un modo per non avere righe di collegamento a una pagina di modifica? (di nuovo, non è necessario ingrandire nessuna riga e non è necessario modificare nulla)
Steve Bennett,

1
Se imposti list_display_links del tuo ModelAdmin su qualcosa che viene valutato come False (come una lista / tupla vuota), ModelAdmin .__ init __ () imposta list_display_links su tutte le colonne (eccetto la casella di controllo dell'azione) - vedi options.py. Immagino che sia fatto per garantire che ci siano collegamenti. Quindi sovrascriverei __init __ () in ReadOnlyAdmin, chiamerei quello genitore e quindi imposterei list_display_links su un elenco o una tupla vuota. Dato che ora non avrai collegamenti ai moduli di modifica in sola lettura, probabilmente è meglio creare un attributo parametro / classe per questo - non penso che sia il comportamento generalmente desiderato. Hth
Danny W. Adair

Per quanto riguarda readonly_fields impostati dal modello, questo probabilmente non funzionerà se si sovrascrive il modulo e si aggiungono altri campi ... basarsi sui campi effettivi del modulo è probabilmente meglio.
Danny W. Adair,

Questo non ha funzionato: def __init __ (self, * args): super (RegistrationStatusAdmin, self) .__ init __ (* args) self.display_links = []
Steve Bennett

50

Qui ci sono due classi che sto usando per creare un modello e / o è inline di sola lettura.

Per l'amministratore del modello:

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

Per inline:

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

Come applicare entrambe le classi a una sottoclasse. Ad esempio, se ho campi normali e inline in una classe? Posso estendere entrambi?
Timo

@timo usa queste classi come mixin
MartinM

1
has_add_permissionin ReadOnlyAdminaccetta solo la richiesta come parametro
MartinM

anche has_change_permission () deve essere sovrascritto. def has_change_permission (self, request, obj = None):
david euler

13

Se vuoi che l'utente si accorga che non può modificarlo, nella prima soluzione mancano 2 pezzi. Hai rimosso l'azione di eliminazione!

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

Secondo: la soluzione di sola lettura funziona bene sui modelli semplici. Ma NON funziona se hai un modello ereditato con chiavi esterne. Sfortunatamente, non conosco ancora la soluzione per questo. Un buon tentativo è:

Intero modello in sola lettura

Ma non funziona neanche per me.

E una nota finale, se vuoi pensare a una soluzione ampia, devi imporre che anche ogni inline debba essere di sola lettura.


11

In realtà puoi provare questa semplice soluzione:

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

    def has_add_permission(self, request):
        return False
  • actions = None: evita di mostrare il menu a tendina con l'opzione "Elimina selezionati ..."
  • list_display_links = None: evita di fare clic nelle colonne per modificare quell'oggetto
  • has_add_permission() restituendo False evita di creare nuovi oggetti per quel modello

1
Questo vieta l'apertura di qualsiasi istanza per la visualizzazione dei campi, tuttavia se va bene solo con l'elenco, allora funziona.
Sebastián Vansteenkiste

8

Questo è stato aggiunto a Django 2.1 che è stato rilasciato l'8 / 1/18!

ModelAdmin.has_view_permission()è proprio come has_delete_permission, has_change_permission e has_add_permission esistenti. Puoi leggere a riguardo nei documenti qui

Dalle note di rilascio:

Ciò consente agli utenti di accedere in sola lettura ai modelli nell'amministratore. ModelAdmin.has_view_permission () è nuovo. L'implementazione è retrocompatibile in quanto non è necessario assegnare l'autorizzazione di "visualizzazione" per consentire agli utenti che dispongono dell'autorizzazione di "modifica" di modificare gli oggetti.


Il superutente sarà comunque in grado di modificare gli oggetti nell'interfaccia di amministrazione, giusto?
Flimm

È corretto, a meno che non si sovrascriva uno di questi metodi per modificare il comportamento in modo da non consentire l'accesso ai superutenti.
grrrrrr

6

Se la risposta accettata non funziona per te, prova questo:

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

Compilare le eccellenti risposte di @darklow e @josir, oltre ad aggiungere un po 'di più per rimuovere i pulsanti "Salva" e "Salva e continua" porta a (nella sintassi di 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

e poi usi mi piace

class MyModelAdmin(ReadOnlyAdmin):
    pass

L'ho provato solo con Django 1.11 / Python 3.


È passato molto tempo da quando ho usato Django. Qualcun altro può garantire per questo?
Steve Bennett

@SteveBennett ㄹ ci sono molte variazioni sui requisiti che questo affronta ... questa risposta non è a tenuta stagna ... suggerisci la spiegazione qui: stackoverflow.com/a/36019597/2586761 e la risposta che hai commentato su stackoverflow.com / a / 33543817/2586761 come più completo della risposta accettata
ptim

3

La risposta accettata dovrebbe funzionare, ma preserverà anche l'ordine di visualizzazione dei campi di sola lettura. Inoltre, non è necessario codificare il modello con questa soluzione.

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

Con Django 2.2 lo faccio in questo modo:

@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

con django 2.2, le linee readonly_fieldse actionsnon sono necessarie
cheng10

3

con django 2.2, readonly admin può essere semplice come:

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

Mi sono imbattuto nello stesso requisito quando avevo bisogno di rendere tutti i campi di sola lettura per determinati utenti in django admin, finendo per sfruttare il modulo django "django-admin-view-permission" senza eseguire il rollio del mio codice. Se è necessario un controllo più dettagliato per definire in modo esplicito quali campi, è necessario estendere il modulo. Puoi controllare il plugin in azione qui


0

sola lettura => autorizzazioni di visualizzazione

  1. pipenv install django-admin-view-permission
  2. aggiungi 'admin_view_permission' a INSTALLED_APPS in settings.py. in questo modo: `INSTALLED_APPS = ['admin_view_permission',
  3. python manage.py migrate
  4. python manage.py runserver 6666

ok. divertiti con il permesso "visualizzazioni"


0

Ho scritto una classe generica per gestire la visualizzazione ReadOnly a seconda delle autorizzazioni dell'utente, inclusi gli inline;)

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 )

Quindi, possiamo semplicemente ereditare normalmente le nostre classi in admin.py:

class ContactAdmin(ReadOnlyAdmin):
    list_display = ("name","email","whatever")
    readonly_fields = ("updated","created")
    inlines = ( PhoneInline, ... )
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.