Django and AJAX

Author: voluntas(Twisted Mind)
Title: Django and AJAX
Keywords: Django, Python, JSON, jQuery, YUI
Version: 1.0.1
License: GNU Free Document License
Date: 6 Dec 2006

Note

Python Workshop 04 presentation materials.

Contents

Django?

Python Web Framework

自分が短時間で思いついたモノをいくつか。 短時間で思いつく→現実的

魅力

利点

欠点

MTV(Model Template View)

図はありません

request -> URL Dispatcher -> Views and Models -> Template -> request

Simple is better than complex.

Django!

Install

Note

www.ymasuda.jp

Tip

Installing minimal Django on Win32 environemnt

Development Environment

Django: 0.96-pre 4150 + patch
Python: 2.5 or 2.4.4
PostgreSQL: 8.1.5

Hello, django!

 $ django-admin.py startproject workshop
 $ cd workshop
 $ python manage.py startapp firststep
 $ cp urls.py ./firststep
 $ python manage.py runserver
It worked!: http://localhost:8000/
 workshop/
   firststep/
     __init__.py
     models.py
     urls.py
     views.py
   __init__.py
   manage.py
   settings.py
   urls.py

/workshop/settings.py:

 INSTALLED_APPS = (
     'django.contrib.auth',
     'django.contrib.contenttypes',
     'django.contrib.sessions',
     'django.contrib.sites',

     'workshop.firststep',
 )

/workshop/urls.py:

 from django.conf.urls.defaults import *

 urlpatterns = patterns('',
     (r'^firststep/', include('workshop.firststep.urls')),

     # Uncomment this for admin:
 #    (r'^admin/', include('django.contrib.admin.urls')),
 )

/workshop/firststep/views.py:

 from django.http import HttpResponse

 def hello_django(request):
     return HttpResponse('Hello, django!')

/workshop/firststep/urls.py:

 from django.conf.urls.defaults import *
 from workshop.firststep.views import hello_django

 urlpatterns = patterns('',
     (r'^hello/django/$', hello_django),
 )
Hello, world!: http://localhost:8000/firststep/hello/django/

View

/workshop/firststep/urls.py:

 from django.conf.urls.defaults import *
 from workshop.firststep.views import *

 urlpatterns = patterns('',
     (r'^hello/python/$', hello_python),
     (r'^hello/ruby/$', hello_ruby),

     (r'^hello/django/$', hello_django),
 )

/workshop/firststep/views.py:

 from django.http import HttpResponse, HttpResponseRedirect

 def hello_django(request):
     return HttpResponse('Hello, django!')

 def hello_python(request):
     data = 'Simple is better than complex.'

     return HttpResponse(data)

 def hello_ruby(request):

     return HttpResponseRedirect('/firststep/hello/python/')
Hello, Python!: http://localhost:8000/firststep/hello/python/
Hello, Ruby?: http://localhost:8000/firststep/hello/ruby/

URL dispatcher

/workshop/firststep/urls.py:

 from django.conf.urls.defaults import *
 from workshop.firststep.views import *

 urlpatterns = patterns('',
     (r'^hello/django/$', hello_django),

     (r'^hello/python/$', hello_python),
     (r'^hello/ruby/$', hello_ruby),

     (r'^which/(Python|ruby)/$', python_or_ruby),
     (r'^select/(?P<language>.+)/$', select_language),
 )

/workshop/django/views.py:

 from django.http import HttpResponse, HttpResponseRedirect

 def hello_django(request):
     return HttpResponse('Hello, django!')

 def hello_python(request):
     data = 'Simple is better than complex.'

     return HttpResponse(data)

 def hello_ruby(request):

     return HttpResponseRedirect('/firststep/hello/python/')

 def python_or_ruby(request, language):
     return HttpResponse('%s is nice programing language...' % language)

 def select_language(request, language):
     return HttpResponse('%s is bad programing language...' % language)

Python? Ruby? Others?:

Python: http://localhost:8000/firststep/which/Python/
python: http://localhost:8000/firststep/which/python/
ruby: http://localhost:8000/firststep/which/ruby/
perl: http://localhost:8000/firststep/which/perl/

Any language:

Java: http://localhost:8000/firststep/select/Java/
Perl: http://localhost:8000/firststep/select/Perl/

Note

www.ymasuda.jp

Model

 $ python manage.py startapp blog
 $ cp urls.py ./blog

/workshop/blog/models.py:

 from django.db import models
 from django.contrib.auth.models import User

 class Label(models.Model):
     name = models.CharField(maxlength=128)
     created = models.DateTimeField(auto_now_add=True)

     class Admin:
         pass

 class Entry(models.Model):
     title = models.CharField(maxlength=32)
     user = models.ForeignKey(User)
     body = models.TextField()
     created = models.DateTimeField(auto_now_add=True)
     updated = models.DateTimeField(auto_now=True)
     tag = models.ManyToManyField(Label)

     class Admin:
         pass

     class Meta:
         verbose_name_plural = 'entries'

     def get_absolute_url(self):
         return '/blog/%s/' % self.id

 class Comment(models.Model):
     name = models.CharField(maxlength=32)
     url = models.URLField()
     mail = models.EmailField()
     body = models.TextField()
     created = models.DateTimeField(auto_now_add=True)
     entry = models.ForeignKey(Entry)

     class Admin:
         pass

/workshop/settings.py:

 DATABASE_ENGINE = 'postgresql_psycopg2'
 DATABASE_NAME = 'workshop' 
 DATABASE_USER = 'django'
 DATABASE_PASSWORD = 'django'

 TIME_ZONE = 'Asia/Tokyo'

 LANGUAGE_CODE = 'ja'

 INSTALLED_APPS = (
     'django.contrib.auth',
     'django.contrib.contenttypes',
     'django.contrib.sessions',
     'django.contrib.sites',

     'workshop.firststep',
     'workshop.blog',
 )
 $ python manage.py syncdb

 Creating table auth_message
 Creating table auth_group
 Creating table auth_user
 Creating table auth_permission
 Creating table django_content_type
 Creating table django_session
 Creating table django_site
 Creating table blog_comment
 Creating table blog_entry
 Creating table blog_label

 You just installed Django's auth system, \
 which means you don't have any superusers defined.
 Would you like to create one now? (yes/no): yes
 Username: admin
 E-mail address: admin@django.ja
 Password: admin
 Password (again): admin
 Superuser created successfully.
 Installing index for auth.Message model
 Installing index for auth.Permission model
 Installing index for blog.Comment model
 Installing index for blog.Entry model

django-admin.py or manage.py

 $ python manage.py sql blog
 BEGIN;
 CREATE TABLE "blog_comment" (
     "id" serial NOT NULL PRIMARY KEY,
     "name" varchar(32) NOT NULL,
     "url" varchar(200) NOT NULL,
     "mail" varchar(75) NOT NULL,
     "body" text NOT NULL,
     "created" timestamp with time zone NOT NULL,
     "entry_id" integer NOT NULL
 );
 CREATE TABLE "blog_entry" (
     "id" serial NOT NULL PRIMARY KEY,
     "title" varchar(32) NOT NULL,
     "user_id" integer NOT NULL REFERENCES "auth_user" ("id"),
     "body" text NOT NULL,
     "created" timestamp with time zone NOT NULL,
     "updated" timestamp with time zone NOT NULL
 );
 ALTER TABLE "blog_comment" ADD CONSTRAINT entry_id_refs_id_2ebcee0 FOREIGN KEY (
 "entry_id") REFERENCES "blog_entry" ("id");
 CREATE TABLE "blog_label" (
     "id" serial NOT NULL PRIMARY KEY,
     "name" varchar(128) NOT NULL,
     "created" timestamp with time zone NOT NULL
 );
 CREATE TABLE "blog_entry_tag" (
     "id" serial NOT NULL PRIMARY KEY,
     "entry_id" integer NOT NULL REFERENCES "blog_entry" ("id"),
     "label_id" integer NOT NULL REFERENCES "blog_label" ("id"),
     UNIQUE ("entry_id", "label_id")
 );
 COMMIT;
 $ python manage.py sqlclear blog
 BEGIN;
 DROP TABLE "blog_entry_tag";
 DROP TABLE "blog_label";
 ALTER TABLE "blog_comment" DROP CONSTRAINT "entry_id_refs_id_2ebcee0";
 DROP TABLE "blog_entry";
 DROP TABLE "blog_comment";
 COMMIT;

Admin

 $ python manage.py shell

 >>> from workshop.blog.models import Entry
 >>> entry = Entry(title='Today is ...', user='admin', body='etc...')
 >>> entry.save()

/workshop/settings.py:

 INSTALLED_APPS = (
     'django.contrib.auth',
     'django.contrib.contenttypes',
     'django.contrib.sites',
     'django.contrib.admin',

     'workshop.firststep',
     'workshop.blog',
 )

/workshop/urls.py:

 from django.conf.urls.defaults import *

 urlpatterns = patterns('',
     (r'^firststep/', include('workshop.firststep.urls')),

     (r'^admin/', include('django.contrib.admin.urls')),
 )
 $ python manage.py syncdb

 Creating table django_admin_log
 Installing index for admin.LogEntry model
Django administration: http://localhost:8000/admin/

/workshop/blog/models.py:

 from django.db import models
 from django.contrib.auth.models import User

 class Label(models.Model):
     name = models.CharField(maxlength=128)
     created = models.DateTimeField(auto_now_add=True)

     class Admin:
         list_display = ('name', 'created')

     def __str__(self):
         return self.name

 class Entry(models.Model):
     title = models.CharField(maxlength=32)
     user = models.ForeignKey(User)
     body = models.TextField()
     created = models.DateTimeField(auto_now_add=True)
     updated = models.DateTimeField(auto_now=True)
     tag = models.ManyToManyField(Label,
         filter_interface=models.HORIZONTAL)

     class Admin:
         list_display = ('title', 'user', 'created', 'updated')
         list_filter = ('created', 'updated')
         list_select_related = True
         search_fields = ('title', 'body')
         date_hierarchy = 'created'
         ordering = ('created', 'updated')

     class Meta:
         verbose_name_plural = 'entries'

     def __str__(self):
         return self.title

     def get_absolute_url(self):
         return '/blog/%s/' % self.id

 class Comment(models.Model):
     name = models.CharField(maxlength=32)
     url = models.URLField(null=True, blank=True)
     mail = models.EmailField(null=True, blank=True)
     body = models.TextField()
     created = models.DateTimeField(auto_now_add=True)
     entry = models.ForeignKey(Entry)

     class Admin:
         list_display = ('name', 'entry', 'created')
         list_filter = ('created', 'entry')
         search_fields = ('name', 'body')
         date_hierarchy = 'created'
         ordering = ('created', )

     def __str__(self):
         return self.name
Django administration: http://localhost:8000/admin/
 $ python manage.py shell

 >>> from workshop.blog.models import *
 >>> entries = Entry.objects.all()
 >>> entry = entries[0]
 >>> entry.user.username
 >>> entry.title

 >>> entry = Entry.objects.get(1)
 >>> entry.body
 >>> entry.created

Note

www.ymasuda.jp

Templates

 $ mkdir templates
 $ cd templates
 $ mkdir blog
 workshop/
   __init__.py
   manage.py
   settings.py
   urls.py
   firststep/
   templates/
     blog/
       base.html
       entry_detail_template.html
       entry_list_template.html
       entry_detail_filter.html
   blog/
     __init__.py
     models.py
     urls.py
     views.py

/wrokshop/settings.py:

 import os.path
 TEMPLATE_DIRS = (
     os.path.join(os.path.dirname(__file__), 'templates'),
 )

/workshop/urls.py:

 from django.conf.urls.defaults import *

 urlpatterns = patterns('',
     (r'^firststep/', include('workshop.firststep.urls')),

     (r'^blog/', include('workshop.blog.urls')),

     (r'^admin/', include('django.contrib.admin.urls')),
 )

/workshop/blog/views.py:

 from django.shortcuts import render_to_response

 from workshop.blog.models import Entry

 def entry_detail(request, entry_id):
     entry = Entry.objects.get(pk=entry_id)

     return render_to_response('blog/entry_detail_template.html',
         dict(entry=entry))

 def entry_list(request):
     entries = Entry.objects.all()

     return render_to_response('blog/entry_list_template.html',
         dict(entries=entries))

 def entry_detail_and_filter(request, entry_id):
     entry = Entry.objects.get(pk=entry_id)

     return render_to_response('blog/entry_detail_filter.html',
         dict(entry=entry))

/workshop/blog/urls.py:

 from django.conf.urls.defaults import *
 from workshop.blog.views import *

 urlpatterns = patterns('',
     (r'^(?P<entry_id>\d)/$', entry_detail),
     (r'^$', entry_list),
     (r'^filter/(?P<entry_id>\d)/$', entry_detail_and_filter)
 )

extends

/workshop/blog/views.py:

 def entry_detail(request, entry_id):
     entry = Entry.objects.get(entry_id)

     return render_to_response('blog/entry_detail_template.html',
         dict(entry=entry))

/workshop/templates/blog/base_template.html:

 <html>
   <head>
     <title>{% block title %}{% endblock %}</title>
   </head>
   <body>
     {% block contents %}
     {% endblock %}
   </body>
 </html>

/workshop/templates/blog/entry_detail_template.html:

 {% extends "blog/base_template.html" %}

 {% block title %}{{ entry.title }}{% endblock %}

 {% block contents %}
   <p>{{ entry.title }}<p>
   <p>{{ entry.body }}</p>
   <p>{{ entry.created }} {{ entry.user.username }}</p>
 {% endblock %}
Entry Detail: http://localhost:8000/blog/1/
Page not found (404): http://localhost:8000/blog/10/

tag

/workshop/blog/views.py:

 def entry_list(request):
     entries = Entry.objects.all()

     return render_to_response('blog/entry_list_template.html', 
         dict(entries=entries))

/workshop/template/blog/entry_list_template.html:

 {% extends "blog/base_template.html" %}

 {% block title %}Blog Entry List{% endblock %}

 {% block contents %}
   <b>Blog Entry List</b>
   {% if entries %}
   <ul>
     {% for entry in entries %}
     <li>
       <a href="{{ entry.get_absolute_url }}">
       {{ entry.title }} by {{ entry.user.username }}</a>
     </li>
     {% endfor %}
   </ul>
   {% endif %}

 {% endblock %}
Entry List: http://localhost:8000/blog/

filter

/workshop/blog/views.py:

 def entry_detail_and_filter(request, entry_id):
     entry = Entry.objects.get(pk=entry_id)

     return render_to_response('blog/entry_detail_filter.html',
         dict(entry=entry))

/workshop/templates/blog/entry_detail_filter.html:

 {% extends "blog/base_template.html" %}

 {% block title %}{{ entry.title }}{% endblock %}

 {% block contents %}
   <p>{{ entry.title }}<p>
   <p>{{ entry.body|linebreaksbr }}</p>
   <p>{{ entry.created|date:"Y/m/d H:i:s" }}
   {{ entry.user.username|upper }}</p>
 {% endblock %}
Entry Detail and Filter: http://localhost:8000/blog/filter/1/
Entry Detail not Filter: http://localhost:8000/blog/1/

Note

www.ymasuda.jp

Generic Views

simple

 from django.conf.urls.defaults import *
 from django.views.generic import simple

 urlpatterns = patterns('',
     (r'^hello/ruby/$', simple.direct_to,
         dict(url='/firststep/hello/python/')),
 )

date_base

list_detail

 from django.conf.urls.defaults import *
 from django.views.generic import object_detail

 from workshop.blog.models import Entry

 queryset = dict(queryset=Entry.objects.all())
 urlpatterns = patterns('',
     (r'^(?P<object_id>\d)/$', list_detail.object_detail, queryset),
 )
 from django.conf.urls.defaults import *
 from django.views.generic import object_detail

 from workshop.blog.models import Entry

 queryset = dict(queryset=Entry.objects.all())
 urlpatterns = patterns('',
     (r'^$', list_detail.object_list, queryset),
 )

create_update

 from django.conf.urls.defaults import *
 from django.views.generic import create_update

 from workshop.blog.models import Entry

 model = dict(model=Entry)
 urlpatterns = patterns('',
     (r'^create/$', create_update.object_create, model),
 )
 from django.conf.urls.defaults import *
 from django.views.generic import create_update

 from workshop.blog.models import Entry

 model = dict(model=Entry)
 urlpatterns = patterns('',
     (r'^update/(?P<object_id>\d+)/$', create_update.object_update, model),
 )

Note

www.ymasuda.jp

Django and AJAX?

Blog

 workshop/
   __init__.py
   manage.py
   settings.py
   urls.py
   templates/
     blog/
       base.html
       entry_detail.html
       entry_list.html
       entry_detail_filter.html
   firststep/
   blog/
     __init__.py
     urls.py
     views.py
     models.py

Tip

www.ymasuda.jp

/workshop/blog/urls.py:

 from django.conf.urls.defaults import *

 from workshop.blog.models import Entry
 from workshop.blog.views import *

 entry_queryset = dict(queryset=Entry.objects.all())

 urlpatterns = patterns('',
     (r'^(?P<entry_id>\d+)/ajax/xml/$', entry_ajax),
     (r'^(?P<entry_id>\d+)/ajaj/json/$', entry_ajaj),

     (r'^(xml|json)/$', entry_list_api),
 )

/workshop/blog/views.py:

 from django.http import HttpResponse
 from django.core import serializers
 from django.utils import simplejson

 from workshop.blog.models import Entry, Comment

 def entry_ajax(request, entry_id):
     entry = Entry.objects.filter(id=entry_id)
     data = serializers.serialize('xml', entry)  

     return HttpResponse(data, mimetype='text/plain')  
 #    return HttpResponse(data, mimetype='appllication/xml')  

 def entry_ajaj(request, entry_id):
     entry = Entry.objects.filter(id=entry_id)
     data = serializers.serialize('json', entry, ensure_ascii=False)  

     return HttpResponse(data, mimetype='text/plain')  
 #    return HttpResponse(data, mimetype='appllication/json')  

 def entry_list_api(request, output):
     entries = Entry.objects.all()

     if output == 'json':
         data = serializers.serialize(output, entries, ensure_ascii=False)
     else:
         data = serializers.serialize(output, entries)

     return HttpResponse(data, mimetype='text/plain')  
 #    return HttpResponse(data, mimetype='appllication/%s' % output)
 $ python manage.py syncdb

Serializers

/workshop/bbs/views.py:

 from django.http import HttpResponse
 from django.core import serializers

 def serialize_sample(request, type):
     queryset = Models.objects.all()
     data = serializers.serialize(type, queryset)

     return HttpResponse(data, mimetype='application/%s' % type)

Warning

日本人は何も気にせずに ensure_ascii=False を設定しましょう。

Note

www.ymasuda.jp

XML

/workshop/blog/views.py:

 def entry_ajax(request, entry_id):
     entry = Entry.objects.filter(id=entry_id)
     data = serializers.serialize('xml', entry)  

     return HttpResponse(data, mimetype='text/plain')  
 #    return HttpResponse(data, mimetype='appllication/xml')

/workshop/blog/urls.py:

 (r'^(?P<entry_id>\d+)/ajax/xml/$', entry_ajax),
xml: http://localhost:8000/blog/1/ajax/xml/
 <?xml version="1.0" encoding="utf-8"?>
 <django-objects version="1.0">
   <object pk="1" model="blog.entry">
     <field type="CharField" name="title">ルースカップリング</field>
     <field to="auth.user" name="user" rel="ManyToOneRel">1</field>
     <field type="TextField" name="body">Django のスタックが目指す基本的なゴールはルースカップリングとタイトコヒージョンの実現にあります.フレームワークの様々なレイヤは,本当に必要な場合を除き,お互いの事情を知らなくてもよいという考え方です.
       例えば,テンプレートシステムは Web リクエストがどのようなものか関知せず,データベースレイヤはデータをどう表示するかに関知せず,ビューシステムはプログラマがどんなテンプレートシステムを使うかに関知しません.
       利便性のため, Django には全てのスタックがついてきますが,スタックの各部分は可能な限り互いに独立になっています.</field>
     <field type="DateTimeField" name="created">2006-11-30 23:06:08</field>
     <field type="DateTimeField" name="updated">2006-11-30 23:06:08</field>
   </object>
 </django-objects>

JSON

/workshop/blog/views.py:

 def entry_ajaj(request, entry_id):
     entry = Entry.objects.filter(id=entry_id)
     data = serializers.serialize('json', entry, ensure_ascii=False)  

     return HttpResponse(data, mimetype='text/plain')  
 #    return HttpResponse(data, mimetype='appllication/json')

/workshop/blog/urls.py:

 (r'^(?P<entry_id>\d+)/ajaj/json/$', entry_ajaj),
json: http://localhost:8000/blog/1/ajaj/json/
 [
   {
     "pk": "1",
     "model": "blog.entry",
     "fields":
       {
         "body": "Django のスタックが目指す基本的なゴールはルースカップリングとタイトコヒージョンの実現にあります.フレームワークの様々なレイヤは,本当に必要な場合を除き,お互いの事情を知らなくてもよいという考え方です.\r\n\r\n例えば,テンプレートシステムは Web リクエストがどのようなものか関知せず,データベースレイヤはデータをどう表示するかに関知せず,ビューシステムはプログラマがどんなテンプレートシステムを使うかに関知しません.\r\n\r\n利便性のため, Django には全てのスタックがついてきますが,スタックの各部分は可能な限り互いに独立になっています.",
         "updated": "2006-11-30 23:06:08",
         "created": "2006-11-30 23:06:08",
         "user": 1,
         "title": "ルースカップリング"
       }
   }
 ]

Tip

www.ymasuda.jp

JSON and XML

/workshop/blog/views.py:

 def entry_list_api(request, output):
     entries = Entry.objects.all()

     if output == 'json':
         data = serializers.serialize(output, entries, ensure_ascii=False)
     else:
         data = serializers.serialize(output, entries)

     return HttpResponse(data, mimetype='text/plain')  
 #    return HttpResponse(data, mimetype='appllication/%s' % output)

/workshop/blog/urls.py:

 (r'^(xml|json)/$', entry_list_api),
json or xml: http://localhost:8000/blog/xml/
json or xml: http://localhost:8000/blog/json/

Django and JSON

simplejson

/workshop/blog/urls.py:

 (r'^(?P<entry_id>\d+)/comments/json/$', comment_list_json),

/workshop/blog/views.py:

 def comment_list_json(request, entry_id):
     comments = Comment.objects.filter(entry__id=entry_id)
     data = [dict(name=c.name, url=c.url, body=c.body) for c in comments]
     values = simplejson.dumps(data, ensure_ascii=False) 

     return HttpResponse(values, mimetype='text/plain')
 #    return HttpResponse(simplejson.dumps(data), mimetype='application/json')

Note

A simple, fast, extensible JSON encoder and decoder

return json: http://localhost:8000/blog/1/comments/json/
 [
   {
     "body": "models はパッケージではなくモジュールになったので,models の設計は,パッケージ中に複数のモジュールファイルを置いてモデル実装を収められるような設計から,単一のモジュールを使ったよりフラットな設計に有利になりました.",
     "url": "",
     "name": "ymasuda"
   },
   {
     "body": "django.views.generic.simple モジュールには,簡単なビューが入っていて,ビューロジックの必要がないときのレンダリングと,リダイレクトの発行というよくある二つのケースを処理できるようになっています:",
     "url": "http://www.ymasuda.jp/",
     "name": "ymasuda"
   }
 ]

Django and AJAX?

 $ mkdir static/js
 workshop/
   __init__.py
   manage.py
   settings.py
   urls.py
   templates/
     blog/
       base.html
       entry_list_jquery.html
       entry_list_yui.html
       entry_detail.html
   firststep/
   blog/
     __init__.py
     urls.py
     views.py
     models.py
   static/
     css/
     js/
       yui/
       yui-ext/
       jquery/

/workshop/settings.py:

 MEDIA_ROOT = os.path.join(os.path.dirname(__file__), 'static')

/workshop/urls.py:

 from django.conf.urls.defaults import *
 from django.conf import settings

 urlpatterns = patterns('',
     (r'^firststep/', include('workshop.firststep.urls')),
     (r'^blog/', include('workshop.blog.urls')),

     (r'^admin/', include('django.contrib.admin.urls')),
 )

 if settings.DEBUG:
     urlpatterns += patterns('',
         (r'^static/(?P<path>.*)$', 'django.views.static.serve',
          dict(document_root=settings.MEDIA_ROOT)),
     )

/workshop/blog/urls.py:

 from django.conf.urls.defaults import *
 from django.views.generic import list_detail

 from workshop.blog.models import Entry
 from workshop.blog.views import *

 entry_queryset = dict(queryset=Entry.objects.all())

 urlpatterns = patterns('',
     # Generic Views
     (r'^jquery/$', list_detail.object_list,
         dict(entry_queryset, template_name="blog/entry_list_jquery.html")),
     (r'^yui/$', list_detail.object_list,
         dict(entry_queryset, template_name="blog/entry_list_yui.html")),

     (r'^(?P<object_id>\d+)/$', list_detail.object_detail, entry_queryset),

     (r'^(?P<entry_id>\d+)/ajax/xml/$', entry_ajax),
     (r'^(?P<entry_id>\d+)/ajaj/json/$', entry_ajaj),

     (r'^(xml|json)/$', entry_list_api),
 )

/workshop/templates/blog/base.html:

 {% comment %}
 vim: syntax=htmldjango
 {% endcomment %}

 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
     "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="ja" lang="ja">
   <head>
     <meta http-equiv="Content-type" content="text/html; charset=utf-8" />
     <meta http-equiv="Content-Language" content="ja" />
     <title>{% block title %}{% endblock %}</title>
     {% block css %}
     {% endblock %}

     {% block importjs %}
     {% endblock %}

     {% block js %}
     {% endblock %}
   </head>
   <body>
     {% block body %}
     {% endblock %}
   </body>
 </html>

jQuery

$.getJSON:

/workshop/templates/blog/entry_list_jquery.html:

 {% comment %}
 vim: syntax=htmldjango
 {% endcomment %}

 {% extends "blog/base.html" %}

 {% block title %}Django and jQuery and JSON{% endblock %}

 {% block importjs %}
 <script type="text/javascript"
   src="/static/js/jquery/jquery.js"></script>
 {% comment %} 
 <script type="text/javascript"
   src="/static/js/jquery/jquery.pack.js"></script>
 {% endcomment %}
 {% endblock %}

 {% block js %}
 <script language="javaScript" type="text/javascript">
 $(function(){
   $.getJSON("/blog/json/", function(json){
     $.each(json, function(t){
       $("#" + this.pk).html(this.fields.body);
     });
   });
 });
 var getEntry = function(id){
   $("#" + id).toggle("slow");   
 }
 </script>
 {% endblock %}

 {% block body %}

 {% if object_list %}
 {% for object in object_list %}
 <p>
   <a href="#"
     onClick="getEntry({{ object.id }})">{{ object.title }}</a>
   <div id="{{ object.id }}" style="display: none;"></div>
 </p>
 {% endfor %}
 {% endif %}

 {% endblock %}
jQuery and JSON: http://localhost:8000/blog/jquery/

Note

New Wave Javascrpit

YUI(Yahoo! User Interface)

YAHOO.util.Connect.asyncRequest:

/workshop/blog/urls.py:

 (r'^(?P<entry_id>\d+)/comments/json/$', comment_list_json),
 (r'^post/comment/$', post_comment),

/workshop/blog/views.py:

 def comment_list_json(request, entry_id):
     comments = Comment.objects.filter(entry__id=entry_id)
     data = dict(comments=[dict(id=str(c.id), author=c.name,
         date=c.created.strftime("%Y/%m/%d %H:%M:%S"),
             link=c.url, text=c.body) for c in comments])

     values = simplejson.dumps(data, ensure_ascii=False) 

     return HttpResponse(values, mimetype='text/plain')
 #    return HttpResponse(values, mimetype='application/json')

 def post_comment(request):
     if request.method == 'POST':
         new_data = request.POST.copy()
         name = new_data['author']
         url = new_data['url']
         body = new_data['comment']
         entry_id = new_data['postId']
         mail = new_data['email']

         e = Entry.objects.get(pk=entry_id)
         c = Comment(name=name, url=url, body=body, mail=mail, entry=e)
         c.save()

         data = dict(comments=[dict(id=str(c.id), author=c.name,
             date=c.created.strftime("%Y/%m/%d %H:%M:%S"),
                 link=c.url, text=c.body)])

         value = simplejson.dumps(data, ensure_ascii=False) 

         return HttpResponse(value, mimetype='text/plain')
     #    return HttpResponse(value, mimetype='application/json')

     return HttpResponse(value, mimetype='text/plain')

/workshop/templates/blog/entry_detail.html:

 {% comment %}
 vim: syntax=htmldjango ts=8 sw=2 sts=2
 {% endcomment %}

 {% extends "blog/base.html" %}

 {% block title %}{{ object.title }}{% endblock %}

 {% block importcss %}
 <link rel="stylesheet" type="text/css"
   href="/static/css/yui-ext/resources/css/reset-min.css" />
 <link rel="stylesheet" type="text/css"
   href="/static/css/yui-ext/resources/css/resizable.css" />
 <link rel="stylesheet" type="text/css"
   href="/static/css/yui-ext/resources/css/tabs.css" />
 <link rel="stylesheet" type="text/css"
   href="/static/css/yui-ext/resources/css/basic-dialog.css" />
 <link rel="stylesheet" type="text/css"
   href="/static/css/yui-ext/resources/css/post.css" />
 {% endblock %}

 {% block importjs %}
 <script type="text/javascript"
   src="/static/js/yui/utilities/utilities.js"></script>
 <script type="text/javascript"
   src="/static/js/yui-ext/yui-ext.js"></script>
 {% endblock %}

 {% block js %}
 <script language="javaScript" type="text/javascript">
 var Comments = function(){
   var dialog, postLink, viewLink, txtComment;
   var tabs, commentsList, postBtn, renderer;
   var wait, error, errorMsg;
   var posting = false;

   return {
     init : function(){
       txtComment = getEl('comment');
       commentsList = getEl('comments-list');
       postLink = getEl('post-comment');
       viewLink = getEl('view-comments');
       wait = getEl('post-wait');
       error = getEl('post-error');
       errorMsg = getEl('post-error-msg');

       this.createDialog();

       postLink.addHandler('click', true, function(){
          tabs.activate('post-tab');
          dialog.show(postLink);
       });

       viewLink.addHandler('click', true, function(){
          tabs.activate('view-tab');
          dialog.show(viewLink);
       });      
     },

     submitComment : function(){
       postBtn.disable();
       wait.radioClass('active-msg');
       var formObj = document.getElementById('comment-form')
       var spam = YAHOO.util.Connect.setForm(formObj);

       var commentSuccess = function(o){
         postBtn.enable();
         var data = renderer.parse(o.responseText);
         if(data){
           wait.removeClass('active-msg');
           renderer.append(data.comments[0]);
           dialog.hide();
         }else{
           error.radioClass('active-msg');
           errorMsg.update(o.responseText);
         }
       };

       var commentFailure = function(o){
         postBtn.enable();
         error.radioClass('active-msg');
         errorMsg.update('Unable to connect.');
       };

       YAHOO.util.Connect.asyncRequest('post', '/blog/post/comment/',
         {success: commentSuccess, failure: commentFailure}, spam);     
     },

     createDialog : function(){
       dialog = new YAHOO.ext.BasicDialog("comments-dlg", {
           autoTabs:true,
           width:500,
           height:400,
           shadow:true,
           minWidth:300,
           minHeight:300
       });
       dialog.addKeyListener(27, dialog.hide, dialog);
       dialog.addButton('Close', dialog.hide, dialog);
       postBtn = dialog.addButton('Post', this.submitComment, this);

       dialog.on('hide', function(){
         wait.removeClass('active-msg');
         error.removeClass('active-msg');
         txtComment.dom.value = '';
       });

       tabs = dialog.getTabs();

       var sizeTextBox = function(){
         txtComment.setSize(dialog.size.width-44, dialog.size.height-264);
       };
       sizeTextBox();
       dialog.on('resize', sizeTextBox);

       tabs.on('tabchange', function(panel, tab){
         postBtn.setVisible(tab.id == 'post-tab');
       });

       renderer = new CommentRenderer(commentsList);
       var um = commentsList.getUpdateManager();
       um.setRenderer(renderer);

       var commentsLoaded = false;
       tabs.getTab('view-tab').on('activate', function(){
         if(!commentsLoaded){
           um.update('/blog/{{ object.id }}/comments/json/');
           commentsLoaded = true;
         }
       });
     }
   };
 }();

 var CommentRenderer = function(list){
   var tpl = new YAHOO.ext.DomHelper.Template(
       '<li id="comment-{id}">' +
       '<div class="cheader">' +
       '<div class="cuser">{author}:</div>' +
       '<div class="commentmetadata">{date}</div>' +
       '</div>{text}</li>'
   );

   this.parse = function(json){
     try{
       return eval('(' + json + ')');
     }catch(e){}
     return null;
   };

   this.render = function(el, response){
     var data = this.parse(response.responseText);
     if(!data || !data.comments || data.comments.length < 1){
       el.update('There are no comments for this post.');
       return;
     }
     el.update('');
     for(var i = 0, len = data.comments.length; i < len; i++){
       this.append(data.comments[i]);
     }
   };

   this.append = function(data){
     tpl.append(list.dom, data);
   };
 };
 YAHOO.ext.EventManager.onDocumentReady(Comments.init, Comments, true);
 </script>
 {% endblock %}

 {% block body %}
 <p>
   <h2>{{ object.title }}</h2>
   <p>{{ object.body|linebreaksbr }}</p>
   <div id="comment-box">
     <ul>
       <li><a id="post-comment" href="#">Post Comment</a></li>
       <li><a id="view-comments" href="#">View Comments</a></li>
     </ul>
   </div>
 </p>
 <!-- comments dialog -->
 <div id="comments-dlg" style="visibility:hidden;">
   <div class="ydlg-hd">Comments</div>
   <div class="ydlg-bd">
     <div id="post-tab" class="ydlg-tab" title="Post Comment">
       <div class="inner-tab">
         <form action="" method="post" id="comment-form" onsubmit="return false;">
         <input id="postId" type="hidden" name="postId" value="{{ object.id }}" />
         <p>
         <label for="author"><small>Name</small></label>
         <input class="textinput" type="text"
           name="author" id="author" value="" size="22" tabindex="1" />
         </p>
         <p><label for="email"><small>E-mail (will not be published)</small></label>
         <input class="textinput" type="text"
           name="email" id="email" value="" size="22" tabindex="2" />
         </p>

         <p><label for="url"><small>Website</small></label>
         <input class="textinput" type="text"
           name="url" id="url" value="" size="22" tabindex="3" />
         </p>
         <p>
         <label for="comment"><small>Comment</small></label>
         <textarea name="comment" id="comment" tabindex="4" cols="40" rows="10"></textarea>
         </p>
         </form>
       </div>
     </div>
     <div id="view-tab" class="ydlg-tab" title="View Comments">
       <ul id="comments-list" class="inner-tab">
       </ul>
     </div>
   </div>
   <div class="ydlg-ft">
     <div id="dlg-msg">
       <span id="post-error" class="posting-msg">
       <img src="/static/images/warning.gif"
       width="16" height="16" align="absmiddle" />
       &nbsp;<span id="post-error-msg"></span></span>
       <span id="post-wait" class="posting-msg">
       <img src="/static/css/yui-ext/resources/images/grid/loading.gif"
       width="16" height="16" align="absmiddle" />
       &nbsp;Posting Comment...</span>
     </div>
   </div>
 </div>
 {% endblock %}
YUI and JSON: http://localhost:8000/blog/yui/

Note

Yahoo! User Interface Library

et cetera

Windows

Windowsで時間がおかしいのを修正するパッチ:

Timezone bug in development server using a Windows environment

http://code.djangoproject.com/ticket/2315

Japanese Validator

日本語用のバリデータサンプルです:

 from django.core import validators
 from django.conf import settings
 import re

 zen_num = re.compile(u'[\uFF10-\uFF19]')
 zen_kana = re.compile(u'[\u30A1-\u30F6]')
 zen_hira = re.compile(u'[\u3041-\u3093]')

 han_kana = re.compile(u'[\uFF61-\uFF9F]')
 def isNotIncludeHalfWidthKatakana(field_data, all_data):
     from_data = unicode(field_data, settings.DEFAULT_CHARSET)
     if han_kana.search(from_data) is not None:
         raise validators.ValidationError(_('Half-width Kana characters'
                                            'are not allowed.'))