Django :: interacting with the browser

All of the Django tutorials that were around when I started learning about it focused very much on the database and models — and Django really does shine in this area. I felt there wasn’t enough documentation on how to interact with the end-user’s browser however, which is why I decided to write this. In particular I will focus on code that will go into the view.py file which would be called the controller code in a MVC framework. This code is responsible for the server-side of a form submission, i.e. selecting the widgets that are used, validating input, etc. The view code will fill a Context that’s used to render a template to generate the html that goes to the user. I will not cover any advanced features of the Django template language. In many (most) situations the view code will manipulate a database based on what the user enters on a form, generally through the database access layer Django creates from the model code. The view code doesn’t have to interact with Django model code of course, it could just as well use SQLAlchemy or SQLObject to e.g. run reports on a legacy database that Django’s database layer doesn’t support, or simply send an email. In versions prior to 0.95, Django required you to setup a database even if you were not going to use it but this is no longer the case (thanks Adrian).

In this post I’ll simply try to get a bare bones form up where a user can enter his name, his date of birth, and a message. There are probably easier ways to accomplish such a simple task, and if you look at the end-result it is quite underwhelming in it’s functionality, but I’ll remedy that in a later posting… For now I’m satisfied that when the user presses a button marked send a message is printed on the console.

Routing the url: We want the user to come to our form by going to http://www.mysite.com/suggestions/. To accomplish this we’ll have to change the urls.py file, adding the following line to the urlpatterns:

(r'^suggestions/$', 'mysite.myapp.views.suggestion_form'),

This tells Django that requests for that url should be routed to the suggest_form function in the views.py file in the myapp directory.

Next we have to subclass django.forms.Manipulator, so that we can define the widgets we want the form to have (e.g. forms.LargeTextField for the user suggestion). In views.py add the following:

from django.http import HttpResponseRedirect
from django.shortcuts import render_to_responsefrom django import forms

class SuggestionForm(forms.Manipulator):
    def __init__(self):
        self.fields = [
            forms.TextField('name', is_required=True),
            forms.DateField('dob', is_required=False),
            forms.LargeTextField('comment', is_required=True),
        ]

    def send_email(self, data):
        print 'Got name =', data['name']
        print 'Got dob  =', data['dob']
        print 'Got comment =', data['comment']

This is called a custom manipulator in the Django documentation, but if you?re like me you?ll end up writing a lot of these. This class will encapsulate much of the ?business? logic of the form, as opposed to the ?processing? logic that we?ll get to next.

The prosessing logic of a form submission goes as follows (this is going to be the same logic for pretty much all forms):

  1. if the request is a POST request, and the data validates, perform the form action and redirect the user to a result page (this prevents multiple submissions if the user hits the back button).
  2. if it is a POST request and the data doesn?t validate,display the form and notify the user of the validation errors.
  3. if it is a GET request, clear all fields and display the form

That becomes the idiomatic/boilerplate:

def suggestion_form(request):
    sform = SuggestionForm()

    if request.POST:
        # Create a copy of the form data because
        # do_html2python below works in-place.
        new_data = request.POST.copy()
        # validate the form
        errors = sform.get_validation_errors(new_data)

        if not errors:
            # convert text from form to Python objects
            sform.do_html2python(new_data)
            # and perform form action
            sform.send_email(new_data)

        return HttpResponseRedirect('thankyou')
    else:
        # it's a GET request
        errors = new_data = {}

    # FormWrapper collects the widgets for easy access through
    # Django's template language.
    fwrap = forms.FormWrapper(sform, new_data, errors)
    return render_to_response('suggestion_form.html', dict(form=fwrap))
 

Write the template: Finally it’s time to write the html. Store it in your template directory and call it suggestion_form.html:

<html>
  <head>
    <title>Comment Form</title>
    <style type="text/css">      /* some minimal layout */
      label, span.widget { display:block; float:left; }
      label { width:10em; text-align:right; margin-right:0.3em;}
      br { clear:left; }      .required { font-weight: bold; }
      span.error { font-weight:bold; color:red }
    </style>
  </head>
  <body>
    <form method="POST" action=".">
      <label for="id_name" class="required">Name</label>
      <span class="widget">{{ form.name }}</span>
      {% if form.name.errors %}
      <span class="error">{{ form.name.errors|join:", " }}</span>
      {% endif %}
      <br>
      <label for="id_dob">Birthdate</label>
      <span class="widget">{{ form.dob }}</span>
      {% if form.dob.errors %}
      <span class="error">{{ form.dob.errors|join:", " }}</span>
      {% endif %}
      <br>
      <label for="id_comment" class="required">Suggestion</label>
      <span class="widget">{{ form.comment }}</span>
      {% if form.comment.errors %}
      <span class="error">{{ form.comment.errors|join:", " }}</span>
      {% endif %}
      <br>
      <input type=submit value="Send">

      <script type="text/javascript">
        document.getElementById("id_name").focus();
      </script>
    </form>
  </body>
</html>

The little snippet of javascript puts the cursor in the first field. The id of the widget produced by the field classes are named with the prefix “id_” in front of their name and this needs to be matched up with the “for” value in the <label ...>.

To finish up, add the following to urls.py

(r'^suggestions/thankyou/$',
   'django.views.generic.simple.direct_to_template',   {'template': 'thankyou.html'}),

And add the following to c:/mysite/templates/thankyou.html

<html>
  <body>
    Thanks for your submission.
  </body>
</html>

That’s it! You should be able to start the development server and test it out by clicking here.

This entry was posted in Uncategorized. Bookmark the permalink.

Leave a Reply

Your email address will not be published. Required fields are marked *