2

This is an extension to this SO question.

Is there an easy way to dynamically generate the required clean_X methods to ensure that read-only form fields do not get modified? I can manually write the methods as suggested by the linked question, but the novelty wears off very quickly when you have multiple forms with many fields that need to be made read-only.

There must be a better way!

Solution: Based on the answer provided, I've decided to just override the Form.clean() method in the base class. This is the class my forms inherit from which takes a parameter read_only which is a list of instance attributes as strings.

class FormBase(forms.ModelForm):
    def __init__(self, *args, **kwargs):
        self.read_only = kwargs.pop("read_only", [])
        super(FormBase, self).__init__(*args, **kwargs)

        #Disable any read only controls
        for f in self.read_only:
            self.fields[f].widget.attrs["readonly"] = True

    def clean(self):
        cleaned_data = super(FormBase, self).clean()
        for f in self.read_only:
            cleaned_data[f] = getattr(self.instance, f)
        return cleaned_data
Community
  • 1
  • 1
CadentOrange
  • 3,263
  • 1
  • 34
  • 52

1 Answers1

4

Note that validation is not restricted to the form.clean_* methods - you can as well use form.clean() and revert cleaned_data values to your initial instance attributes values. If you define a list of readonly fields in your class (or pass it as argument or whatever), you just have to iterate over this list to reset your values.

Now you really insist on dynamically defining clean_* methods, here's and untested, overly simplified and utterly ugly possible solution:

class MyForm(...):
    readonly_fields = ("afield", "another",)
    def __init__(self, *args, **kw):
         super(MyForm, self).__init__(*args, **kw)
         for field in self.readonly_fields:
             self.fields[field].widget.attrs['readonly']
             def clean(field=field):
                 # assume fieldname == instance attribute name
                 return getattr(self.instance, field) 
             setattr(self, "clean_%s" % field, clean)

Obviously using Form.clean is easier, more readable and way less ugly.

bruno desthuilliers
  • 75,974
  • 6
  • 88
  • 118
  • I have gone with the `Form.clean` clean approach which is a lot saner and cleaner. – CadentOrange Apr 08 '13 at 16:16
  • This code will NOT work since the `clean()` method will not have a `self` variable in the scope. You should use `types.MethodType(clean, self)` to make this function a bound method. But again, even though it will be a bound method, the approach is still buggy since the `field` variable inside the `clean()` will always hold the LAST value of the `field` which was set in `for` loop. You'll end up "cleaning" the same and single field. – mennanov Jul 16 '14 at 10:09
  • 1
    @mennanov: Python's functions are closure so here `self` is provided by the `clean()` function's outer scope. Also, being set as an instance attribute, the function will not trigger the descriptor protocol, but will be executed as a plain function. Good point for spotting the flaw wrt/ `field` not being correctly bound though - I fixed the code to take care of this point. – bruno desthuilliers Jul 16 '14 at 10:42
  • Yes, you're right, we don't need `types.MethodType(clean, self)` in this case. By the way, `def clean(field=field)` is a very cool workaround! I could solve this problem only using a wrapper function: `def wrapper(field): def clean(): ... return clean setattr(self, 'clean_%s' % field, wrapper(field))` – mennanov Jul 17 '14 at 09:06