Using Django FormPreview the right way

1.1k views Asked by At

My Goal

I have a django project with a form, and I want to display a preview page before the user submits.

The problem

I can display a preview page using a Django FormPreview, but not all form data is displayed properly. Specifically, if I have a field with choices, the string values of these choices aren't displayed. I'm also having problems applying template filters to date fields. The end result is that some data on the preview page is visible but other data is blank:

Preview page

However, if I display the same data for posts that have actually been submitted, then everything displays properly:

Submitted page

My Code

models.py:

class Game(models.Model):

  # Game Choices
  FOOTBALL = 0
  BASKETBALL = 1
  TENNIS = 2
  OTHER = 3
  GAME_CHOICES = (
      (FOOTBALL, 'Football'),
      (BASKETBALL, 'Basketball'),
      (TENNIS, 'Tennis'),
      (OTHER, 'Other')
    )

  game_id = models.AutoField(primary_key=True)
  location = models.CharField(max_length=200, verbose_name="Location")
  game = models.IntegerField(choices=GAME_CHOICES, default=FOOTBALL)
  game_date = models.DateField(verbose_name='Game Date')

forms.py

class GameForm(ModelForm):
  class Meta:
    model = Game
    fields = (
      'location',
      'game',
      'game_date'
    )

I'm pretty sure that the problem is in my views.py: I'm not sure that I'm processing the POST request the right way to feed all data to the preview page.

views.py

def form_upload(request):
  if request.method == 'GET':
    form = GameForm()
  else:
    # A POST request: Handle Form Upload
    form = GameForm(request.POST) # Bind data from request.POST into a GameForm
    # If data is valid, proceeds to create a new game and redirect the user
    if form.is_valid():
      game = form.save()
      return render(request, 'games/success.html', {})
  return render(request, 'games/form_upload.html', {
    'form': form,
  })

preview.py

class GameFormPreview(FormPreview):

  form_template = 'games/form_upload.html'
  preview_template = 'games/preview.html'

  def done(self, request, cleaned_data):
    # Do something with the cleaned_data, then redirect
    # to a "success" page.
    return HttpResponseRedirect('/games/success')

form_upload.html

...
<form method="post">
  {% csrf_token %}
  <ul><li>{{ form.as_p }}</li></ul>

  <button type="submit">Preview your post</button>
</form>
...

preview.html

{% load humanize %}
...
<h1>Preview your submission</h1>

  <div>
    <p>Location: {{ form.data.location }}</p>
    <p>Game Date: {{ form.data.game_date|date:"l, F d, Y" }}</p>
    <p>Game Type: {{ form.data.get_game_display }}</p>
  </div>

  <div>
    <form action="{% url 'form_upload' %}" method="post">
      {% csrf_token %}
      {% for field in form %}
      {{ field.as_hidden }}
      {% endfor %}
      <input type="hidden" name="{{ stage_field }}" value="2" />
      <input type="hidden" name="{{ hash_field }}" value="{{ hash_value }}" />

      <!-- Submit button -->
      <button type="submit">Submit your post</button>
      <!-- Go back button -->
      <button type="submit">
        <a href="{% url 'form_upload' %}" 
          onClick="history.go(-1);return false;" >
          Go back and edit your post
        </a>
      </button>

      </div>

    </form>
  </div>
...

Two issues

Essentially, I'm having these two issues:

  1. String values for choices are not displayed. If I use the get_FOO_display() method in my preview.html template, it returns blank. However, if I use this in a page after the post has been submitted, it displays properly.
  2. The humanize date filter doesn't work. If I apply a humanize filter ({{ form.data.game_date|date:"l, F d, Y" }}) in preview.html, it also displays blank. Again, this works for submitted posts.

My question essentially is: what's the right way to use the FormPreview here?

1

There are 1 answers

2
Alasdair On BEST ANSWER

form.data does not have get_FOO_display attributes. When you access {{ form.data.get_game_display }} in the template, it fails silently and doesn't display anything.

The get_FOO_display are methods of the instance, so try this instead.

{{ form.instance.get_game_display }}

Wherever possible you should access data from form.cleaned_data (which is validated and 'cleaned') instead of form.data, which is the raw data submitted to the form.

The filters don't work with form.data.game_date because it's a raw string. They should work with form.cleaned_data.game_date, which has been converted to a python date object.

Finally, you haven't implemented anything in your done method, you've just copied the comment from the docs. You could create a new game using cleaned_data as follows:

def done(self, request, cleaned_data):
    game = Game.objects.create(**cleaned_data)
    return HttpResponseRedirect('/games/success')