I am constantly re-inventing the readonly field for crispy forms. Part of the reason for this is I often use TimeStampedModel, and I want to show the timestamps. I also frequently include a “created_by” field. All these fields are automatically set and not meant to be edited by the user.
One solution I have considered is to include the fields, but use the Django Crispy readonly attribute. Such as:
Field('api_token', readonly=True)
I like this solution because it does not require much extra coding and it is in the style of the form. The downside is it does not work well with model select widgets (i.e. created_by). In that case it renders a readonly widget, but it is still a selected, which kind of gives the impression its not readonly. Further, it does not work with TimeStampedModel. As of Django 1.7, forms.ModelForm cannot find the added fields: “created” or “modified”.
I have tried other solutions using the Crispy HTML function to generate all the markup of a pseudo-form field. It works, but feels hacky.
Then a voice of a Stackoverflow guru that has been nagging me for a while, finally made sense. When I was googling this a long time ago, I ended up on SO and this guru commented that making readonly form fields for fields the user never can edited is bad design for a user interface. Wow – that is so true in this case. So my DRY solution was to make a template include for these fields and put it above the form. Here is the include (it’s Bootstrap):
{%if object.created %} <div class="timestamp_include row"> <div class="col-xs-3 col-xs-offset-2"><span>created: </span>{{ obj.created }}</div> <div class="col-xs-3"><span>modified: </span>{{ obj.modified }}</div> {% if obj.created_by %} <div class="col-xs-4"><span>created_by: </span> {% if obj.created_by.last_name or obj.created_by.first_name %} {{ obj.created_by.first_name }} {{ obj.created_by.last_name }} {% else %} {{ obj.created_by }} {% endif %} </div> {% endif %} </div> {% endif %}
A little CSS and problem solved.