In a Django form, how do I make a field readonly (or disabled) so that it cannot be edited?

后端 未结 26 888
-上瘾入骨i
-上瘾入骨i 2020-11-22 04:09

In a Django form, how do I make a field read-only (or disabled)?

When the form is being used to create a new entry, all fields should be enabled - but when the recor

相关标签:
26条回答
  • 2020-11-22 04:12

    For Django 1.2+, you can override the field like so:

    sku = forms.CharField(widget = forms.TextInput(attrs={'readonly':'readonly'}))
    
    0 讨论(0)
  • 2020-11-22 04:12

    As a useful addition to Humphrey's post, I had some issues with django-reversion, because it still registered disabled fields as 'changed'. The following code fixes the problem.

    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)
    
    0 讨论(0)
  • 2020-11-22 04:13

    How I do it with 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
    
    0 讨论(0)
  • 2020-11-22 04:13

    I was going into the same problem so I created a Mixin that seems to work for my use cases.

    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
    

    Usage, just define which ones must be read only:

    class MyFormWithReadOnlyFields(ReadOnlyFieldsMixin, MyForm):
        readonly_fields = ('field1', 'field2', 'fieldx')
    
    0 讨论(0)
  • 2020-11-22 04:17

    Two more (similar) approaches with one generalized example:

    1) first approach - removing field in save() method, e.g. (not tested ;) ):

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

    2) second approach - reset field to initial value in clean method:

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

    Based on second approach I generalized it like this:

    from functools                 import partial
    
    class <Form-name>(...):
    
        def __init__(self, ...):
            ...
            super(<Form-name>, 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)
    
    0 讨论(0)
  • 2020-11-22 04:18

    Here is a slightly more involved version, based on christophe31's answer. It does not rely on the "readonly" attribute. This makes its problems, like select boxes still being changeable and datapickers still popping up, go away.

    Instead, it wraps the form fields widget in a readonly widget, thus making the form still validate. The content of the original widget is displayed inside <span class="hidden"></span> tags. If the widget has a render_readonly() method it uses that as the visible text, otherwise it parses the HTML of the original widget and tries to guess the best representation.

    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 <span>. 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("<span>%s</span>" % 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("""<span class="hidden">%s</span>%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)
    
    0 讨论(0)
提交回复
热议问题