django admin torna um campo somente leitura ao modificar obj, mas necessário ao adicionar novo obj

92

No admin, gostaria de desativar um campo ao modificar um objeto, mas torná-lo obrigatório ao adicionar um novo objeto.

Qual é o jeito do django de fazer isso?

frnhr
fonte

Respostas:

182

Você pode substituir o get_readonly_fieldsmétodo do administrador :

class MyModelAdmin(admin.ModelAdmin):

    def get_readonly_fields(self, request, obj=None):
        if obj: # editing an existing object
            return self.readonly_fields + ('field1', 'field2')
        return self.readonly_fields
Bernhard Vallant
fonte
21
Advertência secundária / principal: isso não funciona para inlines. O botão dinâmico "adicionar outro X" mostra o campo somente leitura como "(Nenhum)", não um campo de formulário como você esperava.
Cerin de
17

Se você deseja definir todos os campos como somente leitura na visualização de alteração, substitua os get_readonly_fields do administrador:

def get_readonly_fields(self, request, obj=None):
    if obj: # editing an existing object
        # All model fields as read_only
        return self.readonly_fields + tuple([item.name for item in obj._meta.fields])
    return self.readonly_fields

E se você deseja ocultar os botões de salvar na visualização de mudança :

  1. Mudar a vista

    def change_view(self, request, object_id, form_url='', extra_context=None):
        ''' customize edit form '''
        extra_context = extra_context or {}
        extra_context['show_save_and_continue'] = False
        extra_context['show_save'] = False
        extra_context['show_save_and_add_another'] = False # this not works if has_add_permision is True
        return super(TransferAdmin, self).change_view(request, object_id, extra_context=extra_context)
    
  2. Altere as permissões se o usuário estiver tentando editar:

    def has_add_permission(self, request, obj=None):
       # Not too much elegant but works to hide show_save_and_add_another button
        if '/change/' in str(request):
            return False
        return True
    

    Esta solução foi testada no Django 1.11

DVicenteR
fonte
Perfeito. Isso é exatamente o que eu precisava!
wogsland
3

Para sua informação: caso outra pessoa tenha os mesmos dois problemas que eu encontrei:

  1. Você ainda deve declarar qualquer readonly_fields permanentemente no corpo da classe, já que o atributo de classe readonly_fields será acessado a partir da validação (veja django.contrib.admin.validation: validate_base (), line.213 appx)

  2. Isso não funcionará com Inlines já que o obj passado para get_readonly_fields () é o obj pai (eu tenho duas soluções bastante hacky e de baixa segurança usando css ou js)

Tim Diggins
fonte
2
2. ponto - isso é devido a um bug no administrador: # 15602 Parece que não será corrigido em breve (última atividade 2 anos atrás), então parece que ficamos com as soluções CSS / JS.
frnhr
3

Uma variação baseada na excelente sugestão anterior de Bernhard Vallant, que também preserva qualquer personalização possível fornecida pela classe base (se houver):

class MyModelAdmin(BaseModelAdmin):

    def get_readonly_fields(self, request, obj=None):
        readonly_fields = super(MyModelAdmin, self).get_readonly_fields(request, obj)
        if obj: # editing an existing object
            return readonly_fields + ['field1', ..]
        return readonly_fields
Mario Orlandi
fonte
2

A situação com os formulários embutidos ainda não foi corrigida para Django 2.2.x, mas a solução de John é realmente muito inteligente.

Código ligeiramente ajustado à minha situação:

class NoteListInline(admin.TabularInline):
""" Notes list, readonly """
    model = Note
    verbose_name = _('Note')
    verbose_name_plural = _('Notes')
    extra = 0
    fields = ('note', 'created_at')
    readonly_fields = ('note', 'created_at')

    def has_add_permission(self, request, obj=None):
    """ Only add notes through AddInline """
    return False

class NoteAddInline(admin.StackedInline):
    """ Notes edit field """
    model = Note
    verbose_name = _('Note')
    verbose_name_plural = _('Notes')
    extra = 1
    fields = ('note',)
    can_delete = False

    def get_queryset(self, request):
        queryset = super().get_queryset(request)
        return queryset.none()  # no existing records will appear

@admin.register(MyModel)
class MyModelAdmin(admin.ModelAdmin):
    # ...
    inlines = (NoteListInline, NoteAddInline)
    # ...
Jlapoutre
fonte
0

Você pode fazer isso substituindo o método formfield_for_foreignkey do ModelAdmin:

from django import forms
from django.contrib import admin

from yourproject.yourapp.models import YourModel

class YourModelAdmin(admin.ModelAdmin):

    class Meta:
        model = YourModel

    def formfield_for_foreignkey(self, db_field, request=None, **kwargs):
        # Name of your field here
        if db_field.name == 'add_only':
            if request:
                add_opts = (self._meta.app_label, self._meta.module_name)
                add = u'/admin/%s/%s/add/' % add_opts
                if request.META['PATH_INFO'] == add:
                    field = db_field.formfield(**kwargs)
                else:
                    kwargs['widget'] = forms.HiddenInput()
                    field = db_field.formfield(**kwargs)
            return field
        return admin.ModelAdmin(self, db_field, request, **kwargs)
David Miller
fonte
0

Tenho um problema semelhante. Resolvi isso com "add_fieldsets" e "restricted_fieldsets" no ModelAdmin.

from django.contrib import admin  
class MyAdmin(admin.ModelAdmin):
 declared_fieldsets = None
 restricted_fieldsets = (
    (None, {'fields': ('mod_obj1', 'mod_obj2')}),
    ( 'Text', {'fields': ('mod_obj3', 'mod_obj4',)}),
 )

 add_fieldsets = (
            (None, {
             'classes': ('wide',),
             'fields': ('add_obj1', 'add_obj2', )}),
             )

Consulte, por exemplo: http://code.djangoproject.com/svn/django/trunk/django/contrib/auth/admin.py

Mas isso não protege seu modelo de mudanças posteriores de "add_objX". Se você quiser isso também, acho que você tem que passar pela função "salvar" da classe Model e verificar as alterações lá.

Consulte: www.djangoproject.com/documentation/models/save_delete_hooks/

Greez, Nick

Nick Ma.
fonte
0

Uma solução mais plugável para as grandes soluções de Bernhard e Mario, adicionando suporte para analog createonly_fields para readonly_fields:

class MyModelAdmin(admin.ModelAdmin):

    # ModelAdmin configuration as usual goes here

    createonly_fields = ['title', ]

    def get_readonly_fields(self, request, obj=None):
        readonly_fields = list(super(MyModelAdmin, self).get_readonly_fields(request, obj))
        createonly_fields = list(getattr(self, 'createonly_fields', []))
            
        if obj:  # editing an existing object
            readonly_fields.extend(createonly_fields)
        return readonly_fields
dirkbo
fonte