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: