views:

9629

answers:

4

In a django form, How to make a field readonly (or disabled) so that it cannot be edited?

When the form is being used for a new record entry, all fields are enabled, but when the record is in update mode some fields need to be readonly.

For example, in the Item model, for a new record entry, all fields are editable, but while updating the record, is there a way to disable sku field so that it is visible but cannot be edited?

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

can class ItemForm be used reused? What changes would be required in ItemForm or Item model class? Would I need to write another class : ItemUpdateForm for updating the item?

def update_item_view(request):   
    if request.method == 'POST':
        form = ItemUpdateForm(request.POST)
     #validate and save
    else:
        form = ItemUpdateForm()
+17  A: 

To disable entry on the widget, this should work:

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.attrs['readonly'] = True

Or, replace if instance and instance.id with another condition indicating you're editing. You could also set the attribute disabled on the input field, instead of readonly.

Note, however, that if you don't otherwise modify your ModelForm instance, that it's still possible to manually post the sku item to the form. You need to also override the save method to make sure you don't overwrite the sku when the user doesn't have permission to.

Otherwise - If you're not going to actually remove the field from the form - there is no Django form field which will only display as just a string, like a span, but not accept a modified value on request.POST. You should instead create a separate ModelForm that doesn't include the uneditable field(s), and just print the uneditable values inside your template.

Daniel
Daniel, Thanks for posting an answer. It is not clear to me how to use this code? wouldn't this code work for same for new as well update mode? Can you edit your answer to give examples on how to use it for new and update forms? Thanks.
Ingenutrix
The key to Daniel's example is testing the .id field. Newly created objects will have id==None. By the way, one of the oldest open Django tickets is about this issue. See http://code.djangoproject.com/ticket/342 .
Peter Rowell
Peter Thanks for the explanation.
Ingenutrix
but what about foreignkey fields.It is not for foreignkey
ha22109
this doesn't work for FileField methinks.
skyl
+19  A: 

Setting READONLY on widget only makes the input in the browser read-only. Adding a clean_sku which returns instance.sku ensures the field value will not change on form level.

def clean_sku(self):
    return self.instance.sku

This way you can use model's (unmodified save) and aviod getting the field required error.

muhuk
+1 This is a great way to avoid more complicated save() overrides. However, you'd want to do an instance check before the return (in newline-less comment mode): "if self.instance: return self.instance.sku; else: return self.fields['sku']"
Daniel
I wish I could +1000 this.
shylent
+1 Simple, great answer.
Liam
+1  A: 

The answer given by Daniel is helpful, but what if we want to set disable="true" for only one OPTION of a SELECT input field?

Good question. You may want to ask this as a separate new question so it gets attention.
Ingenutrix
+4  A: 

To make this work for a ForiegnKey field, a few changes need to be made. Firstly, the SELECT HTML tag does not have the readonly attribute. We need to use disabled="disabled" instead. However, then the broswer doesn't send any form data back for that field. So we need to set that field to not be required so that the field validates correctly. We then need to reset the value back to what it used to be so it's not set to blank.

So for foriegn keys you will need to do something like:

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)

This way the browser won't let the user change the field, and will always POST as it it was left blank. We then override the clean method to set the field's value to be what was originally in the instance.

Humphrey