Modèles en lecture seule dans l'interface d'administration de Django?

86

Comment puis-je rendre un modèle entièrement en lecture seule dans l'interface d'administration? C'est pour une sorte de table de journal, où j'utilise les fonctionnalités d'administration pour rechercher, trier, filtrer, etc., mais il n'est pas nécessaire de modifier le journal.

Dans le cas où cela ressemble à un double, voici ce pas ce que je suis en train de le faire:

  • Je ne recherche pas de champs en lecture seule (même en rendant chaque champ en lecture seule vous permettrait toujours de créer de nouveaux enregistrements)
  • Je ne cherche pas à créer un utilisateur en lecture seule : chaque utilisateur doit être en lecture seule.
Steve Bennett
la source
2
cette fonctionnalité devrait bientôt arriver: github.com/django/django/pull/5297
Bosco
2
has_view_permissiona finalement été implémenté dans Django 2.1. Voir également stackoverflow.com/a/51641149 ci-dessous.
djvg le

Réponses:

21

Voir 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 (pour 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
la source
Semble légitime. Cela fait si longtemps que je n'ai pas utilisé Django, je vais peut-être attendre de voir ce que d'autres commentateurs ont à dire.
Steve Bennett
Est-ce un mixin pour le Model, ou pour le ModelAdmin?
OrangeDog
C'est pour le ModelAdmin.
Pascal Polleunus
Pour Django 1.8 et versions ultérieures, get_all_field_names est obsolète. Méthode rétrocompatible pour les obtenir . Court moyen de les obtenir .
fzzylogic
Vous pouvez utiliser has_add_permission
rluts
70

L'administrateur est destiné à l'édition, pas seulement à la visualisation (vous ne trouverez pas d'autorisation de «vue»). Afin d'obtenir ce que vous voulez, vous devrez interdire l'ajout, la suppression et la mise en lecture seule de tous les champs:

class MyAdmin(ModelAdmin):

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

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

(si vous interdisez de changer vous ne pourrez même pas voir les objets)

Pour un code non testé qui tente d'automatiser la définition de tous les champs en lecture seule, voir ma réponse à tout le modèle en lecture seule

EDIT: également non testé, mais je viens de jeter un œil à mon LogEntryAdmin et il a

readonly_fields = MyModel._meta.get_all_field_names()

Je ne sais pas si cela fonctionnera dans tous les cas.

EDIT: QuerySet.delete () peut toujours supprimer des objets en masse. Pour contourner ce problème, fournissez votre propre gestionnaire "d'objets" et la sous-classe QuerySet correspondante qui ne supprime pas - voir Remplacer QuerySet.delete () dans Django

Danny W. Adair
la source
2
PS: et oui, comme dans l'autre réponse, la voie à suivre est probablement de définir ces trois choses dans une classe ReadOnlyAdmin, puis de sous-classer à partir de là où vous avez besoin de ce comportement. Pourrait même avoir de la fantaisie et permettre la définition de groupes / permissions qui sont autorisés à éditer, puis retourner True en conséquence (et utiliser get_readonly_fields () qui a accès à la requête et donc à l'utilisateur actuel).
Danny W.Adair
presque parfait. puis-je demander avidement s'il existe un moyen de ne pas avoir de lien de lignes vers une page d'édition? (encore une fois, il n'est pas nécessaire de zoomer sur une ligne, ni de modifier quoi que ce soit)
Steve Bennett
1
Si vous définissez list_display_links de votre ModelAdmin sur quelque chose qui évalue False (comme une liste / un tuple vide), ModelAdmin .__ init __ () définit list_display_links sur toutes les colonnes (sauf la case à cocher action) - voir options.py Je suppose que c'est fait pour s'assurer qu'il y a des liens. Donc, je remplacerais __init __ () dans un ReadOnlyAdmin, appeler le parent puis définir list_display_links sur une liste ou un tuple vide. Étant donné que vous n'aurez plus de liens vers les formulaires de modification en lecture seule, il est probablement préférable de créer un attribut de paramètre / classe pour cela - je ne pense pas que ce soit le comportement généralement souhaité. Hth
Danny W.Adair
En ce qui concerne readonly_fields défini à partir du modèle, cela ne fonctionnera probablement pas si vous remplacez le formulaire et ajoutez d'autres champs ... le baser sur les champs du formulaire réels est probablement mieux.
Danny W.Adair
Cela n'a pas fonctionné: def __init __ (self, * args): super (RegistrationStatusAdmin, self) .__ init __ (* args) self.display_links = []
Steve Bennett
50

Voici deux classes que j'utilise pour créer un modèle et / ou il est en lecture seule.

Pour l'administrateur modèle:

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

Pour les 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
la source
Comment appliquer les deux classes à une sous-classe. Par exemple, si j'ai des champs normaux et des inlines dans une classe? Puis-je prolonger les deux?
Timo
@timo utilise ces classes comme mixins
MartinM
1
has_add_permissionen ReadOnlyAdminne prend que la demande en tant que paramètre
MartinM
la fonction has_change_permission () doit également être remplacée. def has_change_permission (self, request, obj = None):
david euler le
13

Si vous voulez que l'utilisateur sache qu'il ne peut pas le modifier, il manque 2 pièces sur la première solution. Vous avez supprimé l'action de suppression!

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

Deuxièmement: la solution en lecture seule fonctionne bien sur les modèles simples. Mais ce n'est PAS fonctionne si vous avez un modèle hérité avec des clés étrangères. Malheureusement, je ne connais pas encore la solution pour cela. Une bonne tentative est:

Modèle entier en lecture seule

Mais cela ne fonctionne pas non plus pour moi.

Et une note finale, si vous voulez réfléchir à une solution globale, vous devez faire en sorte que chaque inline soit également en lecture seule.

Josir
la source
11

En fait, vous pouvez essayer cette solution simple:

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

    def has_add_permission(self, request):
        return False
  • actions = None: évite d'afficher le menu déroulant avec l'option "Supprimer la sélection ..."
  • list_display_links = None: évite de cliquer dans les colonnes pour éditer cet objet
  • has_add_permission() renvoyer False évite de créer de nouveaux objets pour ce modèle
Iván Zugnoni
la source
1
Cela interdit d'ouvrir une instance pour afficher les champs, mais si cela ne vous dérange pas de simplement lister, cela fonctionne.
Sebastián Vansteenkiste
8

Cela a été ajouté à Django 2.1 qui est sorti le 8/1/18!

ModelAdmin.has_view_permission()est exactement comme les has_delete_permission, has_change_permission et has_add_permission existants. Vous pouvez lire à ce sujet dans la documentation ici

À partir des notes de version:

Cela permet de donner aux utilisateurs un accès en lecture seule aux modèles dans l'administrateur. ModelAdmin.has_view_permission () est nouveau. L'implémentation est rétrocompatible en ce sens qu'il n'est pas nécessaire d'attribuer l'autorisation «afficher» pour permettre aux utilisateurs disposant de l'autorisation «modifier» de modifier des objets.

grrrrrr
la source
Le superutilisateur pourra toujours modifier les objets dans l'interface d'administration, non?
Flimm
C'est correct, sauf si vous remplacez l'une de ces méthodes pour modifier le comportement afin d'interdire l'accès des super-utilisateurs.
grrrrrr
6

Si la réponse acceptée ne fonctionne pas pour vous, essayez ceci:

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
la source
5

Compiler les excellentes réponses de @darklow et @josir, et ajouter un peu plus pour supprimer les boutons «Enregistrer» et «Enregistrer et continuer» conduit à (dans la syntaxe 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

et ensuite vous utilisez comme

class MyModelAdmin(ReadOnlyAdmin):
    pass

Je n'ai essayé cela qu'avec Django 1.11 / Python 3.

Mark Chackerian
la source
Cela fait très longtemps que j'utilise Django. Quelqu'un d'autre peut-il en témoigner?
Steve Bennett
@SteveBennett ㄹ il existe de nombreuses variations sur les exigences auxquelles cela répond ... cette réponse n'est pas étanche ... suggérez l'explication ici: stackoverflow.com/a/36019597/2586761 et la réponse que vous avez commentée sur stackoverflow.com / a / 33543817/2586761 comme plus complet que la réponse acceptée
ptim
3

La réponse acceptée devrait fonctionner, mais cela préservera également l'ordre d'affichage des champs en lecture seule. Vous n'avez pas non plus besoin de coder en dur le modèle avec cette solution.

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
la source
3

Avec Django 2.2, je le fais comme ceci:

@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
la source
avec django 2.2, les lignes readonly_fieldset actionsne sont pas nécessaires
cheng10
3

avec django 2.2, l'administrateur en lecture seule peut être aussi simple que:

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
la source
1

J'ai rencontré la même exigence lorsque je devais rendre tous les champs en lecture seule pour certains utilisateurs dans django admin a fini par tirer parti du module django "django-admin-view-permission" sans lancer mon propre code. Si vous avez besoin d'un contrôle plus fin pour définir explicitement les champs, vous devez étendre le module. Vous pouvez consulter le plugin en action ici

Timothy Mugayi
la source
0

lecture seule => autorisation de vues

  1. pipenv install django-admin-view-permission
  2. ajoutez 'admin_view_permission' à INSTALLED_APPS dans les paramètres.py. comme ceci: `INSTALLED_APPS = ['admin_view_permission',
  3. python manage.py migrer
  4. python manage.py runserver 6666

ok.amusez-vous avec la permission 'vues'

Xianhong Xu
la source
0

J'ai écrit une classe générique pour gérer la vue ReadOnly en fonction des autorisations de l'utilisateur, y compris en ligne;)

Dans 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

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

Ensuite, nous pouvons simplement hériter normalement de nos classes dans admin.py:

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