randombits
randombits

Reputation: 48450

Django creating a form field that's read only using widgets

My form field looks something like the following:

class FooForm(ModelForm):
    somefield = models.CharField(
        widget=forms.TextInput(attrs={'readonly':'readonly'})
    )

    class Meta:
        model = Foo

Geting an error like the following with the code above: init() got an unexpected keyword argument 'widget'

I thought this is a legitimate use of a form widget?

Upvotes: 18

Views: 40132

Answers (4)

googletorp
googletorp

Reputation: 33275

You should use a form field and not a model field:

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

replaced with

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

Should fix it.

Upvotes: 47

Michael
Michael

Reputation: 8758

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')

Upvotes: 6

Robert Lujo
Robert Lujo

Reputation: 16371

As Benjamin (https://stackoverflow.com/a/2359167/565525) nicely explained, additionally to rendering correctly, you need to process field on backend properly.

There is an SO question and answers that has many good solutions. But anyway:

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, fname)

Upvotes: 1

Benjamin Wohlwend
Benjamin Wohlwend

Reputation: 31828

Note that the readonly attribute does not keep Django from processing any value sent by the client. If it is important to you that the value doesn't change, no matter how creative your users are with FireBug, you need to use a more involved method, e.g. a ReadOnlyField/ReadOnlyWidget like demonstrated in a blog entry by Alex Gaynor.

Upvotes: 21

Related Questions