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.
Steve Bennett
fonte
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 %}
Pascal Polleunus
fonte
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

Danny W. Adair
fonte
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
Darklow
fonte
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.

Josir
fonte
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
Iván Zugnoni
fonte
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.

grrrrrr
fonte
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
Wouter
fonte
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.

Mark Chackerian
fonte
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
lastoneisbearfood
fonte
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
Eerik Sven Puudist
fonte
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')
cheng10
fonte
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

Timothy Mugayi
fonte
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'

Xianhong Xu
fonte
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, ... )
Enric Mieza
fonte