django: initial or submitted values in manual Form rendering

After defining a form with initial values, we want the values to be shown to the user – but we don’t want to use Django’s built-in form rendering (.. for various reasons) – when using django templates.

So if no data has been submitted, we want to get the initial value defined for the form to show to the user, but if data has been submitted, we want to show the value submitted instead.

The first iteration was something like this:

ExportForm(self.request.GET, initial={
    'start_datetime': utcnow(),
    'end_datetime': utcnow(),
})

Since request.GET is empty, the initial value should be used instead. And if we use {{ form.start_datetime.initial }} everything looks fine. But if we use .value instead of .initial to get the resolved value for the field (the submitted or the initial value), we only get None back.

The problem occurs because we’ve actually bound the form – if the form is unbound, we get the initial value back when calling {{ form.start_datetime.value }}, but since the form has been initialized with a dictionary (self.request.GET), the form is now considered bound, even if self.request.GET is empty (i.e. no parameters has been provided through the URL – the state before a request is made).

The answer is to massage the form slightly on creation instead:

ExportForm(self.request.GET or None, initial={
    'start_datetime': utcnow(),
    'end_datetime': utcnow(),
})

The or None part means that if the GET request is empty, we send in None instead, meaning that the form won’t be considered bound. This way we can use {{ form.start_datetime.value }} in our form, and actually get either the submitted value or the initial value back.