SaltyCrane Blog — Notes on JavaScript and web development

How to reverse words in a sentence using Python and C

This is a technical problem I attempted recently. The problem was to reverse the words in a sentence. For example, The quick brown fox jumped over the lazy dog. becomes dog. lazy the over jumped fox brown quick The. I had to solve the problem first using Python, and then using C. In addition, the C version could only use 1 extra character of memory. I solved the Python version easily, but the C version was too difficult for me. Here are possible solutions.

Python version

sentence = "The quick brown fox jumped over the lazy dog."
words = sentence.split()
sentence_rev = " ".join(reversed(words))
print sentence_rev

C version

Credit for this solution goes to Hai Vu

#include <stdio.h>

/* function declarations */
void reverse_words(char *sentence);
void reverse_chars(char *left, char *right);

/* main program */
int main()
{
   char mysentence[] = "The quick brown fox jumped over the lazy dog.";

   reverse_words(mysentence);
   printf("%s\n", mysentence);

   return 0;
}

/* reverse the words in a sentence */
void reverse_words(char *sentence)
{
   char *start = sentence;
   char *end = sentence;

   /* find the end of the sentence */
   while (*end != '\0') {
      ++end;
   }
   --end;

   /* reverse the characters in the sentence */
   reverse_chars(start, end);
   
   /* reverse the characters in each word */
   while (*start != '\0') {
      /* move start pointer to the beginning of the next word */
      for (; *start != '\0' && *start == ' '; start++) ;

      /* move end pointer to the end of the next word */
      for (end=start; *end != '\0' && *end != ' '; end++) ;
      --end;

      /* reverse the characters in the word */
      reverse_chars(start, end);

      /* move to next word */
      start = ++end;
   }
}

/* reverse the characters in a string */
void reverse_chars(char *left, char *right)
{
   char temp;

   while( left < right) {
      temp = *left;
      *left = *right;
      *right = temp;
      ++left;
      --right;
   }
}

How to convert a PNM file to PDF with Python

  • Install the Python Imaging Library (PIL)
    On Ubuntu/Debian, use:
    sudo apt-get install python-imaging
  • Create a file called convert_pnm_to_pdf.py:
    import Image
    import os
    import sys
    
    filename = sys.argv[1]
    try:
        newfilename = os.path.splitext(filename)[0] + ".pdf"
        Image.open(filename).save(newfilename)
        print "Converted " + newfilename
    except IOError:
        print "Cannot convert" + newfilename
    
  • Run the script:
    python convert_pnm_to_pdf.py yourfile.pnm
    A PDF file named yourfile.pdf will be created

The PIL also supports many other file formats including BMP, GIF, JPEG, PNG, and TIFF. For more information, see the Python Imaging Library Handbook

My Python geek list

I noticed a few Python people had switched from Google Code to github or bitbucket. Then I thought, "Hey, maybe I can gather interesting information about my favorite Python geeks in a table!" Then I started making said table. Then I thought, "This is a dumb idea!" But I decided to post the table anyways. So, here it is: a dumb table of smart people.

BlogNetCodeViaOther
Adam Gomaatwitter iconbitbucketDjango People
Bob Ippolitotwitter iconGoogle Codesimplejson
David Beazlytwitter iconPython Essential Reference, Course on Coroutines, PLY
Glyph Lefkowitztwitter iconTwisted
Guido van Rossumtwitter iconGoogle CodePythonWikipedia
Ian Bickingtwitter iconbitbucketSQLObject, virtualenv, pip
Jack DiederichClass Decorators PyCon talk
Jacob Kaplan-Mosstwitter icongithubDjangoDjango People
James Bennetttwitter iconbitbucketDjangoDjango People
James Taubertwitter icongithubPinaxDjango People
Simon Willisontwitter iconGoogle Code, githubDjangoWikipedia
Django People

Scripting wmii column widths with Python

I mentioned in my previous post on using wmii with Gnome that I had written a script for resizing the column widths in wmii. This is the followup post. Note, I am using the 20080520 snapshot of wmii. This doesn't work with wmii 3.6 (as Marco commented below).

To incrementally change window sizes, I use the following in my ~/.wmii-3.5/wmiirc file:

	Key $MODKEY-y
		# shrink horizontally
		wmiir xwrite /tag/sel/ctl grow sel sel right -10
		wmiir xwrite /tag/sel/ctl grow sel sel left -10
	Key $MODKEY-u
		# grow horizontally
		wmiir xwrite /tag/sel/ctl grow sel sel right 10
		wmiir xwrite /tag/sel/ctl grow sel sel left 10
	Key $MODKEY-i
		# shrink vertically
		wmiir xwrite /tag/sel/ctl grow sel sel down -10
		wmiir xwrite /tag/sel/ctl grow sel sel up -10
	Key $MODKEY-o
		# grow vertically
		wmiir xwrite /tag/sel/ctl grow sel sel down 10
		wmiir xwrite /tag/sel/ctl grow sel sel up 10
	Key $MODKEY-Shift-y
		# shrink horizontally
		wmiir xwrite /tag/sel/ctl grow sel sel right -2
		wmiir xwrite /tag/sel/ctl grow sel sel left -2
	Key $MODKEY-Shift-u
		# grow horizontally
		wmiir xwrite /tag/sel/ctl grow sel sel right 2
		wmiir xwrite /tag/sel/ctl grow sel sel left 2
	Key $MODKEY-Shift-i
		# shrink vertically
		wmiir xwrite /tag/sel/ctl grow sel sel down -2
		wmiir xwrite /tag/sel/ctl grow sel sel up -2
	Key $MODKEY-Shift-o
		# grow vertically
		wmiir xwrite /tag/sel/ctl grow sel sel down 2
		wmiir xwrite /tag/sel/ctl grow sel sel up 2

In addition to incrementally changing column widths, I wanted to be able to switch to predetermined column width ratios with a keyboard shortcut. For example, I wanted to be able to set the column widths at a 20/80 ratio, a 40/60 ratio, a 50/50 ratio, a 60/40 ratio, and so on. So I hacked a Python script to do this. It is pretty ugly because I first grow the window by a set amount, measure the change in size, then grow it again to the correct width. If anyone knows of a better way to do this, please let me know. I'm posting my solution here in case anyone else wanted to do the same thing and got stuck. (Note, this script only works with two columns)

UPDATE 2009-12-21: I just learned from the new wmii documentation that I can specify a grow amount in pixels by suffixing it with "px". This means I no longer have to perform the ugly, extra grow-then-measure step in my script. I'm not sure if this is a newly added change or if it is just newly documented. I am now using wmii 3.9b1. I have updated the script below to use the new method. Also, the script now works with more than two columns. I kept the old method for reference.

#!/usr/bin/env python

import os
import re
import sys

class Wmii:
    """
    wmiir xwrite /tag/sel/ctl grow col row side increment
    col: column number of the window to grow
    row: row number of the window to grow
    side: the side to grow. one of left, right, up, or down
    increment: the number of pixels to grow. use a positive number to grow larger
    and a negative number to grow smaller
    """
    def set_column_widths(self, width_list):
        """Use the 'grow' command to set the column widths to those specified.
        Widths are specified in percentages.
        """
        total_width_perc = sum([float(width) for width in width_list])
        for i, width_perc in enumerate(width_list[:-1]):
            self.read_current_col_widths()
            total_width_px = float(sum(self.curr_colwidths))
            new_width_px =  float(width_perc) / total_width_perc * total_width_px
            grow_amount_px = int(round(new_width_px - self.curr_colwidths[i]))
            self.xwrite("/tag/sel/ctl grow %d 1 right %dpx" % (i+1, grow_amount_px))

    def read_current_col_widths(self):
        """'wmiir read /tag/sel/index' and set the attribute, self.curr_colwidths.
        self.curr_colwidths is a list of the width (ints) (in pixels) of each
        column in the view.
        """
        lines = self.read("/tag/sel/index")
        self.curr_colwidths = []
        for line in lines:
            match = re.search(r"# [^~]+ \d+ (\d+)", line)
            if match:
                self.curr_colwidths.append(int(match.group(1)))
        print self.curr_colwidths

    def xwrite(self, path_and_value):
        """Use the xwrite form."""
        cmd = "wmiir xwrite %s" % path_and_value
        print cmd
        os.system(cmd)

    def read(self, path):
        """Return a list of the lines returned by "wmii read path" """
        return os.popen4("wmiir read " + path)[1].readlines()

if __name__ == "__main__":
    w = Wmii()
    w.set_column_widths(sys.argv[1:])
Old method (for reference):
#!/usr/bin/env python

import os
import re
import sys

class Wmii:
    """
    wmiir xwrite /tag/sel/ctl grow col row side increment
    col: column number of the window to grow
    row: row number of the window to grow
    side: the side to grow. one of left, right, up, or down
    increment: the number of pixels to grow. use a positive number to grow larger
    and a negative number to grow smaller
    """
    def __init__(self):
        pass

    def set_column_widths(self, width0, width1):
        """Use the 'grow' command to set the column widths to those specified.
        Widths are specified in percentages.
        Currently only works with 2 columns.
        """
        self.determine_pixels_per_grow_horiz()
        new_width0 = sum(self.curr_colwidths) * (float(width0) /
                                                 (float(width0)+float(width1)))
        grow_amount = int(round((new_width0-self.curr_colwidths[0]) /
                                self.pixels_per_grow_increment))
        self.xwrite("/tag/sel/ctl grow 1 1 right %d" % grow_amount)

    def determine_pixels_per_grow_horiz(self):
        """Try growing by an increment of 1 and record the number of pixels changed.
        """
        self.read_current_col_widths()
        prev_colwidth0 = self.curr_colwidths[0]
        self.xwrite("/tag/sel/ctl grow 1 1 right 1")
        self.read_current_col_widths()
        self.pixels_per_grow_increment = self.curr_colwidths[0] - prev_colwidth0

    def read_current_col_widths(self):
        """'wmiir read /tag/sel/index' and set the attribute, self.curr_colwidths.
        self.curr_colwidths is a list of the width (ints) (in pixels) of each
        column in the view.
        """
        lines = self.read("/tag/sel/index")
        self.curr_colwidths = []
        for line in lines:
            match = re.search(r"# [^~]+ \d+ (\d+)", line)
            if match:
                self.curr_colwidths.append(int(match.group(1)))
        print self.curr_colwidths

    def read_current_column_number(self):
        """'wmiir read /tag/sel/ctl' and set the attribute, self.curr_col."""
        lines = self.read("/tag/sel/ctl")
        self.curr_col = re.split(" ", lines[1])[1]
        print "curr_col = %s" % self.curr_col

    def xwrite(self, path_and_value):
        """Use the xwrite form."""
        cmd = "wmiir xwrite %s" % path_and_value
        print cmd
        os.system(cmd)

    def read(self, path):
        """Return a list of the lines returned by "wmii read path" """
        return os.popen4("wmiir read " + path)[1].readlines()

if __name__ == "__main__":
    w = Wmii()
    w.set_column_widths(sys.argv[1], sys.argv[2])

I named the script wmii.py, made it executable, and put it on my path. Then I modified my wmiirc:

	Key $MODKEY-q
		wmii.py 20 80
	Key $MODKEY-w
		wmii.py 40 60
	Key $MODKEY-e
		wmii.py 50 50
	Key $MODKEY-r
		wmii.py 60 40
	Key $MODKEY-t
		wmii.py 80 20

Hope that's helpful to someone. Let me know if you've written some cool python wmii scripts.

Consolidating hostings services

I've been using Webfaction shared hosting for this blog and Slicehost VPS hosting for our greeting card store. I've had no problems with Webfaction, but I'd like to put both sites on one host, so I am moving this blog to Slicehost. Later, I might switch from Slicehost to Linode because they seem to have lower prices than Slicehost. But I thought I'll do one step at a time. I've already moved my code over and everything seems to be working OK. I plan to switch the nameservers soon after I put this post up. If any comments are posted during the switch, they might get lost. My domain registrar says it might take ~48 hours to be complete. I'm not sure exactly how that works. I will change the footer at the bottom of the Slicehost-hosted site to read "Hosted by Slicehost" while the Webfaction site will continue to read "Hosted by Webfaction".

Update 2009-06-22: Here is an endorsement for Slicehost by James Bennett (and many more in the comments). Maybe I should rethink a switch to Linode.

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/'}),
    )