ลิงก์ควรศึกษา
การใช้ไพธอนทำเว็บ มีการใช้มอดูลแบบหลัก ๆ คือ
บันทึกเกร็ดเกี่ยวกับการใช้ไพธอนทำเว็บ ด้วย cgi
Content-type: text/html\r\n
เป็นบรรทัดแรก apache ถึงจะตีความเป็น HTML#!/usr/bin/env python print "Content-type: text/html\r\n" ...
#!/usr/bin/env python # -*- coding: utf8 -*- ...
from ... import *
และ import ...
เช่นสมมุติแพกเกจเราชื่อ dweb ใช้... import dweb from dweb import * ...
$ 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
การใช้โมดูล cgi ในการสร้างเว็บ ต้องระวังเรื่องเนื้อหาในเพจให้ดี
ให้ระวังคือ
ไม่งั้นฟังก์ชั่น cgi.FieldStorage จะทำงานผิดพลาดไปหมด ดีบักยากด้วย เพราะแสดงผลออกมาถูก แต่การทำงานภายในผิดหมด
เอามาจาก 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
ที่เราใส่ไว้
อ่าน Dive Into Python เห็นตัวอย่างฟังก์ชั่น Info
เลยประยุกต์มาทำบนเว็บครับ
เผื่อจะขยายไปเป็น search help
ทดลองทำ syntax highlight โดยลอกจาก โมดูล GeSHiFilter ของ drupal ซึ่งเอามาจากโค๊ด PHP ที่ GeSHi อีกทีนึง แก้ปรับสี css นิดเดียว
ทดลองดูซอร์สได้ดังนี้
ตอนนี้ทำได้แค่ไพธอนภาษาเดียว และน่าจะยังมีบั๊กอยู่เยอะ จะค่อย ๆ ปรับปรุงไปเรื่อย ๆ ครับ
ยังไม่รู้ว่าโค๊ดของโมดูลหลัก dweb จะเปลี่ยนแปลงไปยังไงนะครับ แต่ตอนนี้โพสต์แบบนี้ไปก่อน
เราสามารถใช้ 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 -->
ด้วยวิธีนี้เราสามารถนำเข้าไฟล์สตรีมทั้งหลายได้โดยสะดวก
จากตอนก่อน เราสามารถนำมาสร้างสคริปต์ง่าย ๆ เอาไว้เก็บเนื้อหาของหน้าเว็บได้ดังนี้
$ 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
จากครั้งก่อน ที่ทำตัวแจงเมธอด ขยายมาเป็นตัวค้นหาและแสดงซอร์สโค๊ด
โดยใช้พร๊อพเพอตี้ __file__
ปกติเราดูซอร์สได้เองอยู่แล้ว แต่การนำมาลงเว็บให้ค้นได้ง่าย ๆ อาจช่วยทำให้เข้าใจการทำงานของโมดูลในไพธอนดียิ่งขึ้นครับ
ลอง 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
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
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
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}
ลองเพิ่มผู้ใช้และกรุ๊ปดู
$ 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 })
$ 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>
$ 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 เขาใช้พอร์ต 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
เอามาจาก 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 บนเดเบียน
เที่ยวนี้ทำไปบันทึกไป จึงไม่มีกำหนดเสร็จครับ
งานของ 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 ได้ดังนี้
เพิ่มเติม
สำหรับการทำงานให้เต็มรูปแบบ ต้องสร้างหน้า 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
จะได้ดังนี้
อ้างอิง
คราวนี้ทำบล๊อกจาก Falling Bullets - Blog - WordPress Clone in 27 Seconds (Part 1 of 40)
โดย
http://www.example.com/dj
http://dj.example.com
เป็นต้น)/dj
ไปก่อน/home/user1/www
user1
รหัสผ่านคือ USER1_PASSWORD
มีสิทธิ์ในการสร้างฐานข้อมูล/home/user1/www/dj
สร้างแอพลิเคชั่นชื่อ 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 <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 "/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
ได้ดังนี้
จากครั้งก่อน django: ใช้กับ apache2 บนเดเบียน (มีการปรับปรุงให้เนื้อหาสมบูรณ์ขึ้นในหน้าเก่าด้วย)
ตอนนี้เราจะมาสร้างแอพลิเคชั่นชื่อ "to do" จาก sitepoint.com - Django Djumpstart: Build a To-do List in 30 Minutes
โดย
http://www.example.com/dj
http://dj.example.com
เป็นต้น)/dj
ไปก่อน/home/user1/www
user1
รหัสผ่านคือ USER1_PASSWORD
มีสิทธิ์ในการสร้างฐานข้อมูล/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
ตอนนี้ยังไม่มีอะไร เพราะเรายังไม่ได้ใส่อะไรเข้าไป
ถึงตอนนี้เราสามารถใส่เนื้อหาใหม่เข้าไปได้ ผ่านทางหน้า 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 รุ่น svn (ระหว่าง 0.96-)
$ sudo aptitude install python-docutils
$ 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% ไปเป็นตัวเลขอื่นก็ปรับเฉพาะตัวนี้ตัวเดียว
เมื่อแปลงแล้วได้ภาพดังนี้