tkbe

October 22, 2006

Django :: interacting with the browser

Filed under: django — tb @ 3:27 am

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. Click here for bare bones instructions on creating the Django project and application we'll work on.

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:

PYTHON:
  1. (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:

PYTHON:
  1. from django.http import HttpResponseRedirect
  2. from django.shortcuts import render_to_response
  3. from django import forms
  4.  
  5. class SuggestionForm(forms.Manipulator):
  6.     def __init__(self):
  7.         self.fields = [
  8.             forms.TextField('name', is_required=True),
  9.             forms.DateField('dob', is_required=False),
  10.             forms.LargeTextField('comment', is_required=True),
  11.             ]
  12.        
  13.     def send_email(self, data):
  14.         print 'Got name =', data['name']
  15.         print 'Got dob  =', data['dob']
  16.         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:

PYTHON:
  1. def suggestion_form(request):
  2.     sform = SuggestionForm()
  3.    
  4.     if request.POST:
  5.         # Create a copy of the form data because
  6.         # do_html2python below works in-place.
  7.         new_data = request.POST.copy()
  8.         # validate the form
  9.         errors = sform.get_validation_errors(new_data)
  10.        
  11.         if not errors:
  12.             # convert text from form to Python objects
  13.             sform.do_html2python(new_data)
  14.             # and perform form action
  15.             sform.send_email(new_data)
  16.            
  17.             return HttpResponseRedirect('thankyou')
  18.     else:
  19.         # it's a GET request
  20.         errors = new_data = {}
  21.        
  22.     # FormWrapper collects the widgets for easy access through
  23.     # Django's template language.
  24.     fwrap = forms.FormWrapper(sform, new_data, errors)
  25.     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:
  1.     <title>Comment Form</title>
  2.     <style type="text/css">
  3.       /* some minimal layout */
  4.       label, span.widget { display:block; float:left; }
  5.       label { width:10em; text-align:right; margin-right:0.3em;}
  6.       br { clear:left; }
  7.       .required { font-weight: bold; }
  8.       span.error { font-weight:bold; color:red }
  9.     </style>
  10. </head>
  11.   <form method="POST" action=".">
  12.     <label for="id_name" class="required">Name</label>
  13.     <span class="widget">{{ form.name }}</span>
  14.       {% if form.name.errors %}
  15.          <span class="error">{{ form.name.errors|join:", " }}</span>
  16.       {% endif %}
  17.     <br>
  18.  
  19.     <label for="id_dob">Birthdate</label>
  20.     <span class="widget">{{ form.dob }}</span>
  21.       {% if form.dob.errors %}
  22.          <span class="error">{{ form.dob.errors|join:", " }}</span>
  23.       {% endif %}
  24.     <br>
  25.  
  26.     <label for="id_comment" class="required">Suggestion</label>
  27.     <span class="widget">{{ form.comment }}</span>
  28.       {% if form.comment.errors %}
  29.          <span class="error">{{ form.comment.errors|join:", " }}</span>
  30.       {% endif %}
  31.     <br>
  32.        
  33.     <input type=submit value="Send">
  34.     <script type="text/javascript">
  35.       document.getElementById("id_name").focus();
  36.     </script>
  37.   </form>
  38. </body>
  39. </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

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

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

HTML:
  1.     <body>
  2.         Thanks for your submission.
  3.     </body>
  4. </html>

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

October 21, 2006

Educators Debate Taking The Blood And Explosions Out Of Science

Filed under: Philosophy — tb @ 7:25 pm

Techdirt is hosting a misguided article about how educators are trying to take the "fun" out of High School classroms by making students do disections on a computer rather gaggin on formaldehyde while cuttin up some small (but never very furry) animal.

I find the very idea of killing animals so that kids can get a cheap thrill without much educational content quite abhorrent. There is precious little extra that can be gained from a live disection vs. a simulated one at the High School level -- and what little extra there might be can probably also be learned in the kitchen, helping a parent making dinner or by going hunting and fishing.

We ought to have enough respect for life, any life, that we don't throw it away wantonly.

Don't get me wrong though, I'm all for animal experiments when it comes to finding cures for diseases, etc. People who select this field of study must of course also be allowed training, but very few in High School have selected a field yet, so it seems natural, at least to me, to hold off on disection in the classroom until sophomore year in college.

October 9, 2006

Lorem ipsum dolor sit amet,…

Filed under: Philosophy — tb @ 2:09 pm

If you think you recognize the latin, it's probably since it's been used as filler text to demonstrate typefaces/typesetting/etc. Well, who knew that the standard filler text was based on a 45 BC text by Cicero (Roman poet and philosopher) named "The Extremes of Good and Evil ("de Finibus Bonorum et Malorum"). Go to http://www.lipsum.com/ for details (and a cool Firefox plugin), but here's the translation of Cicero's text (H.Rackham, 1914):

"But I must explain to you how all this mistaken idea of denouncing pleasure and praising pain was born and I will give you a complete account of the system, and expound the actual teachings of the great explorer of the truth, the master-builder of human happiness. No one rejects, dislikes, or avoids pleasure itself, because it is pleasure, but because those who do not know how to pursue pleasure rationally encounter consequences that are extremely painful. Nor again is there anyone who loves or pursues or desires to obtain pain of itself, because it is pain, but because occasionally circumstances occur in which toil and pain can procure him some great pleasure. To take a trivial example, which of us ever undertakes laborious physical exercise, except to obtain some advantage from it? But who has any right to find fault with a man who chooses to enjoy a pleasure that has no annoying consequences, or one who avoids a pain that produces no resultant pleasure?"

"On the other hand, we denounce with righteous indignation and dislike men who are so beguiled and demoralized by the charms of pleasure of the moment, so blinded by desire, that they cannot foresee the pain and trouble that are bound to ensue; and equal blame belongs to those who fail in their duty through weakness of will, which is the same as saying through shrinking from toil and pain. These cases are perfectly simple and easy to distinguish. In a free hour, when our power of choice is untrammelled and when nothing prevents our being able to do what we like best, every pleasure is to be welcomed and every pain avoided. But in certain circumstances and owing to the claims of duty or the obligations of business it will frequently occur that pleasures have to be repudiated and annoyances accepted. The wise man therefore always holds in these matters to this principle of selection: he rejects pleasures to secure other greater pleasures, or else he endures pains to avoid worse pains."

So advocating a utilitarian and temporal individual mapping-out of a (middle) path between asceticism and hedonism based on a fundamentally Libertarian approach. Neat-o!

« Previous PageNext Page »

Powered by WordPress