เกร็ดเว็บกับไพธอน

ลิงก์ควรศึกษา

  • Web Python Tutorial มีเรื่อง cgi กับ mod_python อธิบายพร้อมยกตัวอย่างอ่านง่ายดี (แต่หน้าเว็บใช้ Drupal ;P )

การใช้ไพธอนทำเว็บ มีการใช้มอดูลแบบหลัก ๆ คือ

  • cgi มีในตัวอยู่แล้ว - ช้า กินกำลังซีพียู แต่เสถียร และเขียนง่าย
  • Mod_python ต้องลงเพิ่ม - เร็วกว่า (ตัวเลขที่น่าเชื่อ คือประมาณ 2 เท่าของ cgi สำหรับงานจริง) เขียนง่าย แต่เห็นมีคนบอกว่าไม่ค่อยเสถียร ถ้าใช้กับหลายโปรเซส
  • wsgi มีมาในไพธอนรุ่น 2.5 ถ้าใช้รุ่น 2.4 ต้องลงเพิ่ม - ไม่เด่นเรื่องเร็ว แต่เด่นเรื่องใช้งานได้หลากระบบ มึความยืดหยุ่นสูง แต่เขียนยากกว่า cgi หน่อย
  • fastcgi ต้องลงเพิ่ม - เห็นว่าเร็วมาก และเสถียร แต่มีข้อกำหนดมาก และหาเอกสารยาก
Topic: 

python: เกร็ด cgi

บันทึกเกร็ดเกี่ยวกับการใช้ไพธอนทำเว็บ ด้วย cgi

  • ต้องมีบรรทัด Content-type: text/html\r\n เป็นบรรทัดแรก apache ถึงจะตีความเป็น HTML
    #!/usr/bin/env python
    print "Content-type: text/html\r\n"
    ...
  • ควรบอกระบบว่าไฟล์เราเข้ารหัสแบบไหน เช่นถ้าเป็น utf-8
    #!/usr/bin/env python
    # -*- coding: utf8 -*-
    ...

    จาก Defining Python Source Code Encodings

  • ถ้าเราทำโปรแกรมของเราเป็นแพกเกจ หากยังไม่ได้ทำติดตั้งลงใน site-package แต่ยังเป็นเพียงไดเรกทอรี่อยู่ เวลาอิมพอร์ต ต้องใช้ทั้งสองคำสั่งคือ ทั้ง from ... import * และ import ... เช่นสมมุติแพกเกจเราชื่อ dweb ใช้
    ...
    import dweb
    from dweb import *
    ...
  • การใช้งานกับ apache2 แบบ CGI ธรรมดา พารามิเตอร์ที่ต้องใช้ในไฟล์ htaccess คือ
    $ vi .htaccess
    DirectoryIndex index.py
    Options +Indexes ExecCGI FollowSymLinks MultiViews
    AddHandler cgi-script .py
    AddType application/x-python-code .pyc .pyo
    AddHandler python-program .pyc .pyo
    Topic: 

    python: ข้อผิดพลาดของ cgi กับเนื้อหาเว็บ

    การใช้โมดูล cgi ในการสร้างเว็บ ต้องระวังเรื่องเนื้อหาในเพจให้ดี
    ให้ระวังคือ

    • อย่าให้มีแท็ก <head> หลุดเข้ามาในส่วนของ <body>
    • ระวังแท็ก <link rel="..." src="..." /> ต้องมีค่าให้เรียบร้อย ค่าใน src ห้ามมั่ว

    ไม่งั้นฟังก์ชั่น cgi.FieldStorage จะทำงานผิดพลาดไปหมด ดีบักยากด้วย เพราะแสดงผลออกมาถูก แต่การทำงานภายในผิดหมด

    Topic: 

    python: วิธีใช้งาน Cookie

    เอามาจาก ug's Python CGI scripts: cookie.py

    การเซ็ต Cookie
    สร้างไฟล์ชื่อ setcookie.py

    #!/usr/bin/env python
    import Cookie
    
    c1 = Cookie.SimpleCookie()
    # Create a cookie in c1
    # This will be temporary and will disappear when the session is closed
    c1["cracker"] = "hello"
    # The RFC says you should always set this but it seems to work ok without it
    c1["cracker"]["version"] = 1
    
    # Create another one
    c1["bisquit"] = "whatever"
    # Make the browser store it for one hour
    c1["bisquit"]["max-age"] = 3600 # Time to keep, in seconds
    c1["bisquit"]["expires"] = 3600 # Obsolete, but Netscape still seems to require it
    c1["bisquit"]["version"] = 1
    
    # Print the headers that sets the cookies
    print c1
    
    # Show html page
    print "Content-type: text/html\r\n"
    print "<h1>Cookie is set</h1>"
    

    สั่งรันจากบราวเซอร์หนึ่งครั้ง Cookie จะถูกเก็บเข้าในเครื่องของ Client

    การรับ Cookie
    สร้างไฟล์ชื่อ getcookie.py

    #!/usr/bin/env python
    import Cookie, os
    
    # Show html page
    print "Content-type: text/html\r\n"
    print "<h1>Get cookie</h1>"
    
    try:
        cookie = os.environ["HTTP_COOKIE"]
        print "HTTP_COOKIE="+cookie
        print "<p>"
        c2 = Cookie.SimpleCookie()
        c2.load(os.environ["HTTP_COOKIE"])
        print "<pre>"
        print c2
        print "</pre>"
    
        print 'c2["bisquit"].value =', c2["bisquit"].value, "<br />\n"
        print 'c2["cracker"].value =', c2["cracker"].value
    except:
        print "No cookies were received"
    

    สั่งรันจากบราวเซอร์ จะเห็นตัวแปร bisquit และ cracker ที่เราใส่ไว้

    Topic: 

    python: ตัวอย่างฟังก์ชั่น Info

    อ่าน Dive Into Python เห็นตัวอย่างฟังก์ชั่น Info
    เลยประยุกต์มาทำบนเว็บครับ

    เผื่อจะขยายไปเป็น search help

    Topic: 

    python: ทำ syntax hightlight

    ทดลองทำ syntax highlight โดยลอกจาก โมดูล GeSHiFilter ของ drupal ซึ่งเอามาจากโค๊ด PHP ที่ GeSHi อีกทีนึง แก้ปรับสี css นิดเดียว
    ทดลองดูซอร์สได้ดังนี้

    ตอนนี้ทำได้แค่ไพธอนภาษาเดียว และน่าจะยังมีบั๊กอยู่เยอะ จะค่อย ๆ ปรับปรุงไปเรื่อย ๆ ครับ

    ยังไม่รู้ว่าโค๊ดของโมดูลหลัก dweb จะเปลี่ยนแปลงไปยังไงนะครับ แต่ตอนนี้โพสต์แบบนี้ไปก่อน

    python: อ่านเนื้อความจากเว็บ

    เราสามารถใช้ python อ่านเนื้อหาจากเว็บได้โดยใช้โมดุล urllib

    เอาตัวอย่างจาก Dive into Python - 11.2. How not to fetch data over HTTP

    >>> import urllib
    >>> data = urllib.urlopen('http://diveintomark.org/xml/atom.xml').read()    1
    >>> print data
    <?xml version="1.0" encoding="iso-8859-1"?>
    <feed version="0.3"
      xmlns="http://purl.org/atom/ns#"
      xmlns:dc="http://purl.org/dc/elements/1.1/"
      xml:lang="en">
      <title mode="escaped">dive into mark</title>
      <link rel="alternate" type="text/html" href="http://diveintomark.org/"/>
      <-- rest of feed omitted for brevity -->

    ด้วยวิธีนี้เราสามารถนำเข้าไฟล์สตรีมทั้งหลายได้โดยสะดวก

    python: สร้างสคริปต์เก็บเนื้อหาเว็บเพจ

    จากตอนก่อน เราสามารถนำมาสร้างสคริปต์ง่าย ๆ เอาไว้เก็บเนื้อหาของหน้าเว็บได้ดังนี้
    $ vi d.getweb.py

    #!/usr/bin/env python
    # SAVE CONTENT OF WEB PAGE TO FILE
    import sys, os, urllib
    
    def usage(progname):
        print "Usage: %s URL FILENAME" % (progname)
        print "Save content of web page to file."
    
    
    def cannotopenfile(filename="",readwrite="r"):
        if readwrite=="r":
            msg=" reading"
        else:
            msg=" writing"
    
        print "Cannot open file %s for %s." % (filename, readwrite)
    
    
    def genfilename(filename="",ext="new"):
        if filename=="":
            return ""
        if ext.lower()!="new" and ext.lower()!="bak":
            ext="bak"
        if os.path.exists(filename+"."+ext):
            i=0
            while os.path.exists(filename+"."+ext+str(i)) and (i < 1000):
                i=i+1
            if i>999:
                return ""
            return filename+"."+ext+str(i)
        else:
            return filename+"."+ext
        #
    
    if __name__=="__main__":
        progname=os.path.basename(sys.argv[0])
        try:
            url=sys.argv[1]
            filename=sys.argv[2]
        except:
            usage(progname)
            sys.exit(1)
        #
        try:
            if os.path.exists(filename):
                bakfile=genfilename(filename,"bak")
                os.rename(filename,bakfile)
        except: 
            cannotopenfile(filename,"w")
            sys.exit(1)
        #   
        try:
            data=urllib.urlopen(url).read()
        except:
            print "Cannot open %s." % (url)
            sys.exit(1)
        #   
        f=open(filename,"w")
        f.write(data)
        f.close()
        print "Save %s to %s success." % (url, filename)

    เรียกใช้งานด้วยคำสั่ง
    ./d.getweb.py http://WEB.TO.GET FILENAME.EXT

    python: แสดงซอร์สโค๊ดด้วย demo_source.py

    จากครั้งก่อน ที่ทำตัวแจงเมธอด ขยายมาเป็นตัวค้นหาและแสดงซอร์สโค๊ด
    โดยใช้พร๊อพเพอตี้ __file__

    ปกติเราดูซอร์สได้เองอยู่แล้ว แต่การนำมาลงเว็บให้ค้นได้ง่าย ๆ อาจช่วยทำให้เข้าใจการทำงานของโมดูลในไพธอนดียิ่งขึ้นครับ

    debian: ลอง django

    ลอง django

    django: ทดลองติดตั้ง

    เที่ยวนี้เอาแบบดิบ ๆ เลย

    ติดตั้ง Django

    http://www.djangoproject.com/documentation/install/

    Install Python
    # apt-get install python
    +++ mime-support python python-minimal python2.4 python2.4-minimal

    Install Apache and mod_python
    # apt-get install apache2 libapache2-mod-python
    +++ apache2 apache2-mpm-worker apache2-utils apache2.2-common libapache2-mod-python libapr1 libaprutil1 libexpat1 libmagic1 libpcre3 libpq4 libsqlite3-0 python-central

    How to use Django with mod_python

    http://www.djangoproject.com/documentation/modpython/

    Django requires Apache 2.x and mod_python 3.x, and you should use Apache's prefork MPM, as opposed to the worker MPM.
    # apt-get install apache2-mpm-prefork
    --- apache2-mpm-worker
    +++ apache2-mpm-prefork

    Get your database running
    Django works with PostgreSQL (recommended), MySQL and SQLite.
    # apt-get install postgresql-8.1
    +++ openssl postgresql-8.1 postgresql-client-8.1 postgresql-client-common postgresql-common ssl-cert

    แก้ปัญหาการจัดเรียงภาษาไทยของ postgresql
    ตรวจ locales ให้มีภาษาไทย
    # dpkg-reconfigure locales
    <<<--- (*) th_TH TIS-620
    <<<--- (*) th_TH.UTF-8 UTF-8

    inintdb ใหม่ให้เรียงตามภาษาไทย โดยจะสร้างไดเรคทอรี่ของข้อมูลใหม่ ให้อยู่ที่ /server1/var/lib/postgresql/8.1/main
    # /etc/init.d/postgresql-8.1 stop
    # mkdir -p /server1/var/lib/postgresql/8.1/main
    # chown -R postgres:postgres /server1/var/lib/postgresql
    # su postgres
    $ /usr/lib/postgresql/8.1/bin/initdb -D /server1/var/lib/postgresql/8.1/main --locale=th_TH.UTF-8 --pgdata=/server1/var/lib/postgresql/8.1/main
    $ cd /server1/var/lib/postgresql/8.1/main
    $ ln -sf /etc/postgresql-common/root.crt .
    $ ln -sf /etc/ssl/certs/ssl-cert-snakeoil.pem server.crt
    $ ln -sf /etc/ssl/private/ssl-cert-snakeoil.key server.key
    $ exit
    # cd /etc/postgresql/8.1/main
    # rm pgdata
    # ln -sf /server1/var/lib/postgresql-8.1/main pgdata
    # /etc/init.d/postgresql-8.1 start

    ปรับให้ผู้ใช้ในระบบสามารถเข้ามาใช้งานโดยใช้รหัสผ่านของระบบ
    # vi /etc/postgresql/8.1/main/pg_hba.conf

    ...
    # TYPE  DATABASE    USER        CIDR-ADDRESS          METHOD
    host    all        all        192.168.1.0/24        md5
    ...

    สร้างผู้คุมฐานข้อมูล
    # su postgres
    $ psql template1
    template1=# CREATE USER superx SUPERUSER PASSWORD 'superx';
    template1=# \q
    $ exit

    ติดตั้ง phppgadmin
    # apt-get install phppgadmin
    +++ libapache2-mod-php4 libzzip-0-12 php4-common php4-pgsql phppgadmin wwwconfig-common

    # dpkg-reconfigure phppgadmin
    Which web server would you like to reconfigure automatically?
    <<<--- Apache2

    # vi /etc/apache2/conf.d/phppgadmin

    # deny from all
    allow from all

    # /etc/init.d/apache2 restart

    If you're using PostgreSQL, you'll need the psycopg package
    # apt-get install python-psycopg python-psycopg2
    +++ python-egenix-mxdatetime python-egenix-mxtools python-psycopg python-psycopg2

    Install the Django code

    Download Django-0.95.tar.gz from our download page.
    # cd /usr/src
    # tar xfz Django-0.95.tar.gz
    # cd Django-0.95

    Note that the last command will automatically download and install setuptools if you don't already have it installed. This requires a working Internet connection.
    # apt-get install python-setuptools
    +++ python-setuptools

    # python setup.py install

    เสร็จ Django

    เรียน Django

    http://www.sitepoint.com/article/build-to-do-list-30-minute

    จะสร้างไดเรคทอรี่ของเว็บ โดยให้ webmaster เป็นเจ้าของ
    # useradd -m -g www-data webmaster
    # su webmaster
    $ cd
    $ mkdir django
    $ cd django

    Diving In
    จะสร้างโปรเจคต์ ชื่อ gtd
    $ django-admin.py startproject gtd
    $ cd gtd

    รันเซิร์ฟเวอร์ที่พอร์ต 8000 ไอพี 192.168.1.5
    $ python manage.py runserver 192.168.1.5:8000

    ทดสอบโดย เอาบราวเซอร์ไปที่ http://192.168.1.5:8000/

    หยุดเซิร์ฟเวอร์ด้วย Ctrl-C

    จะสร้างแอปพลิเกชั่น todo
    $ python manage.py startapp todo
    $ cd todo
    $ vi models.py

    class List(models.Model):
      title = models.CharField(maxlength=250, unique=True)
      def __str__(self):
        return self.title
      class Meta:
        ordering = ['title']
      class Admin:
        pass
    
    import datetime
    
    PRIORITY_CHOICES = (
      (1, 'Low'),
      (2, 'Normal'),
      (3, 'High'),
    )
    
    class Item(models.Model):
      title = models.CharField(maxlength=250)
      created_date = models.DateTimeField(default=datetime.datetime.now)
      priority = models.IntegerField(choices=PRIORITY_CHOICES, default=2)
      completed = models.BooleanField(default=False)
      todo_list = models.ForeignKey(List)
      def __str__(self):
        return self.title
      class Meta:
        ordering = ['-priority', 'title']
      class Admin:
        pass
    

    $ cd ..

    $ su postgres
    postgres@server1$ psql template1
    template1=# CREATE DATABASE "django" WITH ENCODING='UTF8';
    template1=# \q
    postgres@server1$ exit

    $ vi settings.py

    ...
    DATABASE_ENGINE = 'postgresql'
    DATABASE_NAME = 'django'
    DATABASE_USER = 'USER1'
    DATABASE_PASSWORD = 'USER1PASSWORD'
    ...
    DATABASE_PORT = '5432'
    ...
    TIME_ZONE = 'Asia/Bangkok'
    ...
    INSTALLED_APPS = (
        'django.contrib.auth',
        'django.contrib.contenttypes',
        'django.contrib.sessions',
        'django.contrib.sites',
        'gtd.todo',
    )

    $ python manage.py syncdb
    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 (Leave blank to use 'webmaster'):
    <<<--- {DEFAULT}
    E-mail address:
    <<<--- webmaster@example.com
    Password:
    <<<--- {WEBMASTER-PASSWORD}
    Password (again):
    <<<--- {WEBMASTER-PASSWORD}

    $ vi settings.py

    ...
    INSTALLED_APPS = (
        'django.contrib.auth',
        'django.contrib.contenttypes',
        'django.contrib.sessions',
        'django.contrib.sites',
        'gtd.todo',
        'django.contrib.admin',
    )

    $ vi urls.py

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

    $ python manage.py syncdb

    เริ่มรัน
    $ python manage.py runserver 192.168.1.5:8000

    เอาบราวเซอร์ไปที่ http://192.168.1.5:8000/admin
    Username:
    <<<--- webmaster
    Password:
    <<<--- {WEBMASTER-PASSWORD}

    ลองเพิ่มผู้ใช้และกรุ๊ปดู

    Deliving into Views

    $ cd todo
    $ vi views.py

    ...
    from django.shortcuts import render_to_response
    from gtd.todo.models import List
    
    def status_report(request):
     todo_listing = []
     for todo_list in List.objects.all():
       todo_dict = {}
       todo_dict['list_object'] = todo_list
       todo_dict['item_count'] = todo_list.item_set.count()
       todo_dict['items_complete'] = todo_list.item_set.filter(completed=True).count()
       todo_dict['percent_complete'] = int(float(todo_dict['items_complete']) / todo_dict['item_count'] * 100)
       todo_listing.append(todo_dict)
     return render_to_response('status_report.html', { 'todo_listing': todo_listing })
    

    Writing the Template

    $ mkdir templates
    $ cd templates
    $ vi status_report.html

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
    <html xmlns="http://www.w3.org/1999/xhtml">
     <head>
       <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
       <title>To-do List Status Report</title>
     </head>
     <body>
       <h1>To-do list status report</h1>
    {% for list_dict in todo_listing %}
       <h2>{{ list_dict.list_object.title }}</h2>
       <ul>
         <li>Number of items: {{ list_dict.item_count }}</li>
         <li>Number completed: {{ list_dict.items_complete }} ({{ list_dict.percent_complete }}%)</li>
       </ul>
    {% endfor %}
     </body>
    </html>
    

    Making it Work

    $ cd ../..
    $ vi settings.py

    ...
    TEMPLATE_DIRS = (
        # Put strings here, like "/home/html/django_templates".
        # Always use forward slashes, even on Windows.
        '/home/webmaster/django/gtd/todo/templates',
    )
    

    $ vi urls.py

    ...
    urlpatterns = patterns('',
        # Example:
        # (r'^gtd/', include('gtd.apps.foo.urls.foo')),
    
        # Uncomment this for admin:
        (r'^admin/', include('django.contrib.admin.urls')),
        (r'^report/$', 'gtd.todo.views.status_report'),
    )
    

    $ python manage.py runserver 192.168.1.5:8000

    ลองดูที่บราวเซอร์ http://192.168.1.5:8000/report

    เรียบร้อย

    django: เกร็ด

    ในการติดตั้งของ django เขาใช้พอร์ต 8000 เป็นค่าปริยาย
    ทำนองเดียวกัน apache ก็ใช้พอร์ต 80 เป็นค่าปริยาย
    หากต้องการไปรัน django ที่พอร์ต 80 ต้องทำดังนี้

    แก้ไข /etc/apache2/ports.conf ให้ไปใช้พอร์ตอื่น สมมุติว่าเป็น 8088
    $ sudo vi /etc/apache2/ports.conf

    Listen 8088

    เวลาสั่งรัน django ต้องใช้สิทธิ์รูตในการรัน (พอร์ตที่ต่ำกว่า 1000)
    $ sudo python manage.py runserver 192.168.1.5:80

    Topic: 

    django: ลองทำ Blog

    เอามาจาก Falling Bullets - Blog - WordPress Clone in 27 Seconds (Part 1 of 40)

    เราชื่อ webmaster
    # su webmaster

    เราตั้งให้ไฟล์ของเราอยู่ใน ~/django
    $ cd ~/django

    ก่อนเริ่ม ให้ลบ database ชื่อ mysite ที่เราเคยทำไว้ออกก่อน
    แล้วจึงค่อยสร้างใหม่แบบว่าง ๆ
    $ psql template1 -U superx
    Password for user superx <<<--- SUPERX-PASSWORD
    template1=# DROP DATABASE mysite;
    template1=# CREATE DATABASE mysite;
    template1=# \q

    เริ่มสร้างโปรเจกต์
    $ django-admin.py startproject mysite
    $ cd mysite

    แก้ไข settings.py ให้เรียบร้อย
    $ vi settings.py

    ...
    DATABASE_ENGINE = 'postgresql'
    DATABASE_NAME = 'mysite'
    DATABASE_USER = 'superx'
    DATABASE_PASSWORD = 'SUPERX-PASSWORD'
    DATABASE_PORT = '5432'
    ...
    TIME_ZONE = 'Asia/Bangkok'
    ...

    สร้างแอปพลิเกชั่นชื่อ blog
    $ django-admin.py startapp blog

    แก้ไข models.py เพื่อสร้าง table
    $ vi blog/models.py

    from django.db import models
    
    class Tag(models.Model):
        name = models.CharField(maxlength=50, core=True)
        slug = models.SlugField(prepopulate_from=("name",))
    
        class Admin:
            pass
    
        def __str__(self):
            return self.name
    
        def get_absolute_url(self):
            return "/blog/tags/%s/" % (self.slug)
    
    class Entry(models.Model):
        title = models.CharField(maxlength=200)
        slug = models.SlugField(
            unique_for_date='pub_date',
            prepopulate_from=('title',),
            help_text='Automatically built from the title.'
        )
        summary = models.TextField(help_text="One paragraph. Don't add <p> tag.")
        body = models.TextField()
        pub_date = models.DateTimeField()
        tags = models.ManyToManyField(Tag, filter_interface=models.HORIZONTAL)
    
        class Meta:
            ordering = ('-pub_date',)
            get_latest_by = 'pub_date'
    
        class Admin:
            list_display = ('pub_date', 'title')
            search_fields = ['title', 'summary', 'body']
    
        def __str__(self):
            return self.title
    
        def get_absolute_url(self):
            return "/blog/%s/%s/" % (self.pub_date.strftime("%Y/%b/%d").lower(), self.slug)

    แก้ไข urls.py ให้สามารถเรียกไดเรกทอรี่เลียนแบบ Wordpress
    $ vi urls.py

    from django.conf.urls.defaults import *
    from mysite.blog.models import Entry
    
    blog_dict = {
        'queryset': Entry.objects.all(),
        'date_field': 'pub_date',
    }
    
    urlpatterns = patterns('',
        (r'^blog/(?P<year>\d{4})/(?P<month>[a-z]{3})/(?P<day>\w{1,2})/(?P<slug>[-\w]+)/$', 'django.views.generic.date_based.object_detail', dict(blog_dict, slug_field='slug')),
        (r'^blog/?$', 'django.views.generic.date_based.archive_index', blog_dict),
    )

    รัน syncdb ครั้งนึง เพื่อสร้าง table
    $ python manage.py syncdb
    ...
    Would you like to create one now? (yes/no): <<<--- yes
    Username (Leave blank to use 'webmaster'): <<<--- [ENTER]
    E-mail address: <<<--- webmaster@example.com
    Password: <<<--- WEBMASTER-PASSWORD
    Password (again): <<<--- WEBMASTER-PASSWORD
    Superuser created successfully.
    ...

    แก้ไข settings.py ให้มาใช้ template ของเรา
    $ vi settings.py

    ...
    TEMPLATE_DIRS = (
        # Put strings here, like "/home/html/django_templates".
        # Always use forward slashes, even on Windows.
        "/home/webmaster/django/mysite/templates"
    )
    ...

    สร้าง template หลัก ชื่อ base.html ในไดเรกทอรี่ ~/django/mysite/templates
    $ mkdir templates
    $ vi templates/base.html

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
    <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
    <head>
        <title>My Site - {% block title %}{% endblock %}</title>
    
        <meta http-equiv="content-type" content="text/html; charset=utf-8" />
    </head>
    <body>
        <div id="header">
            <h1><a href="/">My Interweb Tubes Blog</a></h1>
    
            <h2>It's not a truck!</h2>
    
            <ul id="nav">
                <li><a href="/">Home</a></li>
                <li><a href="/blog/">Blog</a></li>
                <li><a href="#">Photos</a></li>
                <li><a href="/links/">Links</a></li>
                <li><a href="/portfolio/">Work</a></li>
                <li><a href="/colophon/">Colophon</a></li>
            </ul>
        </div>
        <div id="content">
            {% block content %}
    
            {% endblock %}
        </div>
    </body>
    </html>

    สร้าง template ย่อย สำหรับดู Entry archive
    $ mkdir templates/blog
    $ vi templates/blog/entry_archive.html

    {% extends "base.html" %}
    
    {% block title %}Latest Blog Entries{% endblock %}
    
    {% block content %}
        <h1>Latest Blog Entries</h1>
    
        <ol id="object-list">
            {% for object in latest %}
                <li>
                    <h2><a href="{{ object.get_absolute_url }}">{{ object.title|escape }}</a></h2>
                    <p class="post-date">{{ object.pub_date|date:"F j, Y" }}</p>
                    <p class="summary">{{ object.summary }}</p>
                </li>
            {% endfor %}
        </ol>
    {% endblock %}

    และอีกอันสำหรับดูรายละเอียด
    $ vi templates/blog/entry_detail.html

    {% extends "base.html" %}
    
    {% block title %}Blog - {{ object.title|escape }}{% endblock %}
    
    {% block content %}
        <h1>{{ object.title|escape }}</h1>
    
        <dl>
            <dt>Posted On:</dt>
            <dd>{{ object.pub_date|date:"F j, Y" }}</dd>
            <dt>Tags:</dt>
            <dd>
                {% for tag in object.tags.all %}
                    <a href="{{ tag.get_absolute_url }}">{{ tag.name }}</a>{% if not forloop.last %}, {% endif %}
                {% endfor %}
            </dd>
        </dl>
    
        {{ object.body }}
    {% endblock %}

    แก้ไข settings.py อีกครั้ง ให้รับโมดูล blog และโมดูล admin
    $ vi settings.py

    ...
    INSTALLED_APPS = (
        'django.contrib.auth',
        'django.contrib.contenttypes',
        'django.contrib.sessions',
        'django.contrib.sites',
        'mysite.blog',
        'django.contrib.admin',
    )
    

    syncdb อีกครั้ง
    $ python manage.py syncdb

    เสร็จแล้ว ลองรันได้เลยด้วยคำสั่ง
    $ python manage.py runserver 192.168.1.5:8000

    สร้างเนื้อหา blog ได้จาก http://192.168.1.5:8000/admin
    โดยล๊อกอินในชื่อ webmaster และสร้างเนื้อหาในหมวด blog

    ดูเนื้อหาที่สร้างแล้วที่ http://192.168.1.5:8000/blog

    django: ใช้กับ apache2 บนเดเบียน

    ลองติดตั้ง django เพื่อใช้งานกับ apache2 บนเดเบียน

    เที่ยวนี้ทำไปบันทึกไป จึงไม่มีกำหนดเสร็จครับ

    งานของ admin เจ้าของเซิร์ฟเวอร์
    เอา django มาก่อน
    # aptitude install subversion
    # svn co http://code.djangoproject.com/svn/django/trunk/

    ติดตั้ง django สู่ระบบ
    # cd trunk
    # python setup.py install

    ลบซอร์ส หากไม่ต้องการดูโค๊ดของ django
    # cd ..
    # rm -rf trunk

    กันเหนียวให้ apache2 เปิดมอดูล env (ส่วนใหญ่จะเปิดมาอยู่แล้วมั้ง)
    # a2enmod env

    ติดตั้ง mod_python และเปิดให้ใช้งาน
    # aptitude install libapache2-mod-python
    # a2enmod mod_python

    งานของเรา เจ้าของเว็บ
    สมมุติว่า admin ติดตั้ง ให้ DocumentRoot ของ apache2 สำหรับโดเมน www.example.com อยู่ที่ไดเรกทอรี่ของเรา /home/user1/www
    และเราจะให้หน้าของ django ไปอยู่ที่ http://www.example.com/dj

    มาที่ไดเรกทอรี่ของเราก่อน
    $ cd ~/www

    เริ่มโปรเจคต์ใหม่ชื่อ dj
    $ django-admin.py startproject dj

    ไปที่ไดเรคทอรี่ dj และเตรียมการให้ apache2 โดยการสร้างไฟล์ .htaccess
    $ cd dj
    $ vi .htaccess

    SetHandler python-program
    PythonHandler django.core.handlers.modpython
    SetEnv SERVER_ADMIN webmaster@example.com
    SetEnv DJANGO_SETTINGS_MODULE dj.settings
    PythonDebug On
    PythonPath "['/home/user1'] + sys.path"
    RewriteEngine On
    RewriteBase /dj/

    เสร็จแล้ว ดูที่หน้า www.example.com/dj ได้ดังนี้
    ตัวอย่างติดตั้ง django กับ apache2 ครั้งที่ 1

    เพิ่มเติม
    สำหรับการทำงานให้เต็มรูปแบบ ต้องสร้างหน้า admin ด้วย
    การที่จะทำให้หน้า admin ทำงานได้สมบูรณ์ ต้องสร้างลิงก์โยงจากทรัพยากรของซอร์สมาที่ไดเรกทอรี่รากของ apache2 ด้วย
    สมมุติถ้าใช้ไพธอน 2.4 บนเดเบียน
    $ cd ~/www
    $ ln -sf /usr/lib/python2.4/site-packages/django/contrib/admin/media/ .

    สร้างหน้า admin โดยการลบคอมเมนต์ในไฟล์ dj/urls.py
    $ cd dj
    $ vi urls.py

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

    แต่ถ้าหากเราจะให้เพจของ django อยู่ในหน้า www.example.com/dj เราต้องแก้ไฟล์ด้วย

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

    แต่...ก่อนจะใช้งานหน้า admin ได้ เราต้องสร้างฐานข้อมูลก่อน สมมุติว่าจะใช้กับ postgresql
    สร้างฐานข้อมูลไว้รองรับ ตั้งชื่อว่า djdb
    $ createdb djdb

    Password: >>> --- USER1_PASSWORD

    ปรับตั้งไฟล์ settings.py ให้ django รับรู้ฐานข้อมูลและให้เรียกใช้มอดูล admin
    $ vi settings.py

    ...
    DATABASE_ENGINE = 'postgresql'
    DATABASE_NAME = 'djdb'
    DATABASE_USER = 'user1'
    DATABASE_PASSWORD = 'USER1_PASSWORD'
    ...
    TIME_ZONE = 'Asia/Bangkok'
    ...
    LANGUAGE_CODE = 'th'
    ...
    INSTALLED_APPS = (
        'django.contrib.auth',
        'django.contrib.contenttypes',
        'django.contrib.sessions',
        'django.contrib.sites',
        'django.contrib.admin',
    )

    สั่งปรับปรุงฐานข้อมูล
    $ 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 django_admin_log
    
    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 (Leave blank to use 'user1'): >>>--- ENTER
    E-mail address: >>>--- user1@example.com
    Password: >>>--- USER1_PASSWORD
    Password (again): >>>--- USER1_PASSWORD
    Superuser created successfully.
    Installing index for auth.Message model
    Installing index for auth.Permission model
    Installing index for admin.LogEntry model
    

    เสร็จแล้ว ดูจาก URL:http://www.example.com/dj/admin จะได้ดังนี้
    หน้าจอ admin

    อ้างอิง

    django+apache2: สร้างแอพลิเคชั่น blog

    คราวนี้ทำบล๊อกจาก Falling Bullets - Blog - WordPress Clone in 27 Seconds (Part 1 of 40)

    โดย

    • ใช้งานกับ apache2 ที่ติดตั้งเรียบร้อยแล้ว
    • เรียกใช้งานภายใต้ไดเรกทอรี่ http://www.example.com/dj
      (ซึ่งจริง ๆ แล้วไม่ค่อยดีเท่าไหร่ เวลาอ้างถึงหน้าหลักมันจะอ้างยาก หากเราต้องการเปลี่ยนไดเรกทอรี่มาเป็น root มันต้องตามเปลี่ยนในโค๊ดด้วย ลองดูจากตัวอย่างได้
      ที่ควรทำจริง ๆ คืออ้าง URL ใหม่ โดยใช้ไปอยู่ที่ root แทน จะยุ่งน้อยกว่า เช่น http://dj.example.com เป็นต้น)

      แต่ของเราเที่ยวนี้เอาแบบอยู่ภายใต้ไดเรคทอรี่ /dj ไปก่อน
    • ใช้ฐานข้อมูล postgresql ที่ติดตั้งไว้แล้ว
    • root ของ apache2 อยู่ที่ /home/user1/www
    • ชื่อผู้ใช้งานฐานข้อมูลคือ user1 รหัสผ่านคือ USER1_PASSWORD มีสิทธิ์ในการสร้างฐานข้อมูล
    • โครงการ (project) ชื่อ dj เหมือนเดิม ฉะนั้นไดเรกทอรี่ของโครงการจะไปอยู่ที่ /home/user1/www/dj
    • การติดตั้งพื้นฐาน ดูจาก django: ใช้กับ apache2 บนเดเบียน
    • เนื้อความในนี้อาจมีข้อความติดพันมาจากการทดลองจากคราวก่อน ๆ ดังนั้นถ้างง ฝากย้อนขึ้นไปดูคราวก่อน เริ่มตั้งแต่ /node/424 ลงมา

    สร้างแอพลิเคชั่นชื่อ blog ในไดเรคทอรี่ dj จากครั้งก่อน
    $ cd ~/www/dj
    $ python manage.py startapp blog

    สร้างตารางฐานข้อมูลด้วย models.py ให้มี 2 ตาราง คือเก็บแท็ก และเก็บเนื้อเรื่อง
    $ vi blog/models.py

    from django.db import models
    
    class Tag(models.Model):
        name = models.CharField(maxlength=50, core=True)
        slug = models.SlugField(prepopulate_from=("name",))
    
        class Admin:
            pass
    
        def __str__(self):
            return self.name
    
        def get_absolute_url(self):
            return "/dj/blog/tags/%s/" % (self.slug)
    
    class Entry(models.Model):
        title = models.CharField(maxlength=200)
        slug = models.SlugField(
            unique_for_date='pub_date',
            prepopulate_from=('title',),
            help_text='Automatically built from the title.'
        )
        summary = models.TextField(help_text="One paragraph. Don't add &lt;p&gt; tag.")
        body = models.TextField()
        pub_date = models.DateTimeField()
        tags = models.ManyToManyField(Tag, filter_interface=models.HORIZONTAL)
    
        class Meta:
            ordering = ('-pub_date',)
            get_latest_by = 'pub_date'
    
        class Admin:
            list_display = ('pub_date', 'title')
            search_fields = ['title', 'summary', 'body']
    
        def __str__(self):
            return self.title
    
        def get_absolute_url(self):
            return "/dj/blog/%s/%s/" % (self.pub_date.strftime("%Y/%b/%d").lower(), self.slug)
    

    แก้ไข urls.py ให้สามารถเรียกไดเรกทอรี่เลียนแบบ Wordpress หรือเรียกแบบปกติ
    $ vi urls.py

    from django.conf.urls.defaults import *
    from dj.blog.models import Entry
    
    blog_dict = {
        'queryset': Entry.objects.all(),
        'date_field': 'pub_date',
    }
    
    urlpatterns = patterns('',
        # Example:
        # (r'^dj/', include('dj.foo.urls')),
    
        # Uncomment this for admin:
        (r'^dj/admin/', include('django.contrib.admin.urls')),
        (r'^dj/report/$', 'dj.todo.views.status_report'),
        (r'^dj/blog/(?P<year>\d{4})/(?P<month>[a-z]{3})/(?P<day>\w{1,2})/(?P<slug>[-\w]+)/$', 'django.views.generic.date_based.object_detail', dict(blog_dict, slug_field='slug')),
        (r'^dj/blog/?$', 'django.views.generic.date_based.archive_index', blog_dict),
    )
    

    รัน syncdb ครั้งนึง เพื่อสร้างและปรับปรุงตาราง
    $ python manage.py syncdb

      title = models.CharField(maxlength=250, unique=True)
      title = models.CharField(maxlength=250)

    ต่อไปเป็นเรื่องอินเทอร์เฟสแสดงหน้าตา

    (เที่ยวนี้แปลกไปนิดนึง เพราะเขาเรียกแสดงผลผ่านฟังก์ชั่นมาตรฐานของ django โดยไม่ได้ใช้ views ของเรา เลยต้องวางไดเรกทอรี่ไว้เป็นมาตรฐาน คือเอา templates ไว้ที่ root ของโครงการ)

    แก้ไข settings.py ให้มาใช้ template ของเรา รวมทั้งบอกให้เปิดมอดูล blog ที่เราเพิ่งสร้างขึ้น
    $ vi settings.py

    ...
    TEMPLATE_DIRS = (
        # Put strings here, like "/home/html/django_templates" or "C:/www/django/templates".
        # Always use forward slashes, even on Windows.
        # Don't forget to use absolute paths, not relative paths.
        '/home/user1/www/dj/todo/templates',
        '/home/user1/www/dj/templates',
    )
    
    INSTALLED_APPS = (
        'django.contrib.auth',
        'django.contrib.contenttypes',
        'django.contrib.sessions',
        'django.contrib.sites',
        'django.contrib.admin',
        'dj.todo',
        'dj.blog',
    )
    

    สร้างเทมเพลตโดยการสร้างไดเรกทอรี่ชื่อ templates ไว้ที่ root ของโครงการ
    ในไดเรกทอรี่ templates จะมีไฟล์ base.html เอาไว้ดูหน้าหลักซึ่งเป็นพวกเมนูต่าง ๆ
    และสร้างไดเรกทอรี่ย่อยชื่อ templates/blog อีกที จะมีไฟล์ entry_archive.html ไว้ดูหัวข้อบล๊อก และ entry_detail.html ไว้ดูรายละเอียดของแต่ละรายการ
    $ mkdir -p templates/blog
    $ vi templates/base.html

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
    <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
    <head>
        <title>My Site - {% block title %}{% endblock %}</title>
    
        <meta http-equiv="content-type" content="text/html; charset=utf-8" />
    </head>
    <body>
        <div id="header">
            <h1><a href="/">My Interweb Tubes Blog</a></h1>
    
            <h2>It's not a truck!</h2>
    
            <ul id="nav">
                <li><a href="/dj/">Home</a></li>
                <li><a href="/dj/blog/">Blog</a></li>
                <li><a href="#">Photos</a></li>
                <li><a href="/dj/links/">Links</a></li>
                <li><a href="/dj/portfolio/">Work</a></li>
                <li><a href="/dj/colophon/">Colophon</a></li>
            </ul>
        </div>
        <div id="content">
            {% block content %}
    
            {% endblock %}
        </div>
    </body>
    </html>

    $ vi templates/blog/entry_archive.html

    {% extends "base.html" %}
    
    {% block title %}Latest Blog Entries{% endblock %}
    
    {% block content %}
        <h1>Latest Blog Entries</h1>
    
        <ol id="object-list">
            {% for object in latest %}
                <li>
                    <h2><a href="{{ object.get_absolute_url }}">{{ object.title|escape }}</a></h2>
                    <p class="post-date">{{ object.pub_date|date:"F j, Y" }}</p>
                    <p class="summary">{{ object.summary }}</p>
                </li>
            {% endfor %}
        </ol>
    {% endblock %}

    $ vi templates/blog/entry_detail.html

    {% extends "base.html" %}
    
    {% block title %}Blog - {{ object.title|escape }}{% endblock %}
    
    {% block content %}
        <h1>{{ object.title|escape }}</h1>
    
        <dl>
            <dt>Posted On:</dt>
            <dd>{{ object.pub_date|date:"F j, Y" }}</dd>
            <dt>Tags:</dt>
            <dd>
                {% for tag in object.tags.all %}
                    <a href="{{ tag.get_absolute_url }}">{{ tag.name }}</a>{% if not forloop.last %}, {% endif %}
                {% endfor %}
            </dd>
        </dl>
    
        {{ object.body }}
    {% endblock %}

    เรียกปรับปรุงตารางอีกครั้ง
    python manage.py syncdb

      title = models.CharField(maxlength=250, unique=True)
      title = models.CharField(maxlength=250)
      name = models.CharField(maxlength=50, core=True)
      title = models.CharField(maxlength=200)
    Creating table blog_entry
    Creating table blog_tag
    Installing index for blog.Entry model
    Installing index for blog.Tag model

    เสร็จแล้ว เรียกผ่าน URL:http://www.example.com/dj/blog ได้ดังนี้
    เรียก blog 1

    django+apache2: สร้างแอพลิเคชั่น todo

    จากครั้งก่อน django: ใช้กับ apache2 บนเดเบียน (มีการปรับปรุงให้เนื้อหาสมบูรณ์ขึ้นในหน้าเก่าด้วย)

    ตอนนี้เราจะมาสร้างแอพลิเคชั่นชื่อ "to do" จาก sitepoint.com - Django Djumpstart: Build a To-do List in 30 Minutes

    โดย

    • ใช้งานกับ apache2 ที่ติดตั้งเรียบร้อยแล้ว
    • เรียกใช้งานภายใต้ไดเรกทอรี่ http://www.example.com/dj
      (ซึ่งจริง ๆ แล้วไม่ค่อยดีเท่าไหร่ เวลาอ้างถึงหน้าหลักมันจะอ้างยาก หากเราต้องการเปลี่ยนไดเรกทอรี่มาเป็น root มันต้องตามเปลี่ยนในโค๊ดด้วย ลองดูจากตัวอย่างได้
      ที่ควรทำจริง ๆ คืออ้าง URL ใหม่ โดยใช้ไปอยู่ที่ root แทน จะยุ่งน้อยกว่า เช่น http://dj.example.com เป็นต้น)

      แต่ของเราเที่ยวนี้เอาแบบอยู่ภายใต้ไดเรคทอรี่ /dj ไปก่อน
    • ใช้ฐานข้อมูล postgresql ที่ติดตั้งไว้แล้ว
    • root ของ apache2 อยู่ที่ /home/user1/www
    • ชื่อผู้ใช้งานฐานข้อมูลคือ user1 รหัสผ่านคือ USER1_PASSWORD มีสิทธิ์ในการสร้างฐานข้อมูล
    • โครงการ (project) ชื่อ dj เหมือนเดิม ฉะนั้นไดเรกทอรี่ของโครงการจะไปอยู่ที่ /home/user1/www/dj

    เริ่มเลย

    สร้างแอพลิเคชั่นชื่อ "to do" เอาไว้สำหรับดูว่าจะทำงานอะไรบ้าง
    $ cd ~/www/dj
    $ python manage.py startapp todo

    สร้างฐานข้อมูลผ่านโปรแกรมชื่อ todo/models.py
    โดยเราจะสร้างเป็น 2 ตาราง โดยแต่ละตารางจะเป็นคลาสใน model.py
    คือคลาส List สำหรับดูหัวข้อ และคลาส Item สำหรับเก็บรายละเอียดของข้อมูลของงานที่จะทำ
    $ vi todo/models.py

    ...
    #TABLE List
    class List(models.Model):
      title = models.CharField(maxlength=250, unique=True)
      def __str__(self):
        return self.title
      class Meta:
        ordering = ['title']
      class Admin:
        pass
    
    
    #TABLE Item
    import datetime
    
    PRIORITY_CHOICES = (
      (1, 'Low'),
      (2, 'Normal'),
      (3, 'High'),
    )
    
    class Item(models.Model):
      title = models.CharField(maxlength=250)
      created_date = models.DateTimeField(default=datetime.datetime.now)
      priority = models.IntegerField(choices=PRIORITY_CHOICES, default=2)
      completed = models.BooleanField(default=False)
      todo_list = models.ForeignKey(List)
      def __str__(self):
        return self.title
      class Meta:
        ordering = ['-priority', 'title']
      class Admin:
        pass
    

    ถึงตอนนี้ต้องมีฐานข้อมูลอยู่แล้ว หากยังไม่ได้สร้างฐานข้อมูล ให้ย้อนไปดูคราวก่อน
    ปรับตั้งให้ django รับรู้ถึงการเพิ่มตาราง ผ่านไฟล์ settings.py
    $ vi settings.py

    ...
    INSTALLED_APPS = (
        'django.contrib.auth',
        'django.contrib.contenttypes',
        'django.contrib.sessions',
        'django.contrib.sites',
        'django.contrib.admin',
        'dj.todo',
    )

    สั่งสร้างตาราง/ปรับปรุงฐานข้อมูล
    $ python manage.py syncdb

      title = models.CharField(maxlength=250, unique=True)
      title = models.CharField(maxlength=250)
    Creating table todo_item
    Creating table todo_list
    Installing index for todo.Item model
    

    ต่อไปเป็นการสร้างอินเทอร์เฟสผ่านไฟล์ชื่อ todo/views.py
    ในไฟล์นี้เราจะสร้างฟังก์ชั่นในการแสดงรายงานสถานะของงานชื่อว่า status_report
    $ vi todo/views.py

    ...
    from django.shortcuts import render_to_response
    from dj.todo.models import List
    
    def status_report(request):
     todo_listing = []
     for todo_list in List.objects.all():
       todo_dict = {}
       todo_dict['list_object'] = todo_list
       todo_dict['item_count'] = todo_list.item_set.count()
       todo_dict['items_complete'] = todo_list.item_set.filter(completed=True).count()
       todo_dict['percent_complete'] = int(float(todo_dict['items_complete']) / todo_dict['item_count'] * 100)
       todo_listing.append(todo_dict)
     return render_to_response('status_report.html', { 'todo_listing': todo_listing })

    เอาตารางมาใช้จากคลาส List ใน todo/models.py

    ไฟล์ views.py นี้เป็นฟังก์ชั่นการทำงานล้วน ๆ ซึ่งเราจะต้องสร้างเทมเพลตในการแสดงผลอีกทีหนึ่ง
    ในการสร้างเทมเพลต เราจะสร้างไดเรคทอรี่ย่อยชื่อ templates ไว้ใน todo เพื่อเอาไว้บรรจุไฟล์เทมเพลต คือไฟล์ HTML ในที่นี้ตั้งชื่อว่า status_report.html
    $ mkdir todo/templates
    $ vi todo/templates/status_report.html

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
    <html xmlns="http://www.w3.org/1999/xhtml">
     <head>
       <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
       <title>To-do List Status Report</title>
     </head>
     <body>
       <h1>To-do list status report</h1>
    {% for list_dict in todo_listing %}
       <h2>{{ list_dict.list_object.title }}</h2>
       <ul>
         <li>Number of items: {{ list_dict.item_count }}</li>
         <li>Number completed: {{ list_dict.items_complete }} ({{ list_dict.percent_complete }}%)</li>
       </ul>
    {% endfor %}
     </body>
    </html>

    สังเกตุว่าคำสั่งจะอยู่ในบล๊อก {% COMMAND %} และตัวแปรจะอยู่ในบล๊อก {{ VARIABLE }} โดยตัวแปรที่ใช้ ใช้เสมือนเราอยู่ภายในมอดูล todo.views.status_report ซึ่งเราต้องกลับไปบอก django ในไฟล์ urls.py

    ต้องกลับไปบอก django ว่าโครงการของเรามีเทมเพลตด้วย และเทมเพลตเราอยู่ที่ไหน ผ่าน settings.py
    $ vi settings.py

    ...
    TEMPLATE_DIRS = (
        # Put strings here, like "/home/html/django_templates" or "C:/www/django/templates".
        # Always use forward slashes, even on Windows.
        # Don't forget to use absolute paths, not relative paths.
        '/home/user1/www/dj/todo/templates',
    )

    และกำหนดให้ apache2 มาเรียกใช้ todo เมื่อเข้า URL:/dj/report/ ผ่าน urls.py
    $ vi urls.py

    ...
    urlpatterns = patterns('',
        # Example:
        # (r'^dj/', include('dj.foo.urls')),
    
        # Uncomment this for admin:
        (r'^dj/admin/', include('django.contrib.admin.urls')),
        (r'^dj/report/$', 'dj.todo.views.status_report'),
    )

    ตอนนี้ดูได้แล้ว ผ่าน URL:http://www.example.com/dj/report
    หน้าจอแรก todo

    ตอนนี้ยังไม่มีอะไร เพราะเรายังไม่ได้ใส่อะไรเข้าไป
    ถึงตอนนี้เราสามารถใส่เนื้อหาใหม่เข้าไปได้ ผ่านทางหน้า admin
    โดยต้องเพิ่ม List ก่อน ทาง URL:http://www.example.com/dj/admin/todo/list/add
    ตามด้วย Item ทาง URL:http://www.example.com/dj/admin/todo/item/add
    พอเข้าหน้า report ใหม่ ก็จะเห็นรายการตามต้องการ

    django: ตกแต่งให้ใช้งานได้

    รายการเพิ่มเติมสำหรับ django รุ่น svn (ระหว่าง 0.96-)

    • ต้องติดตั้ง docutils เพิ่ม เพื่อให้สามารถดูเอกสารในหน้า admin ได้
      สำหรับเดเบียนคำสั่งคือ
      $ sudo aptitude install python-docutils
    • หน้า admin สวยงามก็จริงอยู่ แต่ตัวหนังสือเล็กไปหน่อยสำหรับผู้เฒ่ากับจอใหญ่ ๆ
      เดเบียนปรับแก้ดังนี้ (อย่าลืมสำรองไฟล์ไว้ก่อนด้วยนะครับ)
      $ sudo vi /usr/lib/python2.4/site-packages/django/contrib/admin/media/css/global.css
      body { margin:0; padding:0; font-size:84%; font-family:"Lucida Grande","DejaVu Sans","Bitstream Vera Sans",Verdana,Arial,sans-serif; color:#333; background:#fff; }
      
      /* LINKS */
      a:link, a:visited { color: #5b80b2; text-decoration:none; }
      a:hover { color: #036; }
      a img { border:none; }
      
      /* GLOBAL DEFAULTS */
      p, ol, ul, dl { margin:.2em 0 .8em 0; }
      p { padding:0; line-height:140%; }
      
      h1,h2,h3,h4,h5 { font-weight:bold; }
      h1 { font-size:1.4em; color:#666; padding:0 6px 0 0; margin:0 0 .2em 0; }
      h2 { font-size:1.3em; margin:1em 0 .5em 0; }
      h2.subhead { font-weight:normal;margin-top:0; }
      h3 { font-size:1.2em; margin:.8em 0 .3em 0; color:#666; font-weight:bold; }
      h4 { font-size:1.1em; margin:1em 0 .8em 0; padding-bottom:3px; }
      h5 { font-size:1em; margin:1.5em 0 .5em 0; color:#666; text-transform:uppercase; letter-spacing:1px; }
      
      ul li { list-style-type:square; padding:1px 0; }
      ul.plainlist { margin-left:0 !important; }
      ul.plainlist li { list-style-type:none; }
      li ul { margin-bottom:0; }
      li, dt, dd { font-size:.9em; line-height:1.2em; }
      dt { font-weight:bold; margin-top:4px; }
      dd { margin-left:0; }
      
      form { margin:0; padding:0; }
      fieldset { margin:0; padding:0; }
      
      blockquote { font-size:.9em; color:#777; margin-left:2px; padding-left:10px; border-left:5px solid #ddd; }
      code, pre { font-family:"Bitstream Vera Sans Mono", Monaco, "Courier New", Courier, monospace; background:inherit; color:#666; font-size:.9em; }
      pre.literal-block { margin:10px; background:#eee; padding:6px 8px; }
      code strong { color:#930; }
      hr { clear:both; color:#eee; background-color:#eee; height:1px; border:none; margin:0; padding:0; font-size:1px; line-height:1px; }
      
      /* TEXT STYLES & MODIFIERS */
      .small { font-size:.9em; }
      .tiny { font-size:.8em; }
      p.tiny { margin-top:-2px; }
      .mini { font-size:.7em; }
      p.mini { margin-top:-3px; }
      .help, p.help { font-size:.8em !important; color:#999; }
      p img, h1 img, h2 img, h3 img, h4 img, td img { vertical-align:middle; }
      .quiet, a.quiet:link, a.quiet:visited { color:#999 !important;font-weight:normal !important; }
      .quiet strong { font-weight:bold !important; }
      .float-right { float:right; }
      .float-left { float:left; }
      .clear { clear:both; }
      .align-left { text-align:left; }
      .align-right { text-align:right; }
      .example { margin:10px 0; padding:5px 10px; background:#efefef; }
      .nowrap { white-space:nowrap; }
      
      /* TABLES */
      table { border-collapse:collapse; border-color:#ccc; }
      td, th { font-size:.9em; line-height:1.2em; border-bottom:1px solid #eee; vertical-align:top; padding:5px; font-family:"Lucida Grande", Verdana, Arial, sans-serif; }
      th { text-align:left; font-size:1em; font-weight:bold; }
      thead th, 
      tfoot td { color:#666; padding:2px 5px; font-size:.9em; background:#e1e1e1 url(../img/admin/nav-bg.gif) top left repeat-x; border-left:1px solid #ddd; border-bottom:1px solid #ddd; } 
      tfoot td { border-bottom:none; border-top:1px solid #ddd; }
      thead th:first-child,
      tfoot td:first-child { border-left:none !important; }
      thead th.optional { font-weight:normal !important; }
      fieldset table { border-right:1px solid #eee; }
      tr.row-label td { font-size:.7em; padding-top:2px; padding-bottom:0; border-bottom:none; color:#666; margin-top:-1px; }
      tr.alt { background:#f6f6f6; }
      .row1 { background:#EDF3FE; }
      .row2 { background:white; }
      
      /* SORTABLE TABLES */
      thead th a:link, thead th a:visited { color:#666; display:block; }
      table thead th.sorted { background-position:bottom left !important; }
      table thead th.sorted a { padding-right:13px; }
      table thead th.ascending a { background:url(../img/admin/arrow-down.gif) right .4em no-repeat; }
      table thead th.descending a { background:url(../img/admin/arrow-up.gif) right .4em no-repeat; }
      
      /* ORDERABLE TABLES */
      table.orderable tbody tr td:hover { cursor:move; }
      table.orderable tbody tr td:hover { cursor:move; }
      table.orderable tbody tr td:first-child { padding-left:14px; background-image:url(../img/admin/nav-bg-grabber.gif); background-repeat:repeat-y; }
      table.orderable-initalized .order-cell, body>tr>td.order-cell { display:none; }
      
      /* FORM DEFAULTS */
      input, textarea, select { margin:2px 0; padding:2px 3px; vertical-align:middle; font-family:"Lucida Grande", Verdana, Arial, sans-serif; font-weight:normal; font-size:.9em; }
      textarea { vertical-align:top !important; }
      input[type=text], input[type=password], textarea, select, .vTextField { border:1px solid #ccc; }
      
      /*  FORM BUTTONS  */
      input[type=submit], input[type=button], .submit-row input { background:white url(../img/admin/nav-bg.gif) bottom repeat-x; padding:3px; color:black; border:1px solid #bbb; border-color:#ddd #aaa #aaa #ddd; }
      input[type=submit]:active, input[type=button]:active { background-image:url(../img/admin/nav-bg-reverse.gif); background-position:top; }
      input[type=submit].default, .submit-row input.default { border:2px solid #5b80b2; background:#7CA0C7 url(../img/admin/default-bg.gif) bottom repeat-x; font-weight:bold; color:white; }
      input[type=submit].default:active { background-image:url(../img/admin/default-bg-reverse.gif); background-position:top; }
      
      /* MODULES */
      .module { border:1px solid #ccc; margin-bottom:5px; background:white; }
      .module p, .module ul, .module h3, .module h4, .module dl, .module pre { padding-left:10px; padding-right:10px; }
      .module blockquote { margin-left:12px; }
      .module ul, .module ol { margin-left:1.5em; }
      .module h3 { margin-top:.6em; }
      .module h2, .module caption { margin:0; padding:2px 5px 3px 5px; font-size:.9em; text-align:left; font-weight:bold; background:#7CA0C7 url(../img/admin/default-bg.gif) top left repeat-x; color:white; }
      .module table { border-collapse: collapse; }
      
      /* MESSAGES & ERRORS */ 
      ul.messagelist { padding:0 0 5px 0; margin:0; }
      ul.messagelist li { font-size:1em; display:block; padding:4px 5px 4px 25px; margin:0 0 3px 0; border-bottom:1px solid #ddd; color:#666; background:#ffc url(../img/admin/icon_success.gif) 5px .3em no-repeat; }
      .errornote { font-size:1em !important; display:block; padding:4px 5px 4px 25px; margin:0 0 3px 0; border:1px solid red; color:red;background:#ffc url(../img/admin/icon_error.gif) 5px .3em no-repeat; }
      ul.errorlist { margin:0 !important; padding:0 !important; }
      .errorlist li { font-size:1em !important; display:block; padding:4px 5px 4px 25px; margin:0 0 3px 0; border:1px solid red; color:white; background:red url(../img/admin/icon_alert.gif) 5px .3em no-repeat; }
      td ul.errorlist { margin:0 !important; padding:0 !important; }
      td ul.errorlist li { margin:0 !important; } 
      .error { background:#ffc; }
      .error input, .error select { border:1px solid red; }
      div.system-message { background: #ffc; margin: 10px; padding: 6px 8px; font-size: .8em; }
      div.system-message p.system-message-title { padding:4px 5px 4px 25px; margin:0; color:red; background:#ffc url(../img/admin/icon_error.gif) 5px .3em no-repeat; }
      .description { font-size:1em; padding:5px 0 0 12px; }
      
      /* BREADCRUMBS */
      div.breadcrumbs { background:white url(../img/admin/nav-bg-reverse.gif) 0 -10px repeat-x; padding:2px 8px 3px 8px; font-size:.9em;  color:#999;  border-top:1px solid white; border-bottom:1px solid #ccc; text-align:left; }
      
      /* ACTION ICONS */
      .addlink { padding-left:12px; background:url(../img/admin/icon_addlink.gif) 0 .2em no-repeat; }
      .changelink { padding-left:12px; background:url(../img/admin/icon_changelink.gif) 0 .2em no-repeat; }
      .deletelink { padding-left:12px; background:url(../img/admin/icon_deletelink.gif) 0 .25em no-repeat; }
      a.deletelink:link, a.deletelink:visited { color:#CC3434; }
      a.deletelink:hover { color:#993333; }
      
      /* OBJECT TOOLS */
      .object-tools { font-size:.8em; font-weight:bold; font-family:Arial,Helvetica,sans-serif; padding-left:0; float:right; position:relative; margin-top:-2.4em; margin-bottom:-2em; }
      .form-row .object-tools { margin-top:5px; margin-bottom:5px; float:none; height:2em; padding-left:3.5em; }
      .object-tools li { display:block; float:left; background:url(../img/admin/tool-left.gif) 0 0 no-repeat; padding:0 0 0 8px; margin-left:2px; height:16px; }
      .object-tools li:hover { background:url(../img/admin/tool-left_over.gif) 0 0 no-repeat; }
      .object-tools a:link, .object-tools a:visited { display:block; float:left; color:white; padding:.1em 14px .1em 8px; height:14px; background:#999 url(../img/admin/tool-right.gif) 100% 0 no-repeat; }
      .object-tools a:hover, .object-tools li:hover a { background:#5b80b2 url(../img/admin/tool-right_over.gif) 100% 0 no-repeat; }
      .object-tools a.viewsitelink, .object-tools a.golink { background:#999 url(../img/admin/tooltag-arrowright.gif) top right no-repeat; padding-right:28px; }
      .object-tools a.viewsitelink:hover, .object-tools a.golink:hover { background:#5b80b2 url(../img/admin/tooltag-arrowright_over.gif) top right no-repeat; }
      .object-tools a.viewsitelink:hover, .object-tools a.golink:hover { background:#5b80b2 url(../img/admin/tooltag-arrowright_over.gif) top right no-repeat; }
      .object-tools a.addlink { background:#999 url(../img/admin/tooltag-add.gif) top right no-repeat; padding-right:28px; }
      .object-tools a.addlink:hover { background:#5b80b2 url(../img/admin/tooltag-add_over.gif) top right no-repeat; }
      
      /* OBJECT HISTORY */
      table#change-history { width:100%; }
      table#change-history tbody th { width:16em; }
      

      คราวหน้า ถ้าจะปรับเปลี่ยนเพิ่มเติม เพียงเปลี่ยนเฉพาะบรรทัดแรกจาก 84% ไปเป็นตัวเลขอื่นก็ปรับเฉพาะตัวนี้ตัวเดียว
      เมื่อแปลงแล้วได้ภาพดังนี้
      ปรับปรุง css ของหน้า admin

    Topic: