Modelos somente leitura na interface de administração do Django?


86

Como posso tornar um modelo totalmente somente leitura na interface de administração? É para uma espécie de tabela de log, onde estou usando os recursos do administrador para pesquisar, classificar, filtrar etc, mas não há necessidade de modificar o log.

Caso pareça uma duplicata, não é o que estou tentando fazer:

  • Não estou procurando campos somente leitura (mesmo tornar cada campo somente leitura ainda permitiria a criação de novos registros)
  • Não estou procurando criar um usuário somente leitura : todo usuário deve ser somente leitura.

2
esse recurso deve chegar em breve: github.com/django/django/pull/5297
Bosco

2
has_view_permissionfoi finalmente implementado no Django 2.1. Consulte também stackoverflow.com/a/51641149 abaixo.
djvg

Respostas:


21

Veja 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 %}

Parece legítimo. Já faz tanto tempo desde que usei o Django, porém, posso esperar para ver o que outros comentaristas têm a dizer.
Steve Bennett

Este é um mixin para o Model, ou para o ModelAdmin?
OrangeDog

É para o ModelAdmin.
Pascal Polleunus de

Para Django 1.8 e posterior, get_all_field_names está obsoleto. Forma compatível com versões anteriores de obtê-los . Caminho curto para obtê-los .
fzzylogic

Você pode usar has_add_permission
rluts

70

O administrador é para edição, não apenas visualização (você não encontrará uma permissão de "visualização"). Para conseguir o que deseja, você terá que proibir a adição, exclusão e tornar todos os campos somente leitura:

class MyAdmin(ModelAdmin):

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

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

(se você proibir a mudança, você nem conseguirá ver os objetos)

Para alguns códigos não testados que tentam automatizar a configuração de todos os campos somente leitura, veja minha resposta para Modelo inteiro como somente leitura

EDITAR: também não testado, mas acabei de dar uma olhada no meu LogEntryAdmin e

readonly_fields = MyModel._meta.get_all_field_names()

Não sei se isso funcionará em todos os casos.

EDIT: QuerySet.delete () ainda pode excluir objetos em massa. Para contornar isso, forneça seu próprio gerenciador de "objetos" e a subclasse QuerySet correspondente que não exclui - veja Substituindo QuerySet.delete () no Django


2
PS: e sim, como na outra resposta, o caminho a percorrer é provavelmente definir essas três coisas em uma classe ReadOnlyAdmin e, em seguida, criar uma subclasse disso onde quer que você precise desse comportamento. Pode até ficar mais sofisticado e permitir a definição de grupos / permissões que têm permissão para editar, e então retornar True correspondentemente (e usar get_readonly_fields () que tem acesso à solicitação e, portanto, ao usuário atual).
Danny W. Adair

quase perfeito. eu poderia perguntar avidamente se há uma maneira de não ter linhas vinculadas a uma página de edição? (novamente, não há necessidade de aumentar o zoom em nenhuma linha e nem de editar nada)
Steve Bennett

1
Se você definir os list_display_links do ModelAdmin para algo que seja avaliado como False (como uma lista / tupla vazia), ModelAdmin .__ init __ () define list_display_links para todas as colunas (exceto a caixa de seleção de ação) - consulte options.py. Acho que isso é feito para garantir que haja links. Portanto, eu substituiria __init __ () em um ReadOnlyAdmin, chamaria o pai e definiria list_display_links para uma lista vazia ou tupla. Dado que agora você não terá links para os formulários de alteração somente leitura, provavelmente é melhor criar um atributo de parâmetro / classe para isso - não acho que esse seja o comportamento geralmente desejado. Hth
Danny W. Adair

Com relação a readonly_fields sendo configurados a partir do modelo, isso provavelmente não funcionará se você substituir o formulário e adicionar outros campos ... baseando-o nos campos reais do formulário é provavelmente melhor.
Danny W. Adair

Isso não funcionou: def __init __ (self, * args): super (RegistrationStatusAdmin, self) .__ init __ (* args) self.display_links = []
Steve Bennett

50

Aqui estão duas classes que estou usando para fazer um modelo e / ou em linha somente para leitura.

Para modelo de administrador:

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

Como você aplica ambas as classes a uma subclasse. Por exemplo, se eu tiver campos normais e inlines em uma classe? Posso estender ambos?
Timo,

@timo usa essas classes como mixins
MartinM

1
has_add_permissionin ReadOnlyAdminleva apenas a solicitação como parâmetro
MartinM

o has_change_permission () também precisa ser sobrescrito. def has_change_permission (self, request, obj = None):
david euler

13

Se você quiser que o usuário saiba que não pode editá-lo, faltam 2 peças na primeira solução. Você removeu a ação de exclusão!

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: a solução somente leitura funciona bem em modelos simples. Mas NÃO funciona se você tiver um modelo herdado com chaves estrangeiras. Infelizmente, ainda não sei a solução para isso. Uma boa tentativa é:

Modelo inteiro como somente leitura

Mas também não funciona para mim.

E uma nota final, se você quiser pensar em uma solução ampla, você deve garantir que cada inline também seja somente leitura.


11

Na verdade, você pode tentar esta solução simples:

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 a lista suspensa com a opção "Excluir selecionados ..."
  • list_display_links = None: evita clicar nas colunas para editar esse objeto
  • has_add_permission() retornar False evita a criação de novos objetos para esse modelo

1
Isso proíbe abrir qualquer instância para visualizar os campos, no entanto, se não houver problema em apenas listar, então funciona.
Sebastián Vansteenkiste

8

Isso foi adicionado ao Django 2.1 que foi lançado em 01/08/18!

ModelAdmin.has_view_permission()é como a has_delete_permission, has_change_permission e has_add_permission existentes. Você pode ler sobre isso nos documentos aqui

Das notas de lançamento:

Isso permite dar aos usuários acesso somente leitura aos modelos no admin. ModelAdmin.has_view_permission () é novo. A implementação é compatível com versões anteriores no sentido de que não há necessidade de atribuir a permissão "visualizar" para permitir que os usuários com a permissão "alterar" editem objetos.


O superusuário ainda poderá modificar os objetos na interface de administração, certo?
Flimm

Isso está correto, a menos que você substitua um desses métodos para alterar o comportamento e impedir o acesso de superusuários.
grrrrrr

6

Se a resposta aceita não funcionar para você, tente o seguinte:

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

Compilar as excelentes respostas de @darklow e @josir, além de adicionar um pouco mais para remover os botões "Salvar" e "Salvar e continuar", leva a (na sintaxe 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 então você usa como

class MyModelAdmin(ReadOnlyAdmin):
    pass

Só tentei fazer isso com Django 1.11 / Python 3.


Já faz muito tempo que não uso Django. Alguém mais pode garantir isso?
Steve Bennett

@SteveBennett ㄹ existem muitas variações nos requisitos que isso atende ... esta resposta não é à prova d'água ... sugira a explicação aqui: stackoverflow.com/a/36019597/2586761 e a resposta que você comentou em stackoverflow.com / a / 33543817/2586761 como mais completa do que a resposta aceita
ptim

3

A resposta aceita deve funcionar, mas isso também preservará a ordem de exibição dos campos somente leitura. Você também não precisa codificar o modelo com esta solução.

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

Com Django 2.2 eu faço assim:

@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

com django 2.2, as linhas readonly_fieldse actionsnão são necessárias
cheng10

3

com o django 2.2, o admin somente leitura pode ser tão simples quanto:

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

Eu encontrei o mesmo requisito ao precisar tornar todos os campos somente leitura para certos usuários no django admin. Acabei aproveitando o módulo django "django-admin-view-permission" sem rolar meu próprio código. Se você precisar de um controle mais refinado para definir explicitamente quais campos, você precisará estender o módulo. Você pode verificar o plugin em ação aqui


0

somente leitura => permissão de visualizações

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

ok.se divirta-se com a permissão 'visualizações'


0

Eu escrevi uma classe genérica para lidar com a visualização ReadOnly dependendo das permissões do usuário, incluindo inlines;)

Em 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

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

Então, podemos apenas herdar normalmente nossas classes em admin.py:

class ContactAdmin(ReadOnlyAdmin):
    list_display = ("name","email","whatever")
    readonly_fields = ("updated","created")
    inlines = ( PhoneInline, ... )
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.