SaltyCrane Blog — Notes on JavaScript and web development

Using wmii with Gnome

Thanks to Glyph, I can now use wmii as the window manager for Gnome. I like wmii because it makes good use of screen real estate, is keyboard-navigatable, is scriptable1, and uses minimal resources2.

It is possible to use gnome-panel within wmii-- just press MOD+P gnome-panel RET. And you can manually fine tune your .xinitrc or .xsession by adding your required stuff such as gnome-power-manager and nm-applet. (This is what I had been doing (and may continue to do depending on how this full on Gnome thing works out).)

If, for some strange reason (e.g. you want to use an annoying3 Adobe Air application), you want to use the full Gnome desktop with wmii, that is possible also. To do this, start up Gnome and go to System, Preferences, Sessions. Uncheck Window Manager, then click Add and fill in the path to your wmii executable. Glyph also suggests running gconf-editor and turning off /apps/nautilus/preferences/show_desktop. After logging out and logging in, you should now have wmii as the window manager for Gnome.

Note, I've only used this Gnome+wmii configuration about a day, so I'm not sure if I will keep it or not. I have found TweetDeck will load up but has some disappearing act problems. TwitterLocal on the other hand, seems to run fine.

Update 2010-07-18: I only used the full Gnome+wmii configuration for a couple days before going back to pure wmii. However, I am retrying this configuration with Ubuntu 10.04 Lucid again since I have a more powerful computer now and I need extra non-hacker type stuff to just work without trying to figure out which program provides it... So it looks like Ubuntu 10.04 no longer has the Sessions item in the Preferences menu. I tried using gconf-editor and changin /desktop/gnome/session/required_components/windowmanager from gnome-wm to the path to my wmii executable but that didn't work. If you know how to do it, let me know.


  1. Yes I have written one script for wmii. It allows me to change column widths using the keyboard instead of the mouse. It's a bit of a hack, but maybe I will make another post for that sometime. Update 2009-04-12: here is that post
  2. Of course, using Gnome with wmii negates this advantage. When I bought my Dell 530N with 1GB of RAM, I had planned to purchase more RAM separately because it was so much cheaper. I suppose I ought to do that sometime. Of course at work, my computer had only half a gig of RAM and I was running out of memory continually.
  3. I call Air annoying because it only works in Gnome or KDE (and to add another alliterative adjective). Otherwise, I don't know much about it.

Card store project #5: Redirecting my www-prefixed domain to my non-www-prefixed domain

For search engine optimization and analytics, I wanted to make http://www.handsoncards.com/ permanently redirect to http://handsoncards.com/. So I started Googling. It took me a while to figure this one out... The first few articles I read suggested using Rewrite directives in my .htaccess file but this didn't work for me (maybe because I'm using mod_python and Django?). Then I found I could use Rewrite in my httpd.conf file, but I got an infinite redirect loop. Finally, I found the solution from this discussion. Here's what I did. I'm using Django, mod_python, and Apache on Ubuntu at Slicehost.

  • Enable mod_rewrite. Logged in as root:
    a2enmod rewrite
  • Edit /etc/apache2/httpd.conf:
    <VirtualHost *>
        ServerName handsoncards.com
        ServerAlias www.handsoncards.com
        #
        RewriteEngine on
        RewriteCond %{HTTP_HOST} ^www\.handsoncards\.com
        RewriteRule (.*) http://handsoncards.com$1 [R=301,L] 
        #
        SetHandler python-program
        PythonHandler django.core.handlers.modpython
        SetEnv DJANGO_SETTINGS_MODULE handsoncards.settings
        PythonPath "['/srv/HandsOnCards', '/srv/python-packages'] + sys.path"
        PythonDebug Off
    </VirtualHost>
  • Restart Apache:
    /etc/init.d/apache2 restart

Card store project #4: Notes on using Amazon's CloudFront

I haven't been keeping up with the current events very well recently, but I haven't noticed a lot of people using Amazon's S3 or CloudFront with Django on VPS hosting. Though there is Adrian's post from 2006, I see more articles about serving media files with lighttpd or, more recently, nginx. Is a CDN unnecessary for our needs? I thought it'd be good to take some load off my VPS server since I need all the memory I can get for my Django web server and database. But maybe web servers such as nginx are so lightweight it doesn't make much of an impact? I didn't think the cost would be too much-- on this blog, I'm only paying about $0.10/month for S3 services to serve my static media. Of course, there isn't a lot of static media to serve on this blog, but it still seems like it would be a fraction of the $20/month I'm paying for VPS at Slicehost. It may be the convenience factor-- because every time I update a static file, I then have to upload it to S3. This is even more inconvenient for files uploaded through the admin interface. I think some people have probably solved this already... maybe using Django signals. Maybe it is a combination of all these things. Please let me know what you think. If you're not using S3/CloudFront, why aren't you?

Well I went ahead and gave CloudFront a try since it is so easy. My card store project website seems to be somewhat faster than before. Please check it out here. I'm still not sure if I should be happy with the site's speed though. I did a quick memcached install, but I don't think I've configured it properly. I will probably need to revisit that. Anyways, here are my notes on using CloudFront with my Satchmo store.

Sign up for S3

Get S3 Python library

Create a S3 bucket

  • Create a file named create_bucket.py:
    import S3
    
    ACCESS_KEY = 'myaccesskey'
    SECRET_KEY = 'mysecretaccesskey'
    BUCKET_NAME = 'handsoncards'
    
    conn = S3.AWSAuthConnection(ACCESS_KEY, SECRET_KEY)
    conn.create_bucket(BUCKET_NAME)
  • Run it:
    python create_bucket.py

Upload files to S3

  • Download Adrian's S3 upload script and save it to /srv/HandsOnCards/handsoncards/bin/update_s3.py
  • Edit the script with the correct values for AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, and BUCKET_NAME.
  • Upload files. (Assumes static directory is linked to /var/www/site_media).
    cd /var/www
    find -L site_media | grep -v '~$' | python /srv/HandsOnCards/handsoncards/bin/update_s3.py
    find -L admin_media | grep -v '~$' | python /srv/HandsOnCards/handsoncards/bin/update_s3.py

Set up CloudFront

  • Sign up for CloudFront
  • Get the S3 Fox firefox plugin
  • Click "Manage Accounts" and enter access key and secret key
  • Right click on your bucket (handsoncards) and select "Manage Distributions" Enter a "Comment" and optional CNAME, then click "Create Distribution".
  • Wait a while while the distribution is created. Take note of the "Domain Name". For me it is: http://d16z1yuk7jeryy.cloudfront.net
  • Click the refresh button until the "Status" says "Deployed"

Update settings and templates to use CloudFront

  • In settings.py set MEDIA_URL and ADMIN_MEDIA_PREFIX as follows:
    MEDIA_URL = 'http://d16z1yuk7jeryy.cloudfront.net/site_media/'
    ADMIN_MEDIA_PREFIX = 'http://d16z1yuk7jeryy.cloudfront.net/admin_media/'
  • In your base.html template and all other templates, replace /site_media/ with http://d16z1yuk7jeryy.cloudfront.net/site_media/.

Update 2009-06-08: Add "Expires" headers

For better performance, it is good to add a far-future "Expires" header to static content on S3. To do this I modified Adrian's script to set the "Expires" header to be one year in the future as shown below. Thanks to orip for this tip.

from datetime import datetime, timedelta
import mimetypes
import os.path
import sys
import S3 # Get this from Amazon

AWS_ACCESS_KEY_ID = 'CHANGEME'
AWS_SECRET_ACCESS_KEY = 'CHANGEME'
BUCKET_NAME = 'CHANGEME'

def update_s3():
    conn = S3.AWSAuthConnection(AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY)
    for line in sys.stdin:
        filename = os.path.normpath(line[:-1])
        if filename == '.' or not os.path.isfile(filename):
            continue # Skip this, because it's not a file.
        print "Uploading %s" % filename
        filedata = open(filename, 'rb').read()
        expires = datetime.utcnow() + timedelta(days=365)
        expires = expires.strftime("%a, %d %b %Y %H:%M:%S GMT")
        content_type = mimetypes.guess_type(filename)[0]
        if not content_type:
            content_type = 'text/plain'
        conn.put(BUCKET_NAME, filename, S3.S3Object(filedata),
            {'x-amz-acl': 'public-read', 
             'Content-Type': content_type,
             'Expires': expires,
             })

if __name__ == "__main__":
    update_s3()

For more information:

Update 2009-10-21: Add CNAME record

Go to your DNS Zone manager and add a CNAME record with the following parameters:

  • Type: CNAME
  • Name: static
  • Data: d16z1yuk7jeryy.cloudfront.net. (Don't forget the period at the end!)
  • TTL: whatever you want. I left it at 86400

Now wherever I previously would have used http://d16z1yuk7jeryy.cloudfront.net, I can replace it with http://static.handsoncards.com.

Postgres backup with cron

So I accidentally deleted my Postgres database on my public server instead of my local server last night. Luckily, I was still in the testing phase. I had been meaning to figure out how to backup Postgres but had been putting it off. Now I've been provided the proper motivation.

The PostgreSQL documentation has a whole chapter devoted to backup. There it describes three methods of backup: SQL dump, file system level backup, and continuous archiving. I chose SQL dump.

Manual SQL dump

To do a manual dump, here is what I did. (As you might guess, handsoncards_db is my database name.)

su postgres
pg_dump handsoncards_db > /tmp/testdump
exit

Schedule backups with cron

To perform backups at regular intervals I used cron. Logged in as root, I created a file handsoncards_db_backup in /etc/cron.d. (Note, I'm running Ubuntu Intrepid. Cron will automatically start running this new job without a restart.)

# m  h  dom mon dow user     command
  45 3  *   *   *   postgres pg_dump handsoncards_db > /srv/backups/handsoncards_db_backup
  

This will create a backup at 3:45am every day. Be sure to put a newline at the end of the file.

Finally I created the backup directory and made postgres the owner.

mkdir -p /srv/backups
chown postgres:postgres /srv/backups

Restore a database from the dump file

If necessary, delete the old database. Then create a new databse and restore from the dump file.

su postgres
psql template1
CREATE DATABASE handsoncards_db OWNER django_user ENCODING 'UTF8';
\q
psql handsoncards_db < /srv/backups/handsoncards_db_backup
exit

NOTE: if you get a permission denied error when trying to restore, check the Unix permissions on the backup file and all the parent directories.

Card store project #3: Installing Satchmo, part 2

Here are my initial steps in setting up my Satchmo store. It is meant to be a continuation of my previous post, Installing Satchmo, Django, PostgreSQL, and Apache on Ubuntu at Slicehost. I am using Satchmo version 0.8, released 2008-11-25. I am combining the instructions from the Satchmo documentation and Bruce's blog post. I'm sorry if this post is redundant-- I just wanted to have all the notes in one place for my reference. Almost all the instructions here are from one of these two sources.

Here is my final project directory structure mostly copied from Bruce's post:

HandsOnCards/
`-- handsoncards/
    |-- __init__.py
    |-- bin/
    |-- local_settings.py
    |-- manage.py*
    |-- satchmo.log
    |-- settings.py
    |-- static/
    |   |-- css/
    |   |   |-- blackbird.css*
    |   |   `-- style.css
    |   |-- images/
    |   |   |-- blackbird_icons.png
    |   |   |-- blackbird_panel.png
    |   |   |-- productimage-picture-default.jpg
    |   |   `-- sample-logo.bmp
    |   |-- js/
    |   |   |-- blackbird.js
    |   |   |-- jquery.cookie.js
    |   |   |-- jquery.form.js
    |   |   |-- jquery.js
    |   |   |-- satchmo_checkout.js
    |   |   |-- satchmo_core.js
    |   |   |-- satchmo_pay_ship.js
    |   |   `-- satchmo_product.js
    |   `-- protected/
    |-- store/
    |   |-- __init__.py
    |   |-- models.py
    |   |-- templatetags/
    |   |   `-- __init__.py
    |   |-- urls.py
    |   `-- views.py
    |-- templates/
    |   `-- store/
    `-- urls.py

Set up project

  • Create directory structure:
    cd /srv
    mkdir HandsOnCards
    cd HandsOnCards
    /srv/Django-1.0.2-final/django/bin/django-admin.py startproject handsoncards
    cd handsoncards
    mkdir bin
    mkdir -p templates/store
    ./manage.py startapp store
    mkdir -p store/templatetags
    touch store/templatetags/__init__.py
    
  • Create log file
    touch satchmo.log
    chmod 666 satchmo.log
  • Create the cache directory:
    mkdir django_cache
    chmod 777 django_cache
  • Copy settings files:
    cp /srv/satchmo-0.8/satchmo/local_settings-customize.py local_settings.py
    cp /srv/satchmo-0.8/satchmo/settings-customize.py settings.py
  • Set the Python path:
    export PYTHONPATH=/srv/python-packages:/srv/HandsOnCards
  • Copy static files:
    python manage.py satchmo_copy_static
    chmod 777 static/css
    chmod 777 static/images
    chmod 777 static/js
  • Set the root URLconf. Edit settings.py:
    ROOT_URLCONF = 'handsoncards.urls'
  • Create handsoncards/store/urls.py to only contain the following:
    from django.conf.urls.defaults import *
    from satchmo.urls import urlpatterns
  • Edit handsoncards/urls.py to only contain the following:
    from django.conf.urls.defaults import *
    from handsoncards.store.urls import urlpatterns
  • Configure templates. Edit local_settings.py:
    SATCHMO_DIRNAME = '/srv/python-packages/satchmo'
    DIRNAME = os.path.abspath(os.path.dirname(__file__).decode('utf-8'))
    TEMPLATE_DIRS = (
        os.path.join(DIRNAME, "templates/store"),
        os.path.join(DIRNAME, "templates"),
        os.path.join(SATCHMO_DIRNAME, "templates"),
    )
    (I also commented out TEMPLATE_DIRS in settings.py since this replaces it.)
  • Install the store app. Edit settings.py:
    INSTALLED_APPS = (
      [...]
      'handsoncards.store', #should usually be last
    )
  • From my previous post, use the following database settings in settings.py:
    DATABASE_ENGINE = 'postgresql_psycopg2'
    DATABASE_NAME = 'django_db'
    DATABASE_USER = 'django_user'
    DATABASE_PASSWORD = 'django_password'
    DATABASE_HOST = ''
    DATABASE_PORT = ''

Configure misc. settings

In settings.py:

LOCAL_DEV = False
ADMINS = (
    (Sofeng', [email protected]'),
)
TIME_ZONE = 'America/Los_Angeles'
#LANGUAGE_CODE = 'en-us.utf8'
MEDIA_ROOT = os.path.join(DIRNAME, 'static/')
MEDIA_URL = '/site_media/'
ADMIN_MEDIA_PREFIX = '/admin_media/'
SECRET_KEY = 'yoursecretkeyhere'
SATCHMO_SETTINGS = {
    'SHOP_BASE' : '/shop',
    [...]
}

In local_settings.py:

SITE_DOMAIN = "handsoncards.com"
SITE_NAME = "HandsOnCards.com"
CACHE_BACKEND = "file://" + DIRNAME + "/django_cache"

Test and install data

  • Set the Python path:
    export PYTHONPATH=/srv/python-packages:/srv/HandsOnCards
  • Test Satchmo setup:
    python manage.py satchmo_check
    Results:
    Checking your satchmo configuration.
    Using Django version 1.0.2 final
    Using Satchmo version 0.8
    Your configuration has no errors.
  • Create database tables:
    python manage.py syncdb
    Go ahead and create a superuser
  • Load country data:
    python manage.py satchmo_load_l10n
  • Load US tax table:
    python manage.py satchmo_load_us_tax

Set up httpd.conf and static media links

  • Create symbolic links in /var/www:
    cd /var/www
    ln -s /srv/python-packages/django/contrib/admin/media admin_media
    ln -s /srv/HandsOnCards/handsoncards/static site_media
  • Edit /etc/apache2/httpd.conf:
    <location "/">
        SetHandler python-program
        PythonHandler django.core.handlers.modpython
        SetEnv DJANGO_SETTINGS_MODULE handsoncards.settings
        PythonPath "['/srv/HandsOnCards', '/srv/python-packages'] + sys.path"
        PythonDebug Off
    </location>
    <location "/site_media">
        SetHandler None
    </location>
    <location "/admin_media">
        SetHandler None
    </location>
  • Restart apache:
    /etc/init.d/apache2 restart

View the store

Navigate to http://[your domain or slice ip address]/shop/ in your browser. You should see an empty test store.

Create a Store Configuration

  • Go to http://[your domain or slice ip address]/admin/ and log in.
  • Update the "Site". Click on "Sites". Click on "example.com" and set your domain.
  • Under the "Shop" heading, click on "Store Configurations". Click "Add Store Configuration" and fill in the information.

Create categories and products

  • Create a Category:
    • Next to "Categories", click "Add"
    • Fill in the data
  • Create a Product:
    • Next to "Products", click "Add"
    • Fill in the data

Customize templates and CSS

  • Copy the desired templates from /srv/satchmo-0.8/satchmo/templates to /srv/HandsOnCards/handsoncards/templates/store and edit them.
  • To customize the style, edit handsoncards/static/css/style.css

Redirect '/' to '/shop/'

  • Edit handsoncards/urls.py as follows:
    from django.conf.urls.defaults import *
    from handsoncards.store.urls import urlpatterns
    
    urlpatterns += patterns(
        '',
        (r'^$', 'django.views.generic.simple.redirect_to', {'url': '/shop/'}),
    )

Card store project #2: Installing Satchmo, Django, PostgreSQL, and Apache on Ubuntu at Slicehost

As I mentioned in my previous post, I'm planning to set up an e-commerce site using the Satchmo shopping cart framework. Satchmo is built on the Django web framework which is written in the Python programming language. Satchmo has a lot of features built in which means it saves you a lot of work implementing them yourself. Check out this video introduction to Satchmo at this year's DjangoCon for more information.

After reading this discussion on the Satchmo mailing list, I decided to use Slicehost for hosting my site. Their cheapest plan provides 256MB of RAM for $20/month. Here are my notes on setting up Satchmo and Django with PostgreSQL, Apache, and mod_python on Ubuntu Intrepid at Slicehost. It seems lighttpd and nginx are popular lightweight alternatives to Apache on VPS setups. I do not know too much about this, but may explore these in the future.

Note, I don't describe how to setup a Satchmo project below. Maybe I will write another post about that later... For more information, see the Satchmo Installation documentation. Update 2008-12-12: I did write another blog post-- see Installing Satchmo, part 2.

Setup Slicehost

  • Sign up for an account at http://www.slicehost.com/ I chose Ubuntu Intrepid for my operating system and named my slice "toad".
  • I followed the following two excellent Slicehost tutorials: Ubuntu Setup Part 1 and Part 2.
  • An important step is setting the system locale. Since I'm in the United States, I used the following commands (run as root):
    locale-gen en_US.UTF-8
    update-locale LANG=en_US.UTF-8

Install apache, mod_python, postgresql, and postgres python bindings

  • Run as root (or use sudo):
    apt-get update
    apt-get upgrade
    apt-get dist-upgrade
    apt-get install apache2 apache2-mpm-prefork
    apt-get install libapache2-mod-python
    apt-get install postgresql
    apt-get install python-psycopg2

Configure apache

  • I used the following Slicehost tutorials: Install Apache, Configure Apache Part 1, Configure Apache Part 2
  • Of particular note, when installing apache, I got this warning:
    apache2: Could not reliably determine the server's fully qualified domain name, using 127.0.0.1 for ServerName
    Fix it by setting the ServerName in /etc/apache2/apache2.conf:
    ServerName yourdomain.com
    ServerTokens Prod
    See the Apache docs for more information on ServerName.

Install Django 1.0.2

  • Run the following as root:
    cd /srv
    wget http://www.djangoproject.com/download/1.0.2/tarball/
    
    tar zxvf Django-1.0.2-final.tar.gz
    mkdir python-packages
    cd python-packages
    ln -s ../Django-1.0.2-final/django

Test Django (optional)

  • Run the following as root:
    export PYTHONPATH=/srv/python-packages
    /srv/Django-1.0.2-final/django/bin/django-admin.py startproject testdjango
  • Edit /etc/apache2/httpd.conf:
    <location "/">
        SetHandler python-program
        PythonHandler django.core.handlers.modpython
        SetEnv DJANGO_SETTINGS_MODULE testdjango.settings
        PythonPath "['/srv', '/srv/python-packages'] + sys.path"
        PythonDebug On
    </location>
  • Restart apache:
    /etc/init.d/apache2 restart
    Navigate to your slice's IP address in your browser and you should see the Django "It worked!" page.

Install Satchmo

Satchmo requires a number of external Python packages besides Django. Unfortunately, there isn't one standard way of installing Python packages. I decided to install most of them using Ubuntu's APT package management and one of them using easy_install, and one of them from source. Alternatively, I could have installed most of the packages with easy_install, and one of them with APT (and one of them from source). Or, I could have done it another way... sigh.

Note, I use the directory /srv/python-packages to store symbolic links to my needed python packages. Then I add this directory to my Python Path in the PYTHONPATH environment variable or in the Apache httpd.conf file. See my post, Somewhere on your Python path for more information.

  • Install Satchmo prerequisites. Run the following commands as root:
    Install APT packages:
    apt-get update
    apt-get upgrade
    apt-get install python-crypto
    apt-get install python-yaml
    apt-get install python-imaging
    apt-get install python-reportlab
    apt-get install python-trml2pdf

    Install django-registration using easy_install:
    apt-get install python-setuptools python-dev build-essential
    easy_install django-registration

    Install comment_utils source:
    apt-get install subversion
    mkdir -p /srv/python-packages/dist
    cd /srv/python-packages/dist
    svn co http://django-comment-utils.googlecode.com/svn/trunk/comment_utils/ 
    mv comment_utils comment_utils_rev92
    cd /srv/python-packages
    ln -s dist/comment_utils_rev92 comment_utils
  • Install Satchmo 0.8. Run the following as root: Update 2008-12-12: I changed this to use release 0.8 instead of the SVN trunk version.
    cd /srv
    wget http://www.satchmoproject.com/snapshots/satchmo-0.8.tgz
    tar zxvf satchmo-0.8.tgz
    cd /srv/python-packages
    ln -s ../satchmo-0.8/satchmo
    To install the SVN trunk version instead,
    cd /srv
    svn co svn://satchmoproject.com/satchmo/trunk
    mv trunk satchmo_revXXXX
    cd /srv/python-packages
    ln -s ../satchmo_revXXXX/satchmo

Setup PostgreSQL

I followed the instructions at Punteney.com for setting up Django and Postgres on Slicehost.

  • If you didn't already install Postgres above, run the following as root:
    apt-get install postgresql
    apt-get install python-psycopg2
  • Change the password for the "postgres" Unix user. (run as root or use sudo)
    passwd -d postgres
    su postgres -c passwd
  • Change password for "postgres" user in the database. (From Punteney's article, "It's convenient to have the two passwords match, but not required.")
    su postgres -c psql template1
    ALTER USER postgres WITH PASSWORD 'postgres_user_password';
    template1=\q
  • Create django user:
    su postgres
    createuser -P django_user
    Punteney says to answer no to all here. Remember the password to put in Django settings.py file. (For this example, I set the password to my_password.)
  • Create a database. Still su'ed as the "postgres" user:
    psql template1
    CREATE DATABASE django_db OWNER django_user ENCODING 'UTF8';
    \q
    exit
    (exit from postgres su)
  • Configure access to the database. Edit /etc/postgresql/8.3/main/pg_hba.conf:
    local   all         postgres                          ident sameuser
    local   django_db   django_user                       md5
  • Restart the postgres server:
    /etc/init.d/postgresql-8.3 restart
  • In your Django project, use the following database settings in settings.py:
    DATABASE_ENGINE = 'postgresql_psycopg2'
    DATABASE_NAME = 'django_db'
    DATABASE_USER = 'django_user'
    DATABASE_PASSWORD = 'my_password'
    DATABASE_HOST = ''
    DATABASE_PORT = ''
  • Run syncdb:
    cd /srv/yourproject
    python manage.py syncdb

Setup a domain name

  • Register for a domain name. Google for "domain registration" for options.
  • Follow the Slicehost directions for creating DNS records. This includes setting the nameservers at your domain registration service to use Slicehost's nameservers (ns1.slicehost.net, ns2.slicehost.net, ns3.slicehost.net).

Card store project #1: Plans

I'm planning to start an online greeting card / stationary store with my wife. My wife (and possibly my sister and other friends) will create designs for the cards, while I'll run the website and manage the business. I'll admit, I don't know the first thing about running a business, but, for a number of reasons, I still want to try. If you have any advice on running an online business, I'd appreciate any feedback. Here are some of my plans for the store. I've already started on some of them and will write more details as I complete them.

General plans

My general plan is to have my wife, and possibly other artists, draw card designs using colored pencils, ink, and other media. I'll scan the designs, lay out the cards, and print them. We had thought about doing custom, hand-made cards for each order, but my wife doesn't have a lot of time, and I didn't want to burden her with making tens of millions of cards when our store hits the big time tomorrow. I plan to make the cards on-demand, with quick turn around times by automating as much of the process as possible. In the beginning we'll do things manually, because I don't expect many orders. Then later, I'll add Python scripts, Twisted Perspective Broker communication, and custom robotic cutting, folding, and mail delivery. Finally, I plan to take over the world.

Desktop publishing plans

A degree in Art, or Graphic Design would probably be a whole lot more useful than my degree in Engineering. If I did have one of those degrees, I would probably be doing most of my work on a Mac with Photoshop, Illustrator, InDesign, etc. But, since I'm a geeky engineer and programmer, I'm going to use Linux, the Gimp, and deal with all the hardware/software incompatibilities that come with it. Besides Gimp, I plan to use Scribus, a pretty cool open-source desktop publishing program. For printing, I've already purchased a Xerox Phaser 8560DN solid-ink printer. I've done some preliminary printing, and there has been some pain, as my wife can attest, but overall it is a pretty cool printer that works well with Linux. The biggest hurdle, I think, will be figuring out the ICC profiles / color management stuff. I'll post my Linux Phaser 8560 setup notes when I have everything in order.

Business-related plans

I plan to run the business as a sole proprietorship since it is the simplest of the business structure options. As far as I can tell from preliminary reading, I will need to get a city business license, a sales tax license, a state employer identification number, and probably a ficticious business name. For more information, see the U.S. Small Business Administration guide.

Website plans

Regarding website plans, I need to do a lot of work to learn the front end web development such as design, CSS, and Javascript. On the back end, I have a little more experience, though, again, I still have a lot to learn. Here are my website technical plans.

  • Satchmo: Satchmo is an open source e-commerce framework built on Python and Django. It is actively developed, has a number of features, and, since it is written in Python/Django, is easily hackable should I have custom needs.
  • Slicehost hosting: I originally thought I could use Webfaction shared hosting since it has served me well for this blog. However, after reading this discussion on Satchmo hosting, I realized I needed more RAM and decided to go with VPS hosting at Slicehost.
  • I plan to use Amazon S3 for media storage. I gained experience with this service at work. Also, after reading Adrian Holovaty's (somewhat dated) post on using Django and S3 for media files, I implemented S3 storage for this blog. With the beta release of Amazon CloudFront, performance should be even better.
  • Other technologies: I'll stick with Ubuntu for the server OS. I'll start with Apache for the web server, since it recommended by Django. I may explore other options if memory becomes a problem. Though I have slightly more experience with MySQL from work, I plan to use PostgreSQL for the database, again, since it is recommended by Django and seems to be a better designed database. I don't know very much about caching, but I plan to look into memcached, nginx, and squid-cache.

Well, those are my plans. I am starting off bright-eyed. Hopefully, the trials of the road ahead will not wreck my spirit. Again, feedback is appreciated! Thanks.

Creating remote server nicknames with .ssh/config

Using the ~/.ssh/config file is an easy way to give your remote machines nicknames and reduce the number of keystrokes needed to login with ssh, rsync, hg push/pull/clone, access files via Emacs Tramp (Transparent Remote (file) Access, Multiple Protocol), or use any other SSH-based tool. You can also set other ssh options such as IdentityFile, Port, or CompressionLevel. For more information and a full list of options, check out the man page for ssh_config or this article by Kimmo Suominen.

Here is part of my ~/.ssh/config file. It defines the nicknames turk, tyran, tuna, and tally for some EC2 servers I've been working with.

Host turk
  User root
  HostName ec2-67-202-21-122.compute-1.amazonaws.com

Host tuna
  User root
  HostName ec2-75-101-178-62.compute-1.amazonaws.com

Host tyran
  User root
  HostName ec2-67-202-43-207.compute-1.amazonaws.com

Host tally
  User root
  HostName ec2-67-202-59-207.compute-1.amazonaws.com

Now, wherever I would normally have typed [email protected], I can just type turk. Here are some examples.

SSH login

Old way:

ssh [email protected]

New way:

ssh turk

rsync

Old way:

rsync -avz myproject [email protected]:/srv

New way:

rsync -avz myproject turk:/srv

Mercurial

Old way:

hg push ssh://[email protected]//srv/myproject

New way:

hg push ssh://turk//srv/myproject

Emacs Tramp

To use your ~/.ssh/config with Emacs Tramp, you will need something like the following in your .emacs:

(tramp-set-completion-function "ssh"
                               '((tramp-parse-sconfig "/etc/ssh_config")
                                 (tramp-parse-sconfig "~/.ssh/config")))

Old way:

C-x C-f /[email protected]:/srv/myproject/myfile.py

New way:

C-x C-f /turk:/srv/myproject/myfile.py

scp

Old way:

scp etc/.screenrc [email protected]:/root

New way:

scp etc/.screenrc turk:/root

Python datetime / time conversions

from datetime import datetime
import time

#-------------------------------------------------
# conversions to strings
#-------------------------------------------------
# datetime object to string
dt_obj = datetime(2008, 11, 10, 17, 53, 59)
date_str = dt_obj.strftime("%Y-%m-%d %H:%M:%S")
print date_str

# time tuple to string
time_tuple = (2008, 11, 12, 13, 51, 18, 2, 317, 0)
date_str = time.strftime("%Y-%m-%d %H:%M:%S", time_tuple)
print date_str

#-------------------------------------------------
# conversions to datetime objects
#-------------------------------------------------
# time tuple to datetime object
time_tuple = (2008, 11, 12, 13, 51, 18, 2, 317, 0)
dt_obj = datetime(*time_tuple[0:6])
print repr(dt_obj)

# date string to datetime object
date_str = "2008-11-10 17:53:59"
dt_obj = datetime.strptime(date_str, "%Y-%m-%d %H:%M:%S")
print repr(dt_obj)

# timestamp to datetime object in local time
timestamp = 1226527167.595983
dt_obj = datetime.fromtimestamp(timestamp)
print repr(dt_obj)

# timestamp to datetime object in UTC
timestamp = 1226527167.595983
dt_obj = datetime.utcfromtimestamp(timestamp)
print repr(dt_obj)

#-------------------------------------------------
# conversions to time tuples
#-------------------------------------------------
# datetime object to time tuple
dt_obj = datetime(2008, 11, 10, 17, 53, 59)
time_tuple = dt_obj.timetuple()
print repr(time_tuple)

# string to time tuple
date_str = "2008-11-10 17:53:59"
time_tuple = time.strptime(date_str, "%Y-%m-%d %H:%M:%S")
print repr(time_tuple)

# timestamp to time tuple in UTC
timestamp = 1226527167.595983
time_tuple = time.gmtime(timestamp)
print repr(time_tuple)

# timestamp to time tuple in local time
timestamp = 1226527167.595983
time_tuple = time.localtime(timestamp)
print repr(time_tuple)

#-------------------------------------------------
# conversions to timestamps
#-------------------------------------------------
# time tuple in local time to timestamp
time_tuple = (2008, 11, 12, 13, 59, 27, 2, 317, 0)
timestamp = time.mktime(time_tuple)
print repr(timestamp)

# time tuple in utc time to timestamp
time_tuple_utc = (2008, 11, 12, 13, 59, 27, 2, 317, 0)
timestamp_utc = calendar.timegm(time_tuple_utc)
print repr(timestamp_utc)

#-------------------------------------------------
# results
#-------------------------------------------------
# 2008-11-10 17:53:59
# 2008-11-12 13:51:18
# datetime.datetime(2008, 11, 12, 13, 51, 18)
# datetime.datetime(2008, 11, 10, 17, 53, 59)
# datetime.datetime(2008, 11, 12, 13, 59, 27, 595983)
# datetime.datetime(2008, 11, 12, 21, 59, 27, 595983)
# (2008, 11, 10, 17, 53, 59, 0, 315, -1)
# (2008, 11, 10, 17, 53, 59, 0, 315, -1)
# (2008, 11, 12, 21, 59, 27, 2, 317, 0)
# (2008, 11, 12, 13, 59, 27, 2, 317, 0)
# 1226527167.0
# 1226498367

How to escape (percent-encode) a URL with Python

import urllib

print urllib.quote_plus("http://www.yahoo.com/")
print urllib.quote_plus("Kruder & Dorfmeister")

Results:

http%3A%2F%2Fwww.yahoo.com%2F
Kruder+%26+Dorfmeister

It is easy to be drawn to the urlencode function in the Python urllib module documentation. But for simple escaping, only quote_plus, or possibly quote is needed. I believe this is the appropriate solution to Python urlencode annoyance and O'Reilly's Amazon Hack #92.

For reference: Percent-encoding on Wikipedia