0

I wrote this contact form with some help from the Django docs:

def contact(request):
    if request.method == 'POST':
        form = ContactForm(request.POST)
        if form.is_valid():
            name = form.cleaned_data['name']
            subject = form.cleaned_data['subject']
            message = form.cleaned_data['message']
            sender = form.cleaned_data['sender']
            cc_myself = form.cleaned_data['cc_myself']

            recipients = ['[email protected]']
            if cc_myself:
                recipients.append(sender)

            m = '%s\n\n-----------------\n%s' % (message, name)
            from django.core.mail import send_mail
            send_mail(subject, m, sender, recipients)
            return HttpResponseRedirect('/kontakt/tack/')
    else:
        form = ContactForm()

    return render(request, 'contact/index.html', {
        'form': form,
    })

The problem is that if the form doesn't validate it returns an empty form. That could be a big annoyance for someone that have written a long message but forgot to put his name in or something.

I've fixed that by change the the code below to last else: to:

    try:
        form = ContactForm(form.cleaned_data)
    except UnboundLocalError:
        form = ContactForm()

I needed to try-statement for the first rendering of the page when no form.cleaned_data yet exist. This works, but it seems like a rather ugly hack.

Is there some standard way to use the text from previous fill in when re-rendering the form without my ugly try-except solution?

Template on request

{% block content %}
      <div id="contact-generic-container">
        <p>Lorem ipsum (not really, but ain't relevant).</p>
      </div> <!-- #contact-generic-container -->
      <div id="contact-form" class="clearfix">
    <br/>
    <h2>Kontakta oss!</h2>
    <form action="/kontakt/" method="post">
      {{ form.non_field_errors }}
      <fieldset class="text">
        {% csrf_token %}
            <div class="field-wrapper">
          <label for="id_name">Namn:</label>
        {{ form.name }}
                {{ form.name.errors }}
        </div>
            <div class="field-wrapper">
          <label for="id_sender">E-post:</label> 
        {{ form.sender }}
                {{ form.sender.errors }}
        </div>
            <div class="field-wrapper">
          <label for="id_subject">Ämne:</label> 
        {{ form.subject }}
                {{ form.subject.errors }}
        </div>
      </fieldset>
      <fieldset class="text">
          <div class="field-wrapper">
        <label for="id_message">Meddelande:</label><br/>
        {{ form.message }}
            {{ form.message.errors }}
      </div>
          <div class="field-wrapper">
        <label for="id_cc_myself">Kopia till mig själv:</label> 
        {{ form.cc_myself }}
            {{ form.cc_myself.errors }}
      </div>
      <input type="submit" value="Skicka" />
      <fieldset class="text">
    </form>
      </div>
{% endblock %}
4
  • Your view seems correct and form should have previous data. What is your template? Commented Mar 14, 2013 at 9:17
  • Yes, agree with Rohan that this looks fine. One possibility is indentation: are you sure that the else is lined up with the first if? Have you checked tabs/spaces? Commented Mar 14, 2013 at 9:56
  • Now I've went from a solution that worked but looked hackish, to being confused. If I render the template the first time, of course it shouldn't have any form data, or should it??? Putting up my template but don't see why that is relevant Commented Mar 14, 2013 at 10:40
  • views.py didn't have any tabs (but the template file obviously had, that's why it looks strange) Commented Mar 14, 2013 at 10:46

1 Answer 1

2

Here's a simple way to always either init the form with the POST data, or nothing:

def contact(request):
    form = ContactForm(request.POST or None)
    if request.POST and form.is_valid():

Then remove your else block.

The form will always have data in it, and won't throw an error if it's just a GET.

Since I'm using some Python trickery, allow me to explain.

  1. Python supports and/or operator in expressions.
  2. When evaluting an or python will stop when the first value returns a truthy value.
  3. When evaluating an and, Python will stop if the first condition returns false.

So, when we init the form, we ask Python to assign either the value of request.POST (if it's true), or None, if request.POST is false (which it will be if it's empty). This inits the form with the values from request.POST correctly when there's a POST, but None otherwise.

In our if statement, if request.POST is false (which it will be if request.POST is empty), Python stops there and never calls form.is_valid(), so we don't have to worry about validation running if the POST isn't present.

Neat huh?

Sign up to request clarification or add additional context in comments.

4 Comments

This is not relevant to the question at all.
If his 'fix' was to hack his else statement, that means the form is getting lost somewhere due to some issue or another. So my posted fix should work by pulling the init out of the if block. The rest is just fun and helpful. So I'd say its relevant.
I don't have time to look into it right now and I have some questions come up in the comments on my question. But checking it our later. Thanks :)
Give my code a shot, minus your else. Based on your expanded answer above, there's no reason it shouldn't work. I'm not 100% sure why, but the request.method == 'POST' thing weirds me out.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.