El administrador de django crea un campo de solo lectura al modificar obj, pero es obligatorio al agregar un nuevo obj

91

En admin, me gustaría deshabilitar un campo al modificar un objeto, pero hacerlo obligatorio al agregar un nuevo objeto.

¿Cuál es la forma en que django hace esto?

frnhr
fuente

Respuestas:

178

Puede anular el get_readonly_fieldsmétodo del 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
fuente
21
Advertencia menor / mayor: esto no funciona para inlines. El botón dinámico "agregar otra X" muestra el campo de solo lectura como "(Ninguno)", no un campo de formulario como cabría esperar.
Cerin
17

Si desea establecer todos los campos como de solo lectura solo en la vista de cambio, anule los get_readonly_fields del 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

Y si desea ocultar los botones de guardar en la vista de cambio :

  1. Cambiar la 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. Cambie los permisos si el usuario está intentando 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 solución ha sido probada sobre Django 1.11

DVicentro
fuente
Perfecto. ¡Esto es exactamente lo que necesitaba!
wogsland
3

FYI: en caso de que alguien más se encuentre con los mismos dos problemas que encontré:

  1. Aún debe declarar cualquier readonly_fields permanentemente en el cuerpo de la clase, ya que se accederá al atributo de la clase readonly_fields desde la validación (vea django.contrib.admin.validation: validate_base (), line.213 appx)

  2. Esto no funcionará con Inlines ya que el obj pasado a get_readonly_fields () es el obj padre (tengo dos soluciones bastante hacky y de baja seguridad usando css o js)

Tim Diggins
fuente
2
2. punto - eso se debe a un error en el administrador: # 15602 Parece que no se solucionará tan pronto (última actividad hace 2 años), por lo que parece que nos quedamos con las soluciones CSS / JS.
viernes
2

Una variación basada en la excelente sugerencia anterior de Bernhard Vallant, que también conserva cualquier posible personalización proporcionada por la clase base (si corresponde):

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

La situación con los formularios en línea todavía no se ha solucionado para Django 2.2.x, pero la solución de John es bastante inteligente.

Código ligeramente ajustado a mi situación:

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

Puede hacer esto anulando el método formfield_for_foreignkey del 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
fuente
0

Tengo un problema similar. Lo resolví con "add_fieldsets" y "restricted_fieldsets" en 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 ejemplo: http://code.djangoproject.com/svn/django/trunk/django/contrib/auth/admin.py

Pero esto no protege su modelo de cambios posteriores de "add_objX". Si desea esto también, creo que debe seguir el camino sobre la función "guardar" de la clase Modelo y verificar los cambios allí.

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

Greez, Nick

Nick Ma.
fuente