The cleanest way is to create a new base form class that has a readonlyfields meta
option, and does the rest of the work for you.
You shouldn't have any of that logic in the template, but rather validate data in the view, and put let django render readonly input as a span widget
.
I use this in production with great success.
class SpanWidget(forms.Widget):
'''Renders a value wrapped in a <span> tag.
Requires use of specific form support. (see ReadonlyForm
or ReadonlyModelForm)
'''
def render(self, name, value, attrs=None):
final_attrs = self.build_attrs(attrs, name=name)
return mark_safe(u'<span%s >%s</span>' % (
forms.util.flatatt(final_attrs), self.display_value))
def value_from_datadict(self, data, files, name):
return self.original_value
class SpanField(forms.Field):
'''A field which renders a value wrapped in a <span> tag.
Requires use of specific form support. (see ReadonlyForm
or ReadonlyModelForm)
'''
def __init__(self, *args, **kwargs):
kwargs['widget'] = kwargs.get('widget', SpanWidget)
super(SpanField, self).__init__(*args, **kwargs)
class Readonly(object):
'''Base class for ReadonlyForm and ReadonlyModelForm which provides
the meat of the features described in the docstings for those classes.
'''
class NewMeta:
readonly = tuple()
def __init__(self, *args, **kwargs):
super(Readonly, self).__init__(*args, **kwargs)
readonly = self.NewMeta.readonly
if not readonly:
return
for name, field in self.fields.items():
if name in readonly:
field.widget = SpanWidget()
elif not isinstance(field, SpanField):
continue
model_field = self.instance._meta.get_field_by_name(name)[0]
field.widget.original_value = model_field.value_from_object(self.instance)
field.widget.display_value = unicode(getattr(self.instance, name))
class ReadonlyForm(Readonly, forms.Form):
'''A form which provides the ability to specify certain fields as
readonly, meaning that they will display their value as text wrapped
with a <span> tag. The user is unable to edit them, and they are
protected from POST data insertion attacks.
The recommended usage is to place a NewMeta inner class on the
form, with a readonly attribute which is a list or tuple of fields,
similar to the fields and exclude attributes on the Meta inner class.
class MyForm(ReadonlyForm):
foo = forms.TextField()
class NewMeta:
readonly = ('foo',)
'''
pass
class ReadonlyModelForm(Readonly, forms.ModelForm):
'''A ModelForm which provides the ability to specify certain fields as
readonly, meaning that they will display their value as text wrapped
with a <span> tag. The user is unable to edit them, and they are
protected from POST data insertion attacks.
The recommended usage is to place a NewMeta inner class on the
form, with a readonly attribute which is a list or tuple of fields,
similar to the fields and exclude attributes on the Meta inner class.
class Foo(models.Model):
bar = models.CharField(max_length=24)
class MyForm(ReadonlyModelForm):
class Meta:
model = Foo
class NewMeta:
readonly = ('bar',)
'''
pass
This is code I use in production:
class MembershipForm(ReadonlyModelForm):
class Meta:
model = Membership
fields = ('user','board', 'privileged', 'alumni')
class NewMeta:
readonly = ('user')
def email(self):
return self.instance.user.email