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

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

  • 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: