En una forma de Django, ¿cómo hago un campo de solo lectura (o deshabilitado) para que no se pueda editar?

En una forma Django, ¿cómo puedo hacer que un campo sea de solo lectura (o deshabilitado)?

Cuando se utiliza el formulario para crear una nueva entrada, todos los campos deben estar habilitados, pero cuando la grabación está en modo de actualización, algunos campos deben ser de solo lectura.

Por ejemplo, al crear un nuevo modelo de Item , todos los campos deben ser editables, pero al actualizar el registro, ¿hay alguna forma de desactivar el campo sku para que esté visible, pero no se puede editar?

 class Item(models.Model): sku = models.CharField(max_length=50) description = models.CharField(max_length=200) added_by = models.ForeignKey(User) class ItemForm(ModelForm): class Meta: model = Item exclude = ('added_by') def new_item_view(request): if request.method == 'POST': form = ItemForm(request.POST) # Validate and save else: form = ItemForm() # Render the view 

¿ ItemForm puede reutilizar la clase ItemForm ? ¿Qué cambios serían necesarios en la clase de modelo ItemForm o Item ? ¿Tendría que escribir otra clase, ” ItemUpdateForm “, para actualizar el elemento?

 def update_item_view(request): if request.method == 'POST': form = ItemUpdateForm(request.POST) # Validate and save else: form = ItemUpdateForm() 

Como se señala en esta respuesta , Django 1.9 agregó el atributo Field.disabled :

El argumento booleano desactivado, cuando se establece en True, deshabilita un campo de formulario utilizando el atributo HTML deshabilitado para que los usuarios no lo puedan editar. Incluso si un usuario manipula el valor del campo enviado al servidor, se ignorará a favor del valor de los datos iniciales del formulario.

Con Django 1.8 y versiones anteriores, para inhabilitar la entrada en el widget y evitar los piratas informáticos POST maliciosos, debe eliminar la entrada además de configurar el atributo de readonly en el campo del formulario:

 class ItemForm(ModelForm): def __init__(self, *args, **kwargs): super(ItemForm, self).__init__(*args, **kwargs) instance = getattr(self, 'instance', None) if instance and instance.pk: self.fields['sku'].widget.attrs['readonly'] = True def clean_sku(self): instance = getattr(self, 'instance', None) if instance and instance.pk:  return instance.sku else:  return self.cleaned_data['sku'] 

O bien, reemplace if instance and instance.pk con otra condición que indique que está editando. También puede configurar el atributo disabled en el campo de entrada, en lugar de hacerlo readonly de forma readonly .

La función clean_sku asegurará que el valor de readonly no sea anulado por un POST .

De lo contrario, no hay un campo de formulario integrado de Django que represente un valor mientras se rechazan los datos de entrada enlazados. Si esto es lo que desea, en su lugar debe crear un ModelForm separado que excluya los campos no editables, y simplemente imprimirlos dentro de su plantilla.

Django 1.9 agregó el atributo Field.disabled: https://docs.djangoproject.com/en/1.9/ref/forms/fields/#disabled

El argumento booleano desactivado, cuando se establece en True, deshabilita un campo de formulario utilizando el atributo HTML deshabilitado para que los usuarios no lo puedan editar. Incluso si un usuario manipula el valor del campo enviado al servidor, se ignorará a favor del valor de los datos iniciales del formulario.

Configurar READONLY en el widget solo hace que la entrada en el navegador sea de solo lectura. Agregar un clean_sku que devuelve instance.sku asegura que el valor del campo no cambiará en el nivel del formulario.

 def clean_sku(self): if self.instance: return self.instance.sku else: return self.fields['sku'] 

De esta forma puede usar el modelo (guardar sin modificar) y aviod obteniendo el error de campo requerido.

¡La respuesta de awalker me ayudó mucho!

Cambié su ejemplo para trabajar con Django 1.3, usando get_readonly_fields .

Por lo general, debe declarar algo como esto en la app/admin.py :

 class ItemAdmin(admin.ModelAdmin): ... readonly_fields = ('url',) 

Me he adaptado de esta manera:

 # In the admin.py file class ItemAdmin(admin.ModelAdmin): ... def get_readonly_fields(self, request, obj=None): if obj: return ['url'] else: return [] 

Y funciona bien Ahora, si agrega un elemento, el campo url es de lectura-escritura, pero al cambiar se convierte en solo lectura.

Para que esto funcione para un campo ForeignKey, es necesario realizar algunos cambios. En primer lugar, la etiqueta HTML SELECCIONAR no tiene el atributo de solo lectura. Necesitamos usar disabled = “disabled” en su lugar. Sin embargo, el navegador no envía datos de formulario para ese campo. Por lo tanto, debemos establecer que ese campo no sea necesario para que el campo valide correctamente. Luego, debemos restablecer el valor nuevamente a lo que solía ser, para que no esté configurado en blanco.

Por lo tanto, para las claves externas deberá hacer algo como:

 class ItemForm(ModelForm): def __init__(self, *args, **kwargs): super(ItemForm, self).__init__(*args, **kwargs) instance = getattr(self, 'instance', None) if instance and instance.id: self.fields['sku'].required = False self.fields['sku'].widget.attrs['disabled'] = 'disabled' def clean_sku(self): # As shown in the above answer. instance = getattr(self, 'instance', None) if instance: return instance.sku else: return self.cleaned_data.get('sku', None) 

De esta forma, el navegador no permitirá que el usuario cambie el campo y siempre PUBLICARÁ como se dejó en blanco. A continuación, reemplazamos el método de limpieza para establecer que el valor del campo sea lo que originalmente estaba en la instancia.

Para Django 1.2+, puede anular el campo como sigue:

 sku = forms.CharField(widget = forms.TextInput(attrs={'readonly':'readonly'})) 

Hice una clase MixIn que puede heredar para poder agregar un campo repetible read_only que deshabilitará y seguro los campos en la edición no primera:

(Basado en las respuestas de Daniel y Muhuk)

 from django import forms from django.db.models.manager import Manager # I used this instead of lambda expression after scope problems def _get_cleaner(form, field): def clean_field(): value = getattr(form.instance, field, None) if issubclass(type(value), Manager): value = value.all() return value return clean_field class ROFormMixin(forms.BaseForm): def __init__(self, *args, **kwargs): super(ROFormMixin, self).__init__(*args, **kwargs) if hasattr(self, "read_only"): if self.instance and self.instance.pk: for field in self.read_only: self.fields[field].widget.attrs['readonly'] = "readonly" setattr(self, "clean_" + field, _get_cleaner(self, field)) # Basic usage class TestForm(AModelForm, ROFormMixin): read_only = ('sku', 'an_other_field') 

Acabo de crear el widget más simple posible para un campo de solo lectura: realmente no veo por qué los formularios ya no tienen esto:

 class ReadOnlyWidget(widgets.Widget): """Some of these values are read only - just a bit of text...""" def render(self, _, value, attrs=None): return value 

En la forma:

 my_read_only = CharField(widget=ReadOnlyWidget()) 

Muy simple, y solo me da salida. Práctico en un formset con un montón de valores de solo lectura. Por supuesto, también podría ser un poco más inteligente y darle un div con los atributos para que pueda agregar clases a él.

Me encontré con un problema similar. Parece que pude resolverlo definiendo un método “get_readonly_fields” en mi clase ModelAdmin.

Algo como esto:

 # In the admin.py file class ItemAdmin(admin.ModelAdmin): def get_readonly_display(self, request, obj=None): if obj: return ['sku'] else: return [] 

Lo bueno es que obj será Ninguno cuando está agregando un nuevo Elemento, o será el objeto que se está editando cuando está cambiando un Artículo existente.

get_readonly_display está documentado aquí: http://docs.djangoproject.com/en/1.2/ref/contrib/admin/#modeladmin-methods

Como una adición útil a la publicación de Humphrey , tuve algunos problemas con django-reversión, porque aún registraba los campos desactivados como ‘cambiados’. El siguiente código soluciona el problema.

 class ItemForm(ModelForm): def __init__(self, *args, **kwargs): super(ItemForm, self).__init__(*args, **kwargs) instance = getattr(self, 'instance', None) if instance and instance.id: self.fields['sku'].required = False self.fields['sku'].widget.attrs['disabled'] = 'disabled' def clean_sku(self): # As shown in the above answer. instance = getattr(self, 'instance', None) if instance: try: self.changed_data.remove('sku') except ValueError, e: pass return instance.sku else: return self.cleaned_data.get('sku', None) 

Como aún no puedo comentar ( la solución de Muhuk ), responderé como una respuesta separada. Este es un ejemplo de código completo, que funcionó para mí:

 def clean_sku(self): if self.instance and self.instance.pk: return self.instance.sku else: return self.cleaned_data['sku'] 

Estaba entrando en el mismo problema, así que creé un Mixin que parece funcionar para mis casos de uso.

 class ReadOnlyFieldsMixin(object): readonly_fields =() def __init__(self, *args, **kwargs): super(ReadOnlyFieldsMixin, self).__init__(*args, **kwargs) for field in (field for name, field in self.fields.iteritems() if name in self.readonly_fields): field.widget.attrs['disabled'] = 'true' field.required = False def clean(self): cleaned_data = super(ReadOnlyFieldsMixin,self).clean() for field in self.readonly_fields: cleaned_data[field] = getattr(self.instance, field) return cleaned_data 

Uso, simplemente defina cuáles deben ser de solo lectura:

 class MyFormWithReadOnlyFields(ReadOnlyFieldsMixin, MyForm): readonly_fields = ('field1', 'field2', 'fieldx') 

Una vez más, voy a ofrecer una solución más 🙂 Estaba usando el código de Humphrey , así que esto se basa en eso.

Sin embargo, me encontré con problemas con el campo siendo un ModelChoiceField. Todo funcionaría en la primera solicitud. Sin embargo, si el formset intentaba agregar un nuevo elemento y una validación fallida, algo iba mal con los formularios “existentes” donde la opción SELECTED se restablecía a la predeterminada “———“.

De todos modos, no pude encontrar la manera de solucionarlo. Entonces, en cambio, (y creo que esto es realmente más limpio en la forma), hice los campos HiddenInputField (). Esto solo significa que tienes que trabajar un poco más en la plantilla.

Entonces la solución para mí fue simplificar el formulario:

 class ItemForm(ModelForm): def __init__(self, *args, **kwargs): super(ItemForm, self).__init__(*args, **kwargs) instance = getattr(self, 'instance', None) if instance and instance.id: self.fields['sku'].widget=HiddenInput() 

Y luego en la plantilla, necesitarás hacer un bucle manual del formset .

Entonces, en este caso, harías algo como esto en la plantilla:

 
{{ form.instance.sku }} {{ form }}

Esto funcionó un poco mejor para mí y con menos manipulación de formas.

Dos enfoques más (similares) con un ejemplo generalizado:

1) primer enfoque – eliminación del campo en el método save (), p. Ej. (No probado;)):

 def save(self, *args, **kwargs): for fname in self.readonly_fields: if fname in self.cleaned_data: del self.cleaned_data[fname] return super(, self).save(*args,**kwargs) 

2) segundo enfoque – restablecer el campo al valor inicial en el método de limpieza:

 def clean_(self): return self.initial[] # or getattr(self.instance, fieldname) 

Basado en el segundo enfoque, lo generalicé así:

 from functools import partial class (...): def __init__(self, ...): ... super(, self).__init__(*args, **kwargs) ... for i, (fname, field) in enumerate(self.fields.iteritems()): if fname in self.readonly_fields: field.widget.attrs['readonly'] = "readonly" field.required = False # set clean method to reset value back clean_method_name = "clean_%s" % fname assert clean_method_name not in dir(self) setattr(self, clean_method_name, partial(self._clean_for_readonly_field, fname=fname)) def _clean_for_readonly_field(self, fname): """ will reset value to initial - nothing will be changed needs to be added dynamically - partial, see init_fields """ return self.initial[fname] # or getattr(self.instance, fieldname) 

Una opción simple es escribir form.instance.fieldName en la plantilla en lugar de form.fieldName .

si necesita campos múltiples de solo lectura. Puede usar cualquiera de los métodos que se detallan a continuación.

Método 1

 class ItemForm(ModelForm): readonly = ('sku',) def __init__(self, *arg, **kwrg): super(ItemForm, self).__init__(*arg, **kwrg) for x in self.readonly: self.fields[x].widget.attrs['disabled'] = 'disabled' def clean(self): data = super(ItemForm, self).clean() for x in self.readonly: data[x] = getattr(self.instance, x) return data 

método 2

método de herencia

 class AdvancedModelForm(ModelForm): def __init__(self, *arg, **kwrg): super(AdvancedModelForm, self).__init__(*arg, **kwrg) if hasattr(self, 'readonly'): for x in self.readonly: self.fields[x].widget.attrs['disabled'] = 'disabled' def clean(self): data = super(AdvancedModelForm, self).clean() if hasattr(self, 'readonly'): for x in self.readonly: data[x] = getattr(self.instance, x) return data class ItemForm(AdvancedModelForm): readonly = ('sku',) 

Cómo lo hago con Django 1.11:

 class ItemForm(ModelForm): disabled_fields = ('added_by',) class Meta: model = Item fields = '__all__' def __init__(self, *args, **kwargs): super(ItemForm, self).__init__(*args, **kwargs) for field in self.disabled_fields: self.fields[field].disabled = True 

Para la versión de administración, creo que esta es una forma más compacta si tiene más de un campo:

 def get_readonly_fields(self, request, obj=None): skips = ('sku', 'other_field') fields = super(ItemAdmin, self).get_readonly_fields(request, obj) if not obj: return [field for field in fields if not field in skips] return fields 

En base a la respuesta de Yamikep , encontré una solución mejor y más simple que también maneja los campos de ModelMultipleChoiceField .

La eliminación del campo de form.cleaned_data evita que los campos se guarden:

 class ReadOnlyFieldsMixin(object): readonly_fields = () def __init__(self, *args, **kwargs): super(ReadOnlyFieldsMixin, self).__init__(*args, **kwargs) for field in (field for name, field in self.fields.iteritems() if name in self.readonly_fields): field.widget.attrs['disabled'] = 'true' field.required = False def clean(self): for f in self.readonly_fields: self.cleaned_data.pop(f, None) return super(ReadOnlyFieldsMixin, self).clean() 

Uso:

 class MyFormWithReadOnlyFields(ReadOnlyFieldsMixin, MyForm): readonly_fields = ('field1', 'field2', 'fieldx') 

Aquí hay una versión un poco más complicada, basada en la respuesta de christophe31 . No se basa en el atributo “readonly”. Esto hace que sus problemas, como que los cuadros de selección sigan siendo cambiables y que los buscadores de datos sigan apareciendo, desaparezcan.

En cambio, envuelve el widget de campos de formulario en un widget de solo lectura, lo que hace que el formulario aún valide. El contenido del widget original se muestra dentro de las tags . Si el widget tiene un método render_readonly() lo usa como el texto visible, de lo contrario, analiza el HTML del widget original e intenta adivinar la mejor representación.

 import django.forms.widgets as f import xml.etree.ElementTree as etree from django.utils.safestring import mark_safe def make_readonly(form): """ Makes all fields on the form readonly and prevents it from POST hacks. """ def _get_cleaner(_form, field): def clean_field(): return getattr(_form.instance, field, None) return clean_field for field_name in form.fields.keys(): form.fields[field_name].widget = ReadOnlyWidget( initial_widget=form.fields[field_name].widget) setattr(form, "clean_" + field_name, _get_cleaner(form, field_name)) form.is_readonly = True class ReadOnlyWidget(f.Select): """ Renders the content of the initial widget in a hidden . If the initial widget has a ``render_readonly()`` method it uses that as display text, otherwise it tries to guess by parsing the html of the initial widget. """ def __init__(self, initial_widget, *args, **kwargs): self.initial_widget = initial_widget super(ReadOnlyWidget, self).__init__(*args, **kwargs) def render(self, *args, **kwargs): def guess_readonly_text(original_content): root = etree.fromstring("%s" % original_content) for element in root: if element.tag == 'input': return element.get('value') if element.tag == 'select': for option in element: if option.get('selected'): return option.text if element.tag == 'textarea': return element.text return "N/A" original_content = self.initial_widget.render(*args, **kwargs) try: readonly_text = self.initial_widget.render_readonly(*args, **kwargs) except AttributeError: readonly_text = guess_readonly_text(original_content) return mark_safe("""%s""" % ( original_content, readonly_text)) # Usage example 1. self.fields['my_field'].widget = ReadOnlyWidget(self.fields['my_field'].widget) # Usage example 2. form = MyForm() make_readonly(form) 

¿Es esta la manera más simple?

Justo en una vista, codifica algo como esto:

 def resume_edit(request, r_id): ..... r = Resume.get.object(pk=r_id) resume = ResumeModelForm(instance=r) ..... resume.fields['email'].widget.attrs['readonly'] = True ..... return render(request, 'resumes/resume.html', context) 

¡Funciona bien!

Resolví este problema así:

  class UploadFileForm(forms.ModelForm): class Meta: model = FileStorage fields = '__all__' widgets = {'patient': forms.HiddenInput()} 

en vistas:

 form = UploadFileForm(request.POST, request.FILES, instance=patient, initial={'patient': patient}) 

Es todo.

Si está utilizando el administrador de Django, esta es la solución más simple.

 class ReadonlyFieldsMixin(object): def get_readonly_fields(self, request, obj=None): if obj: return super(ReadonlyFieldsMixin, self).get_readonly_fields(request, obj) else: return tuple() class MyAdmin(ReadonlyFieldsMixin, ModelAdmin): readonly_fields = ('sku',) 

Creo que su mejor opción sería simplemente incluir el atributo de solo lectura en su plantilla presentada en un o

lugar de incluirlo en el formulario si es de solo lectura.

Los formularios son para recostackr datos, no mostrarlos. Dicho esto, las opciones para mostrar en un widget de readonly y restregar datos POST son buenas soluciones.

Para django 1.9+
Puede usar Fields disabled argument para hacer que el campo se deshabilite. Por ejemplo, en el siguiente fragmento de código del archivo forms.py, he desactivado el campo employee_code.

 class EmployeeForm(forms.ModelForm): employee_code = forms.CharField(disabled=True) class Meta: model = Employee fields = ('employee_code', 'designation', 'salary') 

Referencia https://docs.djangoproject.com/en/2.0/ref/forms/fields/#disabled

Si está trabajando con Django ver < 1.9 (el 1.9 tiene el atributo Field.disabled agregado) puede intentar agregar el siguiente decorador a su método __init__ formulario:

 def bound_data_readonly(_, initial): return initial def to_python_readonly(field): native_to_python = field.to_python def to_python_filed(_): return native_to_python(field.initial) return to_python_filed def disable_read_only_fields(init_method): def init_wrapper(*args, **kwargs): self = args[0] init_method(*args, **kwargs) for field in self.fields.values(): if field.widget.attrs.get('readonly', None): field.widget.attrs['disabled'] = True setattr(field, 'bound_data', bound_data_readonly) setattr(field, 'to_python', to_python_readonly(field)) return init_wrapper class YourForm(forms.ModelForm): @disable_read_only_fields def __init__(self, *args, **kwargs): ... 

La idea principal es que si el campo es de readonly no necesita ningún otro valor, excepto el initial .

PD: No te olvides de configurar yuor_form_field.widget.attrs['readonly'] = True