Advanced Django

Simon Willison
http://simonwillison.net/
PyCon UK, 8th September 2007
Today’s topics

  Unit testing
  newforms
  Ajax
  And if we have time... OpenID
Unit testing
Hard core TDD
 Test Driven Development as taught by Kent
 Beck
 “Write new code only if an automated test has
 failed”
 Re...
“     I don’t do test driven
  development. I do stupidity
driven testing... I wait until I do
 something stupid, and then...
What NOT to do

 No matter how tough the deadline, don't let
 your test suite start breaking and say “I’ll fix it
 later”
 ...
Testing in Django
  Testing web apps is HARD, but Django helps out
  with a bunch of features:
   Fixtures
   Doctests
   ...
Doctests
  Used by Django for both ORM testing and
  generated documentation
  You are encouraged to add them to your own
...
>>>   p = Person(name=quot;Bobquot;, dob=date(1980, 1, 1))
>>>   p.age(date(1980, 1, 1))
0
>>>   p.age(date(1979, 1, 1))
-...
class Person(models.Model):
    quot;quot;quot;
    ... tests here ...
    quot;quot;quot;
    name = models.CharField(max...
$ python manage.py test

...F
======================================================================
FAIL: Doctest: people...
def age(self, age=False):
    if not age:
        age = date.today()
    delta = age - self.dob
    return int(math.floor(...
File quot;/home/simon/Projects/Django/oscon07/peopleage/models.pyquot;, line 16,
in peopleage.models.Person
Failed example...
def age(self, age=False):
    if not age:
        age = date.today()
    years = age.year - self.dob.year
    this_year_bi...
Fixtures

  It’s useful to be able to clear and populate your
  test database in between tests
  Fixtures let you do that ...
Denormalisation
“
Normalised data is
   for sissies


                   ”
          Cal Henderson
A forum, where each thread can have one
or more replies

Maintain a separate counter in the Forum
table of number of repli...
class Thread(models.Model):
    subject = models.CharField(maxlength=100)
    num_replies = models.IntegerField(default=0)...
[{ quot;modelquot;: quot;forum.threadquot;,
    quot;pkquot;: quot;1quot;,
    quot;fieldsquot;: {
      quot;num_repliesq...
from django.test import TestCase
from models import Thread, Reply

class ThreadCountTestCase(TestCase):
    fixtures = ['t...
======================================================
FAIL: test_add_reply (forum.tests.ThreadCountTestCase)
------------...
class Reply(models.Model):
    ...
    def save(self):
        super(Reply, self).save()
        self.thread.num_replies =...
.......
-----------------------------------------------------------
Ran 7 tests in 0.372s

OK
Testing views

  Django’s TestClient lets you simulate a browser
  interacting with your site
  It also provides hooks in ...
from django.test import TestCase

class RegistrationTest(TestCase):
    def test_slash(self):
        response = self.clie...
def test_signup_done_page(self):
    self.assertEqual(len(mail.outbox), 0)
    data = {
        'email': 'simon@example.co...
More on testing with Django:
http://www.djangoproject.com/documentation/testing/
newforms
The perfect form
  Display a form
  User fills it in and submits it
  Validate their entered data
    If errors, redisplay ...
Manipulators
Manipulators
 newforms
Forms are declarative
from django import newforms as forms

class UserProfileForm(forms.Form):
    name = forms.CharField(...
A form instance can...

  validate a set of data against itself

  render itself (and its widgets)

  re-render itself wit...
Simple form handling view
 def create_profile(request):
     if request.method == 'POST':
         form = UserProfileForm(...
Initial data

    form = UserProfileForm(
        initial = {
            'name': 'Simon Willison',
            'email': '...
Simple template
<style type=quot;text/cssquot;>
ul.errorlist { color: red; }
</style>

...
<form action=quot;/profile/crea...
Output
<form action=quot;/profile/create/quot; method=quot;POSTquot;>
<p><label for=quot;id_namequot;>Name:</label> <input...
Output
<form action=quot;/profile/create/quot; method=quot;POSTquot;>
<p><label for=quot;id_namequot;>Name:</label> <input...
Output
<form action=quot;/profile/create/quot; method=quot;POSTquot;>
<p><label for=quot;id_namequot;>Name:</label> <input...
Custom validation
from django import newforms as forms
from django.newforms.util import ValidationError

class UserProfile...
Custom validation
from django import newforms as forms
from django.newforms.util import ValidationError

class UserProfile...
Custom rendering
<ol class=quot;formItems longFormquot;>
     <li{% if form.email.errors %} class=quot;errorsquot;{% endif...
Model shortcuts
  DRY: You’ve already declared your models; you
  shouldn’t have to repeat yourself in your forms
  UserFo...
Full documentation:
http://www.djangoproject.com/documentation/newforms/

    django/trunk/tests/regressiontests/forms/tes...
Ajax
First things first
  If you're going to do Ajax, you need a JavaScript
  library
  You could use Yet Another XMLHttpRequest...
.. and jQuery

  I'm going to be using jQuery
    Almost everything is done in terms of CSS
    selectors and chained meth...
Ajax formats...

  Django has great support for any and every Ajax
  format
    HTML fragments
    XML
    JSON
Username available?
from django.contrib.auth.models import User

def check_username(request):
    reply = quot;quot;
    u...
jQuery('span#msg').load(
    '/check_username/?username=' + input.val()
);
$('span#msg').load(
    '/check_username/?username=' + input.val()
);
var input = $('input#id_username')
input.keyup(function() {
    $('span#msg').load(
        '/check_username/?username=' +...
$(document).ready(function() {
    var input = $('input#id_username')
    input.keyup(function() {
        $('span#msg').l...
$(function() {
    var input = $('input#id_username')
    input.keyup(function() {
        $('span#msg').load(
           ...
Recycling server-side
  form validation
from django import newforms as forms

class ContactForm(forms.Form):
    subject = forms.CharField(max_length=100)
    mes...
def validate_contact(request):
    quot;Validate post data, return errors as jsonquot;
    form = ContactForm(request.POST...
from django.utils import simplejson

class JsonResponse(HttpResponse):
    def __init__(self, data):
        HttpResponse....
function validateInput(input) {
      $.post('/contact/validate/?field=' + input.attr('id').replace('id_', ''),
          ...
function relatedErrorList(input) {
     var prevUL = $(input).parent().prev();
     if (prevUL && prevUL.attr('class') == ...
Django philosophy

  Django often gets marked down in “framework
  comparisons” due to the lack of built in Ajax
  support...
(bonus section)
What is OpenID?
OpenID is a decentralised
mechanism for Single Sign On
An OpenID is a URL

  http://simonwillison.myopenid.com/
  http://simonwillison.net/
  http://swillison.livejournal.com/
 ...
How it works

  You enter your OpenID on a site (instead of the
  usual username and password)
  It redirects you back to ...
Simple registration
(an optional but useful extension)
Consumers can also ask...

   Your preferred username
   Your e-mail address
   Your first and last name
   Your date of bi...
How do you use
OpenID in a Django
   application?
The hard way
  Use the JanRain OpenID library
  Pretty much a reference implementation for the
  OpenID spec
  Well writte...
The easy way

  Use django-openid
  A simple wrapper around JanRain
  A middleware component, some models and a
  few pre-...
Installation
  Add 'django_openidconsumer' to your
  INSTALLED_APPS setting
  manage.py syncdb
  Add the OpenIDMiddleware ...
In urls.py
  ...
  (r'^openid/$',
  'django_openidconsumer.views.begin'),
  (r'^openid/complete/$',
  'django_openidconsum...
request.openid

  The middleware adds an openid property to the
  Django request object
  If the user is not signed in, th...
def example_view(request):
    if request.openid:
        return HttpResponse(quot;OpenID is %squot; % escape(request.open...
request.openids

  The module supports users signing in with more
  than one OpenID at a time
   request.openids provides ...
For simple registration

(r'^openid/$', 'django_openidconsumer.views.begin', {
    'sreg': 'email,nickname'
}),

def examp...
Coming soon

 django_openidauth, providing tools to associate
 OpenIDs with existing django.contrib.auth
 accounts
 django...
More information

   http://openid.net/
     Also home to the OpenID mailing lists
   http://www.openidenabled.com/
   htt...
Thank you!
Upcoming SlideShare
Loading in...5
×

Advanced Django

66,438

Published on

One hour tutorial at PyCon UK 2007. Material from OSCON, with an extra section on newforms.

Published in: Business, Technology
2 Comments
89 Likes
Statistics
Notes
  • the unit testing makes me think about myself, yep, I do that kind stupid driven test too. seems that for agile development it's great. nice ppt
       Reply 
    Are you sure you want to  Yes  No
    Your message goes here
  • 正在学习中,希望你看得懂中文啦!!
       Reply 
    Are you sure you want to  Yes  No
    Your message goes here
No Downloads
Views
Total Views
66,438
On Slideshare
0
From Embeds
0
Number of Embeds
8
Actions
Shares
0
Downloads
2,726
Comments
2
Likes
89
Embeds 0
No embeds

No notes for slide

Advanced Django

  1. 1. Advanced Django Simon Willison http://simonwillison.net/ PyCon UK, 8th September 2007
  2. 2. Today’s topics Unit testing newforms Ajax And if we have time... OpenID
  3. 3. Unit testing
  4. 4. Hard core TDD Test Driven Development as taught by Kent Beck “Write new code only if an automated test has failed” Revolutionises the way you write code Pretty hard to adopt
  5. 5. “ I don’t do test driven development. I do stupidity driven testing... I wait until I do something stupid, and then write ” tests to avoid doing it again. Titus Brown
  6. 6. What NOT to do No matter how tough the deadline, don't let your test suite start breaking and say “I’ll fix it later” This will hurt. A lot.
  7. 7. Testing in Django Testing web apps is HARD, but Django helps out with a bunch of features: Fixtures Doctests Test Client E-mail capture
  8. 8. Doctests Used by Django for both ORM testing and generated documentation You are encouraged to add them to your own models.py manage.py test to execute them Great for regression tests - just copy and paste from an interpreter session
  9. 9. >>> p = Person(name=quot;Bobquot;, dob=date(1980, 1, 1)) >>> p.age(date(1980, 1, 1)) 0 >>> p.age(date(1979, 1, 1)) -1 >>> p.age(date(1981, 6, 1)) 1 >>> p.age(date(2000, 1, 1)) 20 >>> p.age(date(1999, 12, 31)) 19 >>> p2 = Person(name=quot;Barryquot;, dob=date(1981, 5, 5)) >>> p2.age(date(1981, 5, 5)) 0 >>> p2.age(date(1982, 5, 4)) 0 >>> p2.age(date(1982, 5, 5)) 1 >>> p2.age(date(1982, 5, 6)) 1
  10. 10. class Person(models.Model): quot;quot;quot; ... tests here ... quot;quot;quot; name = models.CharField(maxlength=100) dob = models.DateField() def age(self, age=False): return 1
  11. 11. $ python manage.py test ...F ====================================================================== FAIL: Doctest: peopleage.models.Person ---------------------------------------------------------------------- Traceback (most recent call last): File quot;/usr/lib/python2.5/site-packages/django/test/_doctest.pyquot;, line 2161, in runTest raise self.failureException(self.format_failure(new.getvalue())) AssertionError: Failed doctest test for peopleage.models.Person File quot;/home/simon/Projects/Django/oscon07/peopleage/models.pyquot;, line 4, in Person ---------------------------------------------------------------------- File quot;/home/simon/Projects/Django/oscon07/peopleage/models.pyquot;, line 7, in peopleage.models.Person Failed example: p.age(date(1980, 1, 1)) Expected: 0 Got: 1
  12. 12. def age(self, age=False): if not age: age = date.today() delta = age - self.dob return int(math.floor(delta.days / float(365)))
  13. 13. File quot;/home/simon/Projects/Django/oscon07/peopleage/models.pyquot;, line 16, in peopleage.models.Person Failed example: p.age(date(1999, 12, 31)) Expected: 19 Got: 20
  14. 14. def age(self, age=False): if not age: age = date.today() years = age.year - self.dob.year this_year_birthday = self.dob.replace(year=age.year) birthday_has_passed = age >= this_year_birthday if not birthday_has_passed: years = years - 1 return years
  15. 15. Fixtures It’s useful to be able to clear and populate your test database in between tests Fixtures let you do that (they also let you populate your database with real data when you deploy your application)
  16. 16. Denormalisation
  17. 17. “ Normalised data is for sissies ” Cal Henderson
  18. 18. A forum, where each thread can have one or more replies Maintain a separate counter in the Forum table of number of replies, to speed up queries
  19. 19. class Thread(models.Model): subject = models.CharField(maxlength=100) num_replies = models.IntegerField(default=0) class Reply(models.Model): thread = models.ForeignKey(Thread) message = models.TextField()
  20. 20. [{ quot;modelquot;: quot;forum.threadquot;, quot;pkquot;: quot;1quot;, quot;fieldsquot;: { quot;num_repliesquot;: 0, quot;subjectquot;: quot;First threadquot; } },{ quot;modelquot;: quot;forum.threadquot;, quot;pkquot;: quot;2quot;, quot;fieldsquot;: { quot;num_repliesquot;: 1, quot;subjectquot;: quot;Second threadquot; } },{ quot;modelquot;: quot;forum.replyquot;, quot;pkquot;: quot;1quot;, quot;fieldsquot;: { quot;threadquot;: 2, quot;messagequot;: quot;First post!1quot; } }]
  21. 21. from django.test import TestCase from models import Thread, Reply class ThreadCountTestCase(TestCase): fixtures = ['threadcount.json'] def test_add_reply(self): thread = Thread.objects.get(pk=2) self.assertEqual(thread.num_replies, 1) thread.reply_set.create(message=quot;Another postquot;) thread = Thread.objects.get(pk=2) self.assertEqual(thread.reply_set.count(), 2) self.assertEqual(thread.num_replies, 2) def test_delete_reply(self): thread = Thread.objects.get(pk=2) self.assertEqual(thread.num_replies, 1) Reply.objects.get(pk=1).delete() thread = Thread.objects.get(pk=2) self.assertEqual(thread.reply_set.count(), 0) self.assertEqual(thread.num_replies, 0)
  22. 22. ====================================================== FAIL: test_add_reply (forum.tests.ThreadCountTestCase) ---------------------------------------------------------------------- Traceback (most recent call last): File quot;/home/simon/Projects/Django/oscon07/forum/tests.pyquot;, line 16, in test_add_reply self.assertEqual(thread.num_replies, 2) AssertionError: 1 != 2 ====================================================== FAIL: test_delete_reply (forum.tests.ThreadCountTestCase) ---------------------------------------------------------------------- Traceback (most recent call last): File quot;/home/simon/Projects/Django/oscon07/forum/tests.pyquot;, line 23, in test_delete_reply self.assertEqual(thread.num_replies, 0) AssertionError: 1 != 0 ----------------------------------------------------------------------
  23. 23. class Reply(models.Model): ... def save(self): super(Reply, self).save() self.thread.num_replies = self.thread.reply_set.count() self.thread.save() def delete(self): super(Reply, self).delete() self.thread.num_replies = self.thread.reply_set.count() self.thread.save()
  24. 24. ....... ----------------------------------------------------------- Ran 7 tests in 0.372s OK
  25. 25. Testing views Django’s TestClient lets you simulate a browser interacting with your site It also provides hooks in to the underlying application, so you can test against the template and context that was used to generate a page
  26. 26. from django.test import TestCase class RegistrationTest(TestCase): def test_slash(self): response = self.client.get('/register') self.assertEqual(response.status_code, 301) response = self.client.get('/register/') self.assertEqual(response.status_code, 200) def test_register(self): response = self.client.get('/register/') self.assertEqual(response.template[0].name, 'register.html') self.assertTemplateUsed(response, 'register.html')
  27. 27. def test_signup_done_page(self): self.assertEqual(len(mail.outbox), 0) data = { 'email': 'simon@example.com', 'username': 'example', 'firstname': 'Example', 'lastname': 'User', 'password': 'password1', 'password2': 'password1', } response = self.client.post('/signup/', data) self.assertEquals(response.status_code, 302) self.assertEquals(response['Location'], '/welcome/') # Check that the confirmation e-mail was sent self.assertEqual(len(mail.outbox), 1) sent = mail.outbox[0] self.assertEqual(sent.subject, 'Welcome to example.com')
  28. 28. More on testing with Django: http://www.djangoproject.com/documentation/testing/
  29. 29. newforms
  30. 30. The perfect form Display a form User fills it in and submits it Validate their entered data If errors, redisplay form with previously entered data and contextual error messages Continue until their submission is valid Convert submission to appropriate Python types
  31. 31. Manipulators
  32. 32. Manipulators newforms
  33. 33. Forms are declarative from django import newforms as forms class UserProfileForm(forms.Form): name = forms.CharField(max_length=100) email = forms.EmailField() bio = forms.CharField(widget=forms.Textarea) dob = forms.DateField(required=False) receive_newsletter = forms.BooleanField(required=False)
  34. 34. A form instance can... validate a set of data against itself render itself (and its widgets) re-render itself with errors convert to Python types
  35. 35. Simple form handling view def create_profile(request): if request.method == 'POST': form = UserProfileForm(request.POST) if form.is_valid(): # ... save the user’s profile return HttpResponseRedirect('/profile/saved/') else: form = UserProfileForm() return render_to_response('profile.html', {'form': form})
  36. 36. Initial data form = UserProfileForm( initial = { 'name': 'Simon Willison', 'email': 'simon@simonwillison.net', } )
  37. 37. Simple template <style type=quot;text/cssquot;> ul.errorlist { color: red; } </style> ... <form action=quot;/profile/create/quot; method=quot;POSTquot;> {{ form.as_p }} <input type=quot;submitquot; value=quot;Submitquot; /> </form> ...
  38. 38. Output <form action=quot;/profile/create/quot; method=quot;POSTquot;> <p><label for=quot;id_namequot;>Name:</label> <input id=quot;id_namequot; type=quot;textquot; name=quot;namequot; maxlength=quot;100quot; /></p> <p><label for=quot;id_emailquot;>Email:</label> <input type=quot;textquot; name=quot;emailquot; id=quot;id_emailquot; /></p> <p><label for=quot;id_bioquot;>Bio:</label> <textarea id=quot;id_bioquot; rows=quot;10quot; cols=quot;40quot; name=quot;bioquot;></textarea></p> <p><label for=quot;id_dobquot;>Dob:</label> <input type=quot;textquot; name=quot;dobquot; id=quot;id_dobquot; /></p> <p><label for=quot;id_receive_newsletterquot;>Receive newsletter:</label> <input type=quot;checkboxquot; name=quot;receive_newsletterquot; id=quot;id_receive_newsletterquot; /></p> <input type=quot;submitquot; value=quot;Submitquot; /> </form>
  39. 39. Output <form action=quot;/profile/create/quot; method=quot;POSTquot;> <p><label for=quot;id_namequot;>Name:</label> <input id=quot;id_namequot; type=quot;textquot; name=quot;namequot; maxlength=quot;100quot; /></p> <p><label for=quot;id_emailquot;>Email:</label> <input type=quot;textquot; name=quot;emailquot; id=quot;id_emailquot; /></p> <p><label for=quot;id_bioquot;>Bio:</label> <textarea id=quot;id_bioquot; rows=quot;10quot; cols=quot;40quot; name=quot;bioquot;></textarea></p> <p><label for=quot;id_dobquot;>Dob:</label> <input type=quot;textquot; name=quot;dobquot; id=quot;id_dobquot; /></p> <p><label for=quot;id_receive_newsletterquot;>Receive newsletter:</label> <input type=quot;checkboxquot; name=quot;receive_newsletterquot; id=quot;id_receive_newsletterquot; /></p> <input type=quot;submitquot; value=quot;Submitquot; /> </form>
  40. 40. Output <form action=quot;/profile/create/quot; method=quot;POSTquot;> <p><label for=quot;id_namequot;>Name:</label> <input id=quot;id_namequot; type=quot;textquot; name=quot;namequot; maxlength=quot;100quot; /></p> <p><label for=quot;id_emailquot;>Email:</label> <input type=quot;textquot; name=quot;emailquot; id=quot;id_emailquot; /></p> <p><label for=quot;id_bioquot;>Bio:</label> <textarea id=quot;id_bioquot; rows=quot;10quot; cols=quot;40quot; name=quot;bioquot;></textarea></p> <p><label for=quot;id_dobquot;>Dob:</label> <input type=quot;textquot; name=quot;dobquot; id=quot;id_dobquot; /></p> <p><label for=quot;id_receive_newsletterquot;>Receive newsletter:</label> <input type=quot;checkboxquot; name=quot;receive_newsletterquot; id=quot;id_receive_newsletterquot; /></p> <input type=quot;submitquot; value=quot;Submitquot; /> </form>
  41. 41. Custom validation from django import newforms as forms from django.newforms.util import ValidationError class UserProfileForm(forms.Form): name = forms.CharField(max_length=100) email = forms.EmailField() bio = forms.CharField(widget=forms.Textarea) dob = forms.DateField(required=False) receive_newsletter = forms.BooleanField(required=False) def clean_email(self): if self.cleaned_data['email'].split('@')[1] == 'hotmail.com': raise ValidationError, quot;No hotmail.com emails, please.quot;
  42. 42. Custom validation from django import newforms as forms from django.newforms.util import ValidationError class UserProfileForm(forms.Form): name = forms.CharField(max_length=100) email = forms.EmailField() bio = forms.CharField(widget=forms.Textarea) dob = forms.DateField(required=False) receive_newsletter = forms.BooleanField(required=False) def clean_email(self): if self.cleaned_data['email'].split('@')[1] == 'hotmail.com': raise ValidationError, quot;No hotmail.com emails, please.quot;
  43. 43. Custom rendering <ol class=quot;formItems longFormquot;> <li{% if form.email.errors %} class=quot;errorsquot;{% endif %}> <label for=quot;id_emailquot;>Email: </label> {{ form.email }} {{ form.email.errors }} <p class=quot;infoquot;>Your e-mail address.</p> </li> ... </ol>
  44. 44. Model shortcuts DRY: You’ve already declared your models; you shouldn’t have to repeat yourself in your forms UserForm = form_for_model(User) ############################### page = Page.objects.get(pk=1) PageForm = form_for_instance(page) form = PageForm(request.POST) ... if form.is_valid(): form.save()
  45. 45. Full documentation: http://www.djangoproject.com/documentation/newforms/ django/trunk/tests/regressiontests/forms/tests.py
  46. 46. Ajax
  47. 47. First things first If you're going to do Ajax, you need a JavaScript library You could use Yet Another XMLHttpRequest abstraction... but the popular libraries offer fantastic convenience Good libraries include YUI, Dojo, MochiKit and (controversial) Prototype...
  48. 48. .. and jQuery I'm going to be using jQuery Almost everything is done in terms of CSS selectors and chained methods It looks like a gimmick, but it isn't http://simonwillison.net/2007/Aug/15/jquery/
  49. 49. Ajax formats... Django has great support for any and every Ajax format HTML fragments XML JSON
  50. 50. Username available? from django.contrib.auth.models import User def check_username(request): reply = quot;quot; username = request.GET.get('username', '') if username: if User.objects.filter(username=username).count(): reply = 'Unavailable' else: reply = 'Available' return HttpResponse(reply)
  51. 51. jQuery('span#msg').load( '/check_username/?username=' + input.val() );
  52. 52. $('span#msg').load( '/check_username/?username=' + input.val() );
  53. 53. var input = $('input#id_username') input.keyup(function() { $('span#msg').load( '/check_username/?username=' + input.val() ); });
  54. 54. $(document).ready(function() { var input = $('input#id_username') input.keyup(function() { $('span#msg').load( '/check_username/?username=' + input.val() ); }); });
  55. 55. $(function() { var input = $('input#id_username') input.keyup(function() { $('span#msg').load( '/check_username/?username=' + input.val() ); }); });
  56. 56. Recycling server-side form validation
  57. 57. from django import newforms as forms class ContactForm(forms.Form): subject = forms.CharField(max_length=100) message = forms.CharField(widget=forms.Textarea()) sender = forms.EmailField()
  58. 58. def validate_contact(request): quot;Validate post data, return errors as jsonquot; form = ContactForm(request.POST) if (request.GET.has_key('field')): # Validate a single field errors = form.errors[request.GET['field']] else: errors = form.errors return JsonResponse({ 'valid': not errors, 'errors': errors })
  59. 59. from django.utils import simplejson class JsonResponse(HttpResponse): def __init__(self, data): HttpResponse.__init__( self, simplejson.dumps(data), mimetype='application/json' )
  60. 60. function validateInput(input) { $.post('/contact/validate/?field=' + input.attr('id').replace('id_', ''), $('form').formToArray(), function(data) { var json = eval('(' + data + ')'); showErrors(input, json.errors); } ); } $(function() { $(':input').blur(function() { validateInput($(this)); }); });
  61. 61. function relatedErrorList(input) { var prevUL = $(input).parent().prev(); if (prevUL && prevUL.attr('class') == 'errorlist') { return prevUL; } var errorlist = $('<ul class=quot;errorlistquot;></ul>'); input.parent().before(errorlist); return errorlist; } function showErrors(input, errors) { var errorlist = relatedErrorList(input); errorlist.empty(); $.each(errors, function(i, error) { errorlist.append('<li>' + error + '</li>'); }); }
  62. 62. Django philosophy Django often gets marked down in “framework comparisons” due to the lack of built in Ajax support Personally I think that shipping without a recommended library is a feature, not a bug
  63. 63. (bonus section)
  64. 64. What is OpenID?
  65. 65. OpenID is a decentralised mechanism for Single Sign On
  66. 66. An OpenID is a URL http://simonwillison.myopenid.com/ http://simonwillison.net/ http://swillison.livejournal.com/ http://openid.aol.com/simonwillison
  67. 67. How it works You enter your OpenID on a site (instead of the usual username and password) It redirects you back to your OpenID provider They authenticate you in some way They redirect you back to the original site
  68. 68. Simple registration (an optional but useful extension)
  69. 69. Consumers can also ask... Your preferred username Your e-mail address Your first and last name Your date of birth Your language, country and timezone
  70. 70. How do you use OpenID in a Django application?
  71. 71. The hard way Use the JanRain OpenID library Pretty much a reference implementation for the OpenID spec Well written, well tested but takes a while to get the hang of www.openidenabled.com/openid/libraries/python/
  72. 72. The easy way Use django-openid A simple wrapper around JanRain A middleware component, some models and a few pre-written views http://code.google.com/p/django-openid/
  73. 73. Installation Add 'django_openidconsumer' to your INSTALLED_APPS setting manage.py syncdb Add the OpenIDMiddleware to your MIDDLEWARE_CLASSES setting Add the views to your URLconf
  74. 74. In urls.py ... (r'^openid/$', 'django_openidconsumer.views.begin'), (r'^openid/complete/$', 'django_openidconsumer.views.complete'), (r'^openid/signout/$', 'django_openidconsumer.views.signout'), ...
  75. 75. request.openid The middleware adds an openid property to the Django request object If the user is not signed in, this will be None Otherwise, it will be an OpenID object; the str() representation will be the OpenID (or use request.openid.openid)
  76. 76. def example_view(request): if request.openid: return HttpResponse(quot;OpenID is %squot; % escape(request.openid)) else: return HttpResponse(quot;No OpenIDquot;)
  77. 77. request.openids The module supports users signing in with more than one OpenID at a time request.openids provides a list of all authenticated OpenIDs request.openid merely returns the most recent from this list
  78. 78. For simple registration (r'^openid/$', 'django_openidconsumer.views.begin', { 'sreg': 'email,nickname' }), def example_sreg(request): if request.openid and request.openid.sreg.has_key('email'): return HttpResponse(quot;Your e-mail address is: %squot; % escape( request.openid.sreg['email'] )) else: return HttpResponse(quot;No e-mail addressquot;)
  79. 79. Coming soon django_openidauth, providing tools to associate OpenIDs with existing django.contrib.auth accounts django_openidserver, to make it easy to provide OpenIDs for users of your Django application
  80. 80. More information http://openid.net/ Also home to the OpenID mailing lists http://www.openidenabled.com/ http://simonwillison.net/tags/openid/ http://code.google.com/p/django-openid/
  81. 81. Thank you!
  1. A particular slide catching your eye?

    Clipping is a handy way to collect important slides you want to go back to later.

×