¿Modelos de solo lectura en la interfaz de administración de Django?

86

¿Cómo puedo hacer que un modelo sea completamente de solo lectura en la interfaz de administración? Es para una especie de tabla de registro, donde estoy usando las funciones de administración para buscar, ordenar, filtrar, etc., pero no es necesario modificar el registro.

En caso de que parezca un duplicado, esto no es lo que estoy tratando de hacer:

  • No estoy buscando campos de solo lectura (incluso hacer que todos los campos sean de solo lectura le permitiría crear nuevos registros)
  • No estoy buscando crear un usuario de solo lectura : todos los usuarios deben ser de solo lectura.
Steve Bennett
fuente
2
esta función debería estar disponible pronto: github.com/django/django/pull/5297
Bosco
2
has_view_permissionfinalmente se implementó en Django 2.1. También vea stackoverflow.com/a/51641149 a continuación.
djvg

Respuestas:

21

Ver 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 (para 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
fuente
Parece legitimo. Sin embargo, ha pasado tanto tiempo desde que utilicé Django, podría esperar a ver qué tienen que decir otros comentaristas.
Steve Bennett
¿Es esto un mixin para el Model, o para el ModelAdmin?
OrangeDog
Es para el ModelAdmin.
Pascal Polleunus
Para Django 1.8 y posteriores, get_all_field_names está obsoleto. Manera compatible con versiones anteriores de obtenerlos . Manera corta de conseguirlos .
fzzylogic
Puede usar has_add_permission
rluts
70

El administrador es para editar, no solo para ver (no encontrará un permiso de "ver"). Para lograr lo que desea, tendrá que prohibir agregar, eliminar y hacer que todos los campos sean de solo lectura:

class MyAdmin(ModelAdmin):

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

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

(si prohíbe el cambio, ni siquiera podrá ver los objetos)

Para algunos códigos no probados que intentan automatizar la configuración de todos los campos de solo lectura, vea mi respuesta a Todo el modelo como de solo lectura

EDITAR: tampoco probado, pero acabo de echar un vistazo a mi LogEntryAdmin y tiene

readonly_fields = MyModel._meta.get_all_field_names()

No sé si eso funcionará en todos los casos.

EDITAR: QuerySet.delete () aún puede eliminar objetos de forma masiva. Para evitar esto, proporcione su propio administrador de "objetos" y la subclase correspondiente de QuerySet que no se borra; consulte Anulación de QuerySet.delete () en Django

Danny W. Adair
fuente
2
PD: y sí, como en la otra respuesta, el camino a seguir es probablemente definir esas tres cosas en una clase ReadOnlyAdmin, y luego subclase desde donde sea que necesite ese comportamiento. Incluso podría conseguir la suposición y permitir la definición de los grupos / permisos, que están autorizados a editar, y luego devolver True correspondiente (y get_readonly_fields uso (), que tiene acceso a la solicitud y por lo tanto el usuario actual).
Danny W. Adair
casi perfecto. ¿Podría preguntar con avidez si hay una manera de que las filas no se vinculen a una página de edición? (de nuevo, no es necesario hacer zoom en ninguna fila y no es necesario editar nada)
Steve Bennett
1
Si establece los list_display_links de su ModelAdmin en algo que se evalúe como False (como una lista / tupla vacía), ModelAdmin .__ init __ () establece list_display_links en todas las columnas (excepto la casilla de verificación de acción) - vea options.py. Supongo que eso se hace para asegurar que haya enlaces. Así que anularía __init __ () en un ReadOnlyAdmin, llamaría al padre y luego establecería list_display_links en una lista o tupla vacía. Dado que ahora no tendrá enlaces a los formularios de cambio de solo lectura, probablemente sea mejor crear un atributo de parámetro / clase para esto; no creo que ese sea el comportamiento generalmente deseado. Hth
Danny W. Adair
Con respecto a readonly_fields que se configuran desde el modelo, esto probablemente no funcionará si anula el formulario y agrega otros campos ... basarlo en los campos reales del formulario probablemente sea mejor.
Danny W. Adair
Esto no funcionó: def __init __ (self, * args): super (RegistrationStatusAdmin, self) .__ init __ (* args) self.display_links = []
Steve Bennett
50

Aquí hay dos clases que estoy usando para hacer un modelo y / o solo lectura en línea.

Para el administrador de modelos:

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

Para líneas:

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
oscuro
fuente
Cómo aplica ambas clases a una subclase. ¿Por ejemplo, si tengo campos normales y en línea en una clase? ¿Puedo extender ambos?
Timo
@timo usa estas clases como mixins
MartinM
1
has_add_permissionin ReadOnlyAdmintoma solo la solicitud como parámetro
MartinM
has_change_permission () también debe anularse. def has_change_permission (self, request, obj = None):
david euler
13

Si desea que el usuario sepa que no puede editarlo, faltan 2 piezas en la primera solución. ¡Has eliminado la acción de eliminación!

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

Segundo: la solución de solo lectura funciona bien en modelos simples. Pero NO funciona si tiene un modelo heredado con claves externas. Desafortunadamente, todavía no conozco la solución. Un buen intento es:

Todo el modelo como de solo lectura

Pero tampoco me funciona.

Y una nota final, si desea pensar en una solución amplia, debe hacer cumplir que cada línea debe ser de solo lectura también.

Josir
fuente
11

De hecho, puedes probar esta sencilla solución:

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

    def has_add_permission(self, request):
        return False
  • actions = None: evita mostrar el menú desplegable con la opción "Eliminar seleccionados ..."
  • list_display_links = None: evita hacer clic en columnas para editar ese objeto
  • has_add_permission() devolver False evita la creación de nuevos objetos para ese modelo
Iván Zugnoni
fuente
1
Esto prohíbe abrir cualquier instancia para ver los campos, sin embargo, si está bien con solo enumerar, entonces funciona.
Sebastián Vansteenkiste
8

¡Esto se agregó a Django 2.1 que se lanzó el 1/8/18!

ModelAdmin.has_view_permission()es como los has_delete_permission, has_change_permission y has_add_permission existentes. Puedes leer sobre esto en los documentos aquí.

De las notas de la versión:

Esto permite dar a los usuarios acceso de solo lectura a los modelos en el administrador. ModelAdmin.has_view_permission () es nuevo. La implementación es compatible con versiones anteriores en el sentido de que no es necesario asignar el permiso "ver" para permitir que los usuarios que tienen el permiso "cambiar" editen objetos.

grrrrrr
fuente
Sin embargo, el superusuario aún podrá modificar los objetos en la interfaz de administración, ¿verdad?
Flimm
Eso es correcto, a menos que anule uno de estos métodos para cambiar el comportamiento y no permitir el acceso de superusuarios.
grrrrrr
6

Si la respuesta aceptada no funciona para usted, intente esto:

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

Compilar las excelentes respuestas de @darklow y @josir, además de agregar un poco más para eliminar los botones "Guardar" y "Guardar y continuar" conduce a (en la sintaxis de 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

y luego usas like

class MyModelAdmin(ReadOnlyAdmin):
    pass

Solo probé esto con Django 1.11 / Python 3.

Mark Chackerian
fuente
Ha pasado mucho tiempo desde que utilicé Django. ¿Alguien más puede dar fe de esto?
Steve Bennett
@SteveBennett ㄹ hay muchas variaciones en los requisitos que esto aborda ... esta respuesta no es hermética ... sugiera la explicación aquí: stackoverflow.com/a/36019597/2586761 y la respuesta que comentó en stackoverflow.com / a / 33543817/2586761 como más completo que la respuesta aceptada
ptim
3

La respuesta aceptada debería funcionar, pero esto también preservará el orden de visualización de los campos de solo lectura. Tampoco tiene que codificar el modelo con esta solución.

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
comida de oso
fuente
3

Con Django 2.2 lo hago así:

@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
fuente
con django 2.2, las líneas readonly_fieldsy actionsno son necesarias
cheng10
3

con django 2.2, el administrador de solo lectura puede ser tan simple como:

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

Me encontré con el mismo requisito cuando necesité hacer que todos los campos fueran de solo lectura para ciertos usuarios en django admin terminó aprovechando el módulo de django "django-admin-view-allow" sin ejecutar mi propio código. Si necesita un control más detallado para definir explícitamente qué campos, necesitará ampliar el módulo. Puedes ver el complemento en acción aquí.

Timothy Mugayi
fuente
0

solo lectura => permiso de visualización

  1. pipenv install django-admin-view-permission
  2. agregue 'admin_view_permission' a INSTALLED_APPS en settings.py. de esta manera: `INSTALLED_APPS = ['admin_view_permission',
  3. python manage.py migrar
  4. python manage.py runserver 6666

ok, diviértete con el permiso de 'vistas'

Xianhong Xu
fuente
0

He escrito una clase genérica para manejar la vista de solo lectura según los permisos del usuario, incluidas las líneas;)

En 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

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

Entonces, podemos simplemente heredar normalmente nuestras clases en admin.py:

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