Customize the Django newform admin UI

Python, Web January 11th, 2009

One of the exciting features Django 1.0 brings to the table is the integration of newform into the admin UI. Hat off to Brian, great work.

The essential workload in the Pattee’s admin UI is to input the meta information of the book, then link it to the eBook file. It is more convenient to take the advantage of Amazon Web Service, so the user scenario is:

  1. Search Amazon by keyword or ISBN
  2. Select the correct book
  3. Update the eBook file
  4. Save it to the database
Add a Book

It is straightforward to override the default template and implementation: redirect the admin URL to our own version of change_form.html template and add_view implementation, then save the object regardless the data integration; or we may reuse the infrastructure, and just implement our required function. Pattee takes the latter approach.

First, declare the BookAdmin and eBookAdmin in admin.py, and hook eBook inline:

class eBookAdmin(admin.StackedInline):
    model = eBook
    extra = 1

class BookAdmin(admin.ModelAdmin):
    form = BookForm
    inlines = [eBookAdmin]
    change_form_template = ‘book_change_form.html’

and don’t forget to register it to admin.site, which is required by Django 1.0.

admin.site.register(Book, BookAdmin)

Then in BookForm, we could override full_clean to provide the cleaned_data. And in BookAdmin, the following tricks will guard against duplication of Book instances:

def save_form(self, request, form, change):
        if form.is_valid():
            try :
                instance = self.model.objects.get(isbn = form.cleaned_data[‘isbn’])
            except ObjectDoesNotExist:
                return super(BookAdmin, self).save_form(request, form, change)
            return instance

That is all. You may consider to check out the source code for details:

svn checkout http://my-svn.assembla.com/svn/pattee/trunk pattee

Restart the Django engine

Python, Web January 10th, 2009

It has been one year since the last update of my Learning Django by Example series, partially in account of the demanding day job, the main reason lies in that the project is bloated with some advanced features which delay scratching my itch. Thanks to the snow storm in the holiday season, our vacation to Las Vegas was canceled, I had time to restart the Django engine.

The new project, Pattee, named after the library in Penn State University, aims to a simple, stupid eBook management system. It is so simple that it even does not have a dedicated UI, only integrated to a third-party web application(currently, douban.com). You may check it out at here:

svn checkout http://my-svn.assembla.com/svn/pattee/trunk pattee

There is no step-by-step tutorial to follow the footprints of the Django Book, each post will target a specific topic.

I may post

Rewrite WordPress and ZenPhoto for Nginx

Web October 15th, 2008

Nginx also supports URL rewrite, not compatible to Apache’s mod_rewrite, but more intuitive and more powerful imho. The only problem is most applications, WordPress and ZenPhoto for this specific case do include the mod_rewrite code snippet and/or may update the .htaccess for your convenience.

Thanks to the Slicehost community, the port of mod_rewrite rules perfectly covers WordPress and SuperCache. Here are some minor modification to craft for more general usage:

# the blog dir, aka where index.php is
set $blog_dir ”;
# the wordpress dir where all wp-* stays
set $wordpress_dir ‘/wordpress’;
include wordpress.rewrite;

In nginx.conf, define wordpress_dir and blog_dir, these two variables are equivalent to WordPress address (URL) and Blog address (URL) stripped off the host information. Then we can replace the hard-coded /blog path by using $wordpress_dir or $blog_dir:

2d1
<
26c25
< set $supercache_file /blog/wp-content/cache/supercache/$http_host/$1index.html;
---
> set $supercache_file $wordpress_dir/wp-content/cache/supercache/$http_host/$1index.html;
36c35
< rewrite . /blog/index.php last;
---
> rewrite ^(.*)$ $blog_dir/index.php?q=$1 last;

Here is my zenphoto.rewrite, it seems sivel’s more concise. Either of these should work.

HOWTO: Serve virtual host with Nginx

Web September 6th, 2008

As the limited memory budget, and I plan to host multiple website in the VPS, I decided to take a less versatile, but lightweight Apache alternative, the Nginx made by the polar bear.

There is no RPM in the repository I have enlisted, so let’s fallback the old-school way:

# remove the blocking glibc-dummy-centos-4 package, then get the toolchain:
yum remove glibc-dummy-centos-4
yum -y install gcc openssl-devel
# Now it is time to build the nginx:
./configure –prefix=/opt/nginx –with-http_ssl_module –with-http_stub_status_module
make && sudo make install

Then get the PHP with FastCGI support, and the lighttpd-fastcgi for the fastcgi loader.

yum install php-cli php-mysql lighttpd-fastcgi

Here is the nginx.conf that server.

user nobody;
worker_processes 2;
pid logs/nginx.pid;
error_log logs/error.log;

events {
worker_connections 2048;
use epoll;
}

http {
include mime.types;
include fastcgi_params;
default_type application/octet-stream;

log_format main ‘$remote_addr – $remote_user [$time_local] $request ‘
‘”$status” $body_bytes_sent “$http_referer” ‘
‘”$http_user_agent” “$http_x_forwarded_for”‘;

access_log logs/access.log main;
client_header_timeout 3m;
client_body_timeout 3m;
send_timeout 3m;

client_header_buffer_size 1k;
large_client_header_buffers 4 4k;

gzip on;
gzip_min_length 1100;
gzip_buffers 4 8k;
gzip_types text/plain;
gzip_static on;

output_buffers 1 32k;
postpone_output 1460;

sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 75 20;

server {
#REVIEW: how to redirect https? using re?
server_name www.kunxi.org;
rewrite ^(.*) http://kunxi.org$1 permanent;
}

server {
# kunxi’s gallery
listen 80;
server_name gallery.kunxi.org;
root /home/webadmin/$host;
access_log logs/$host.log main;

location / {
root /home/webadmin/$host;
index index.html index.htm index.php;
include zenphoto.rewrite;
}

# pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
location ~ \.php$ {
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
}

# deny access to .htaccess files.
location ~ /\.ht {
deny all;
}
}

server {
# kunxi’s sites
listen 80;
server_name kunxi.org *.kunxi.org;
root /home/webadmin/$host;

error_page 404 $document_root/404.html;
error_page 500 502 503 504 $document_root/50x.html;
access_log logs/$host.log main;

location / {
root /home/webadmin/$host;
index index.html index.htm index.php;

# the blog dir, aka where index.php is
set $blog_dir ”;
# the wordpress dir where all wp-* stays
set $wordpress_dir ‘/wordpress’;

include wordpress.rewrite;
}

# rewrite the /files/
#
location /files/ {
alias /home/webadmin/static.kunxi.org/;
}

# pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
#
location ~ \.php$ {
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
}

# deny access to .htaccess files.
#
location ~ /\.ht {
deny all;
}
}
}

Some highlights of the configuration:
Rewrite www.kunxi.org to kunxi.org, yes, we support no-www!

server {
#REVIEW: how to redirect https? using re?
server_name www.kunxi.org;
rewrite ^(.*) http://kunxi.org$1 permanent;
}

And this wildcards will cover all sub-domains powered by PHP:

server_name kunxi.org *.kunxi.org;
root /home/webadmin/$host;
… ….

Home-brewed nginx and fastcgi init scripts to make it works after the reboot:

chkconfig –add nginx
chkconfig –add fcgi-php
service start nginx
service start fcgi-php

Tips and Traps:
Nginx supports 0 downtime upgrade, so if your nginx.conf is wrong, the server would ignore it and suck up the complain. Make sure stop the nginx service and start nginx during debugging rewrite rules.

fcgi-php seems to have problem to parse localhost, so I use 127.0.0.1 instead.

The rewrite rule for WordPress and ZenPhoto are explained here and here. (TODO).

Move to the new VPS, Yummy

Web September 4th, 2008

I finally ride the trend of VPS, yes, I just bought Economy VPS COS4x64 with 512M RAM, 200G HD and 350G bandwidth. The host is visualized via virtuozzo, and loaded with CentOS 4.4.

The package management of RPM is quite lacking, so the first thing is to get YUM working:

rpm -Uvh http://mirror.centos.org/centos/4/os/x86_64/CentOS/RPMS/yum-2.4.3-4.el4.centos.noarch.rpm
rpm -Uvh http://mirror.centos.org/centos/4/os/x86_64/CentOS/RPMS/python-elementtree-1.2.6-5.el4.centos.x86_64.rpm
rpm -Uvh http://mirror.centos.org/centos/4/os/x86_64/CentOS/RPMS/python-sqlite-1.1.7-1.2.1.x86_64.rpm
rpm -Uvh http://mirror.centos.org/centos/4/os/x86_64/CentOS/RPMS/sqlite-3.3.6-2.x86_64.rpm
rpm -Uvh http://mirror.centos.org/centos/4/os/x86_64/CentOS/RPMS/python-sqlite-1.1.7-1.2.1.x86_64.rpm
rpm -Uvh http://mirror.centos.org/centos/4/os/x86_64/CentOS/RPMS/python-urlgrabber-2.9.8-2.noarch.rpm
rpm -Uvh http://mirror.centos.org/centos/4/os/x86_64/CentOS/RPMS/yum-metadata-parser-1.0-8.el4.centos.x86_64.rpm
rpm -Uvh http://mirror.centos.org/centos/4/os/x86_64/CentOS/RPMS/yum-2.4.3-4.el4.centos.noarch.rpm
yum update

Since I have been spoiled by Gentoo’s portage, the upgrade of CentOS makes little sense to me: wipe off the disk, then reinstall the OS. The upgrade from CentOS 4 to CentOS is quite hair-rising, and I was scared off by the tech support. So I would stick to CentOS 4 right now.

The next step is to configure the repository:

wget -P /etc/yum.repos.d http://jpackage.org/jpackage.repo
wget -P /etc/yum.repos.d http://centos.karan.org/kbsingh-CentOS-Extras.repo
wget -P /etc/yum.repos.d http://centos.karan.org/kbsingh-CentOS-Misc.repo
wget -P /etc/yum.repos.d http://www.sipfoundry.org/pub/sipX/3.2/sipx-centos.repo
wget -P /etc/yum.repos.d http://dev.centos.org/centos/4/CentOS-Testing.repo

And manually add the following repos as well

# /etc/yum.repos.d/dag.repo
[dag]
name=Dag RPM Repository for Red Hat Enterprise Linux
baseurl=http://apt.sw.be/redhat/el$releasever/en/$basearch/dag
gpgcheck=1
enabled=1

# /etc/yum.repos.d/utterramblings.repo
[utterramblings]
name=Jason’s Utter Ramblings Repo
baseurl=http://www.jasonlitka.com/media/EL$releasever/$basearch/
enabled=1
gpgcheck=1
gpgkey=http://www.jasonlitka.com/media/RPM-GPG-KEY-jlitka

[dries]
name=Extra Fedora rpms dries – $releasever – $basearch
baseurl=http://ftp.belnet.be/packages/dries.ulyssis.org/redhat/el4/en/i386/dries/RPMS
gpgcheck=1
enabled=1

[atrpms]
name=Fedora Core $releasever – $basearch – ATrpms
baseurl=http://dl.atrpms.net/fc$releasever-$basearch/atrpms/stable
gpgkey=http://ATrpms.net/RPM-GPG-KEY.atrpms
gpgcheck=1enabled=1

Import the GPG keys as well:

rpm –import http://centos.karan.org/RPM-GPG-KEY-karan.org.txt
rpm –import http://dries.ulyssis.org/rpm/RPM-GPG-KEY.dries.txt
rpm –import http://dag.wieers.com/packages/RPM-GPG-KEY.dag.txt
rpm –import http://ATrpms.net/RPM-GPG-KEY.atrpms

And accelerate YUM by cache the meta data locally:

yum makecache

With YUM, it is much easier to start the engine.